Skip to content

Commit

Permalink
feature(Sales): dispatch documents and evaluate document transport co…
Browse files Browse the repository at this point in the history
…nfig
  • Loading branch information
corneliusweiss committed Nov 6, 2024
1 parent 2a82041 commit 3533376
Show file tree
Hide file tree
Showing 22 changed files with 768 additions and 49 deletions.
2 changes: 1 addition & 1 deletion tine20/Filemanager/js/QuickLookMediaPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @copyright Copyright (c) 2022 Metaways Infosystems GmbH (http://www.metaways.de)
*/

MediaPanel = Ext.extend(Ext.Panel, {
const MediaPanel = Ext.extend(Ext.Panel, {
border: false,

initComponent: function() {
Expand Down
15 changes: 12 additions & 3 deletions tine20/Sales/Frontend/Http.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,18 @@ class Sales_Frontend_Http extends Tinebase_Frontend_Http_Abstract

public function getXRechnungView(string $fileNodeId): void
{
echo (new Sales_EDocument_Service_View())->getXRechnungView(
Filemanager_Controller_Node::getInstance()->get($fileNodeId)
);
try {
echo (new Sales_EDocument_Service_View())->getXRechnungView(
Filemanager_Controller_Node::getInstance()->get($fileNodeId)
);
} catch (Exception $e) {
$twig = new Tinebase_Twig(Tinebase_Core::getLocale(), Tinebase_Translation::getTranslation('Sales'));

$template = $twig->load('Sales/views/EDocumentViewError.html.twig');

$renderContext = [];
echo $template->render($renderContext);
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions tine20/Sales/Frontend/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class Sales_Frontend_Json extends Tinebase_Frontend_Json_Abstract
Sales_Model_EDocument_EAS::MODEL_NAME_PART,
Sales_Model_Einvoice_XRechnung::MODEL_NAME_PART,
Sales_Model_Document_AttachedDocument::MODEL_NAME_PART,
Sales_Model_Document_DispatchHistory::MODEL_NAME_PART,
// 'OrderConfirmation',
// 'PurchaseInvoice',
// 'Offer',
Expand Down
13 changes: 11 additions & 2 deletions tine20/Sales/Model/Document/Abstract.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ abstract class Sales_Model_Document_Abstract extends Tinebase_Record_NewAbstract
'cpintern_id' => [],
],
],
self::FLD_DEBITOR_ID => [],
self::FLD_DEBITOR_ID => [
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
Sales_Model_Debitor::FLD_EAS_ID => [],
],
],
self::FLD_RECIPIENT_ID => [
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
Sales_Model_Address::FLD_DEBITOR_ID => [],
Expand All @@ -156,7 +160,12 @@ abstract class Sales_Model_Document_Abstract extends Tinebase_Record_NewAbstract
Sales_Model_DocumentPosition_Abstract::FLD_PRECURSOR_POSITION => [],
],
],
self::FLD_ATTACHED_DOCUMENTS => [],
self::FLD_ATTACHED_DOCUMENTS => [
Tinebase_Record_Expander::EXPANDER_PROPERTIES => [
Sales_Model_Document_AttachedDocument::FLD_DISPATCH_HISTORY => [],
],
],
self::FLD_CONTACT_ID => [],
]
],

Expand Down
3 changes: 3 additions & 0 deletions tine20/Sales/Model/Document/AttachedDocument.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class Sales_Model_Document_AttachedDocument extends Tinebase_Record_NewAbstract
self::APP_NAME => Sales_Config::APP_NAME,
self::MODEL_NAME => self::MODEL_NAME_PART,

self::EXPOSE_JSON_API => true,

self::TABLE => [
self::NAME => self::TABLE_NAME,
self::INDEXES => [
Expand Down Expand Up @@ -123,6 +125,7 @@ class Sales_Model_Document_AttachedDocument extends Tinebase_Record_NewAbstract
self::FLD_DISPATCH_HISTORY => [
self::TYPE => self::TYPE_RECORDS,
self::CONFIG => [
self::DEPENDENT_RECORDS => true,
self::APP_NAME => Sales_Config::APP_NAME,
self::MODEL_NAME => Sales_Model_Document_DispatchHistory::MODEL_NAME_PART,
self::REF_ID_FIELD => Sales_Model_Document_DispatchHistory::FLD_ATTACHED_DOCUMENT_ID,
Expand Down
27 changes: 27 additions & 0 deletions tine20/Sales/Model/Document/DispatchHistory.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Sales_Model_Document_DispatchHistory extends Tinebase_Record_NewAbstract


public const FLD_ATTACHED_DOCUMENT_ID = 'attached_document_id';
public const FLD_DISPATCH_DATE = 'dispatch_date';
public const FLD_DISPATCH_TRANSPORT = 'dispatch_transport';
public const FLD_DISPATCH_REPORT = 'dispatch_report';

/**
* Holds the model configuration (must be assigned in the concrete class)
Expand All @@ -32,6 +35,7 @@ class Sales_Model_Document_DispatchHistory extends Tinebase_Record_NewAbstract
self::VERSION => 1,
self::MODLOG_ACTIVE => true,
self::IS_DEPENDENT => true,
self::HAS_ATTACHMENTS => true,

self::APP_NAME => Sales_Config::APP_NAME,
self::MODEL_NAME => self::MODEL_NAME_PART,
Expand All @@ -46,6 +50,29 @@ class Sales_Model_Document_DispatchHistory extends Tinebase_Record_NewAbstract
],

self::FIELDS => [
self::FLD_DISPATCH_DATE => [
self::LABEL => 'Dispatch Date', //_('Dispatch Date')
self::TYPE => self::TYPE_DATE,
self::NULLABLE => false,
self::UI_CONFIG => [
'format' => ['medium'],
],
],
self::FLD_DISPATCH_TRANSPORT => [
self::TYPE => self::TYPE_KEY_FIELD,
self::LABEL => 'Dispatch Transport Method', // _('Dispatch Transport Method')
self::NAME => Sales_Config::EDOCUMENT_TRANSPORT,
self::NULLABLE => false,
self::CONFIG => [
self::APP_NAME => Sales_Config::APP_NAME,
],
],
self::FLD_DISPATCH_REPORT => [
self::LABEL => 'Dispatch Report', //_('Dispatch Report')
self::TYPE => self::TYPE_TEXT,
self::NULLABLE => true,
self::QUERY_FILTER => true,
],
self::FLD_ATTACHED_DOCUMENT_ID => [
self::TYPE => self::TYPE_RECORD,
self::NULLABLE => true,
Expand Down
8 changes: 8 additions & 0 deletions tine20/Sales/css/Sales.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
background-image:url(../../images/icon-set/icon_invoice_cancle.svg) !important;
}

.action_dispatch_document {
background-image:url(../../images/icon-set/icon_email_out.svg) !important;
}

.action_book_document {
background-image:url(../../images/icon-set/icon_booked.svg) !important;
}

.action_rebill {
background-image:url(../../images/icon-set/icon_return.svg) !important;
}
Expand Down
55 changes: 55 additions & 0 deletions tine20/Sales/js/Document/AbstractAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const abstractAction = Ext.extend(Ext.Action, {


/**
* @param config
*
* maskMsg: 'Please Wait...',
* documentType: '', // one of Offer|Order|Delivery|Invoice
*
*/
constructor: function (config) {
config.app = Tine.Tinebase.appMgr.get('Sales')
config.recordClass = Tine.Tinebase.data.RecordMgr.get(`Sales.Document_${config.documentType}`)
config.statusFieldName = `${config.documentType.toLowerCase()}_status`
config.statusDef = Tine.Tinebase.widgets.keyfield.getDefinitionFromMC(config.recordClass, config.statusFieldName)

Ext.Action.prototype.constructor.call(this, config);
},
// NOTE: action updater is not executed in action but in component of the action
// so it does not work to define it here
// actionUpdater(action, grants, records, isFilterSelect, filteredContainers) {
// let enabled = records.length === 1 // no batch processing yet, needs a robust concept!
// action.setDisabled(!enabled)
// action.baseAction.setDisabled(!enabled) // WTF?
// },
handler: async function(cmp) {
// @TODO working with this might be a bad idea as it's excecuted here only and not in constructor?


// this.recordsName = recordClass.getRecordsName()
this.selections = [...this.initialConfig.selections]
this.errorMsgs = []
this.editDialog = cmp.findParentBy((c) => {return c instanceof Tine.widgets.dialog.EditDialog})
this.maskEl = cmp.findParentBy((c) => {return c instanceof Tine.widgets.dialog.EditDialog || c instanceof Tine.widgets.MainScreen }).getEl()
this.mask = new Ext.LoadMask(this.maskEl, { msg: this.maskMsg || this.app.i18n._('Please wait...') })

this.unbooked = this.selections.reduce((unbooked, record) => {
record.noProxy = true // kill grid autoSave
const status = record.get(this.statusFieldName)
return unbooked.concat(this.statusDef.records.find((r) => { return r.id === status })?.booked ? [] : [record])
}, [])

// if (editDialog) {
// try {
// await editDialog.isValid()
// } catch (e) {
// return
// }
// }
//
// this.handle(options)
}
});

export default abstractAction
96 changes: 96 additions & 0 deletions tine20/Sales/js/Document/BookDocumentAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Tine 2.0
*
* @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
* @author Cornelius Weiss <[email protected]>
* @copyright Copyright (c) 2022 Metaways Infosystems GmbH (http://www.metaways.de)
*/
import AbstractAction from "./AbstractAction";

Promise.all([Tine.Tinebase.appMgr.isInitialised('Sales'),
Tine.Tinebase.ApplicationStarter.isInitialised()]).then(() => {
const app = Tine.Tinebase.appMgr.get('Sales')

const getAction = (type, config) => {
return new AbstractAction({
documentType: type,
text: app.i18n._('Book Document'),
iconCls: `action_book_document`,
actionUpdater(action, grants, records, isFilterSelect, filteredContainers) {
let enabled = records.length

enabled = records.reduce((enabled, record) => {
return enabled && !_.find(action.statusDef.records, {id: record.get(action.statusFieldName) })?.booked
}, enabled)

// action.setDisabled(!enabled) // this is the component itsef
action.baseAction.setDisabled(!enabled) // this is the action which sets all instances
},
handler: async function(cmp) {
AbstractAction.prototype.handler.call(this, cmp);

// @TODO: maybe we should define default booked state somehow? e.g. offer should be accepted (not only send) or let the user select?
const bookedState = this.statusDef.records.find((r) => { return r.booked })
this.mask.show()

try {
// check if date is set and ask if user want's to change it to today
const notToday = _.reduce(this.unbooked, (acc, record) => {
return _.concat(acc, record.get('date') && record.get('date').format('Ymd') !== new Date().format('Ymd') ? record : []);
}, [])
if (notToday.length) {
_.each(await Tine.widgets.dialog.MultiOptionsDialog.getOption({
title: this.app.formatMessage('Change Document Date?'),
questionText: this.app.formatMessage('Please select the { sourceRecordsName } where you want to change the document date to today.', { sourceRecordsName: this.recordClass.getRecordsName() }),
allowMultiple: true,
allowEmpty: true,
allowCancel: false,
height: notToday.length * 30 + 100,
options: notToday.map((source) => {
return { text: source.getTitle() + ': ' + Tine.Tinebase.common.dateRenderer(source.get('date')), name: source.id, checked: false, source }
})
}), (option) => { _.find(unbooked, { id: option.name }).set('date', new Date().clearTime()); debugger});
}
} catch (e) {/* USERABORT -> continue */ }

await this.unbooked.asyncForEach(async (record) => {
if (record.phantom) {
record = await this.recordClass.getProxy().promiseSaveRecord(record)
if (this.recordClass === this.editDialog?.recordClass) {
this.editDialog ? await this.editDialog.loadRecord(record) : null
}
}
record.set(this.statusFieldName, bookedState.id)
let updatedRecord
try {
updatedRecord = await this.recordClass.getProxy().promiseSaveRecord(record)
this.selections.splice.apply(this.selections, [this.selections.indexOf(record), 1].concat(updatedRecord ? [updatedRecord] : []))
if (this.recordClass === this.editDialog?.recordClass) {
this.editDialog ? await this.editDialog.loadRecord(updatedRecord) : null
}
} catch (e) {
record.reject()
this.errorMsgs.push(this.app.formatMessage('Cannot book { sourceDocument }: ({e.code}) { e.message }', { sourceDocument: record.getTitle(), e }))
}
})
this.mask.hide()

if (this.errorMsgs.length) {
await Ext.MessageBox.show({
buttons: Ext.Msg.OK,
icon: Ext.MessageBox.WARNING,
title: this.app.formatMessage('There where Errors:'),
msg: this.errorMsgs.join('<br />')
})
}
}
})
}
['Offer', 'Order', 'Delivery', 'Invoice'].forEach((type) => {
const action = getAction(type, {})
const medBtnStyle = { scale: 'medium', rowspan: 2, iconAlign: 'top'}
Ext.ux.ItemRegistry.registerItem(`Sales-Document_${type}-GridPanel-ContextMenu`, action, 2)
Ext.ux.ItemRegistry.registerItem(`Sales-Document_${type}-editDialog-Toolbar`, Ext.apply(new Ext.Button(action), medBtnStyle), 30)
Ext.ux.ItemRegistry.registerItem(`Sales-Document_${type}-GridPanel-ActionToolbar-leftbtngrp`, Ext.apply(new Ext.Button(action), medBtnStyle), 30)
})
})
2 changes: 1 addition & 1 deletion tine20/Sales/js/Document/CreateFollowUpAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Promise.all([Tine.Tinebase.appMgr.isInitialised('Sales'),
options: notToday.map((source) => {
return { text: source.getTitle() + ': ' + Tine.Tinebase.common.dateRenderer(source.get('date')), name: source.id, checked: false, source }
})
}), (option) => { _.find(unbooked, { id: option.name }).set('date', new Date().clearTime()); debugger});
}), (option) => { _.find(unbooked, { id: option.name }).set('date', new Date().clearTime()) });
} catch (e) {/* USERABORT -> continue */ }

await unbooked.asyncForEach(async (record) => {
Expand Down
Loading

0 comments on commit 3533376

Please sign in to comment.