diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 17:14:58 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 17:14:58 +0700 |
| commit | 1ca3b3df3421961caec3b747a364071c80f5c7da (patch) | |
| tree | 6778a1f0f3f9b4c6e26d6d87ccde16e24da6c9d6 /base_accounting_kit/static/src/js/payment_matching.js | |
| parent | b57188be371d36d96caac4b8d65a40745c0e972c (diff) | |
initial commit
Diffstat (limited to 'base_accounting_kit/static/src/js/payment_matching.js')
| -rw-r--r-- | base_accounting_kit/static/src/js/payment_matching.js | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/base_accounting_kit/static/src/js/payment_matching.js b/base_accounting_kit/static/src/js/payment_matching.js new file mode 100644 index 0000000..80a1732 --- /dev/null +++ b/base_accounting_kit/static/src/js/payment_matching.js @@ -0,0 +1,505 @@ +odoo.define('base_accounting_kit.ReconciliationClientAction', function (require) { +"use strict"; + +var AbstractAction = require('web.AbstractAction'); +var ReconciliationModel = require('base_accounting_kit.ReconciliationModel'); +var ReconciliationRenderer = require('base_accounting_kit.ReconciliationRenderer'); +var core = require('web.core'); +var QWeb = core.qweb; + + +/** + * Widget used as action for 'account.bank.statement' reconciliation + */ +var StatementAction = AbstractAction.extend({ + hasControlPanel: true, + withSearchBar: true, + loadControlPanel: true, + title: core._t('Bank Reconciliation'), + contentTemplate: 'reconciliation', + custom_events: { + change_mode: '_onAction', + change_filter: '_onAction', + change_offset: '_onAction', + change_partner: '_onAction', + add_proposition: '_onAction', + remove_proposition: '_onAction', + update_proposition: '_onAction', + create_proposition: '_onAction', + getPartialAmount: '_onActionPartialAmount', + quick_create_proposition: '_onAction', + partial_reconcile: '_onAction', + validate: '_onValidate', + close_statement: '_onCloseStatement', + load_more: '_onLoadMore', + reload: 'reload', + search: '_onSearch', + navigation_move:'_onNavigationMove', + }, + config: _.extend({}, AbstractAction.prototype.config, { + // used to instantiate the model + Model: ReconciliationModel.StatementModel, + // used to instantiate the action interface + ActionRenderer: ReconciliationRenderer.StatementRenderer, + // used to instantiate each widget line + LineRenderer: ReconciliationRenderer.LineRenderer, + // used context params + params: ['statement_line_ids'], + // number of statements/partners/accounts to display + defaultDisplayQty: 10, + // number of moves lines displayed in 'match' mode + limitMoveLines: 15, + }), + + _onNavigationMove: function (ev) { + var non_reconciled_keys = _.keys(_.pick(this.model.lines, function(value, key, object) {return !value.reconciled})); + var currentIndex = _.indexOf(non_reconciled_keys, ev.data.handle); + var widget = false; + switch (ev.data.direction) { + case 'up': + ev.stopPropagation(); + widget = this._getWidget(non_reconciled_keys[currentIndex-1]); + break; + case 'down': + ev.stopPropagation(); + widget = this._getWidget(non_reconciled_keys[currentIndex+1]); + break; + case 'validate': + ev.stopPropagation(); + widget = this._getWidget(non_reconciled_keys[currentIndex]); + widget.$('caption .o_buttons button:visible').click(); + break; + } + if (widget) widget.$el.focus(); + }, + + /** + * @override + * @param {Object} params + * @param {Object} params.context + * + */ + init: function (parent, params) { + this._super.apply(this, arguments); + this.action_manager = parent; + this.params = params; + this.searchModelConfig.modelName = 'account.bank.statement.line'; + this.controlPanelProps.cp_content = {}; +// this.controlPanelParams.modelName = 'account.bank.statement.line'; + this.model = new this.config.Model(this, { + modelName: "account.reconciliation.widget", + defaultDisplayQty: params.params && params.params.defaultDisplayQty || this.config.defaultDisplayQty, + limitMoveLines: params.params && params.params.limitMoveLines || this.config.limitMoveLines, + }); + this.widgets = []; + // Adding values from the context is necessary to put this information in the url via the action manager so that + // you can retrieve it if the person shares his url or presses f5 + _.each(params.params, function (value, name) { + params.context[name] = name.indexOf('_ids') !== -1 ? _.map((value+'').split(','), parseFloat) : value; + }); + params.params = {}; + _.each(this.config.params, function (name) { + if (params.context[name]) { + params.params[name] = params.context[name]; + } + }); + }, + + /** + * instantiate the action renderer + * + * @override + */ + willStart: function () { + var self = this; + var def = this.model.load(this.params.context).then(this._super.bind(this)); + return def.then(function () { + if (!self.model.context || !self.model.context.active_id) { + self.model.context = {'active_id': self.params.context.active_id, + 'active_model': self.params.context.active_model}; + } + var journal_id = self.params.context.journal_id; + if (self.model.context.active_id && self.model.context.active_model === 'account.journal') { + journal_id = journal_id || self.model.context.active_id; + } + if (journal_id) { + var promise = self._rpc({ + model: 'account.journal', + method: 'read', + args: [journal_id, ['display_name']], + }); + } else { + var promise = Promise.resolve(); + } + return promise.then(function (result) { + var title = result && result[0] ? result[0]['display_name'] : self.params.display_name || '' + self._setTitle(title); + self.renderer = new self.config.ActionRenderer(self, self.model, { + 'bank_statement_line_id': self.model.bank_statement_line_id, + 'valuenow': self.model.valuenow, + 'valuemax': self.model.valuemax, + 'defaultDisplayQty': self.model.defaultDisplayQty, + 'title': title, + }); + }); + }); + }, + + reload: function() { + // On reload destroy all rendered line widget, reload data and then rerender widget + var self = this; + + self.$('.o_reconciliation_lines').addClass('d-none'); // prevent the browser from recomputing css after each destroy for HUGE perf improvement on a lot of lines + _.each(this.widgets, function(widget) { + widget.destroy(); + }); + this.widgets = []; + self.$('.o_reconciliation_lines').removeClass('d-none'); + return this.model.reload().then(function() { + return self._renderLinesOrRainbow(); + }); + }, + + _renderLinesOrRainbow: function() { + var self = this; + return self._renderLines().then(function() { + var initialState = self.renderer._initialState; + var valuenow = self.model.statement ? self.model.statement.value_min : initialState.valuenow; + var valuemax = self.model.statement ? self.model.statement.value_max : initialState.valuemax; + // No more lines to reconcile, trigger the rainbowman. + if(valuenow === valuemax){ + initialState.valuenow = valuenow; + initialState.context = self.model.getContext(); + self.renderer.showRainbowMan(initialState); + self.remove_cp(); + }else{ + // Create a notification if some lines have been reconciled automatically. + if(initialState.valuenow > 0) + self.renderer._renderNotifications(self.model.statement.notifications); + self._openFirstLine(); + self.renderer.$('[data-toggle="tooltip"]').tooltip(); + self.do_show(); + } + }); + }, + + /** + * append the renderer and instantiate the line renderers + * + * @override + */ + start: function () { + var self = this; + var args = arguments; + var sup = this._super; + + return this.renderer.prependTo(self.$('.o_form_sheet')).then(function() { + return self._renderLinesOrRainbow().then(function() { + self.do_show(); + return sup.apply(self, args); + }); + }); + }, + + /** + * update the control panel and breadcrumbs + * + * @override + */ + do_show: function () { + this._super.apply(this, arguments); + if (this.action_manager) { + this.$pager = $(QWeb.render('reconciliation.control.pager', {widget: this.renderer})); + this.controlPanelProps.cp_content = {$pager: this.$pager}; +// this.updateControlPanel({ +// clear: true, +// cp_content: { +// $pager: this.$pager, +// }, +// }); + this.renderer.$progress = this.$pager; + $(this.renderer.$progress).parent().css('width', '100%').css('padding-left', '0'); + } + }, + + remove_cp: function() { + this.controlPanelProps.cp_content = {}; +// this.updateControlPanel({ +// clear: true, +// }); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {string} handle + * @returns {Widget} widget line + */ + _getWidget: function (handle) { + return _.find(this.widgets, function (widget) {return widget.handle===handle;}); + }, + + /** + * + */ + _loadMore: function(qty) { + var self = this; + return this.model.loadMore(qty).then(function () { + return self._renderLines(); + }); + }, + /** + * sitch to 'match' the first available line + * + * @private + */ + _openFirstLine: function (previous_handle) { + var self = this; + previous_handle = previous_handle || 'rline0'; + var handle = _.compact(_.map(this.model.lines, function (line, handle) { + return (line.reconciled || (parseInt(handle.substr(5)) < parseInt(previous_handle.substr(5)))) ? null : handle; + }))[0]; + if (handle) { + var line = this.model.getLine(handle); + this.model.changeMode(handle, 'default').then(function () { + self._getWidget(handle).update(line); + }).guardedCatch(function(){ + self._getWidget(handle).update(line); + }).then(function() { + self._getWidget(handle).$el.focus(); + } + ); + } + return handle; + }, + + _forceUpdate: function() { + var self = this; + _.each(this.model.lines, function(handle) { + var widget = self._getWidget(handle['handle']); + if (widget && handle.need_update) { + widget.update(handle); + widget.need_update = false; + } + }) + }, + /** + * render line widget and append to view + * + * @private + */ + _renderLines: function () { + var self = this; + var linesToDisplay = this.model.getStatementLines(); + var linePromises = []; + _.each(linesToDisplay, function (line, handle) { + var widget = new self.config.LineRenderer(self, self.model, line); + widget.handle = handle; + self.widgets.push(widget); + linePromises.push(widget.appendTo(self.$('.o_reconciliation_lines'))); + }); + if (this.model.hasMoreLines() === false) { + this.renderer.hideLoadMoreButton(true); + } + else { + this.renderer.hideLoadMoreButton(false); + } + return Promise.all(linePromises); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * dispatch on the camelcased event name to model method then update the + * line renderer with the new state. If the mode was switched from 'inactive' + * to 'create' or 'match_rp' or 'match_other', the other lines switch to + * 'inactive' mode + * + * @private + * @param {OdooEvent} event + */ + _onAction: function (event) { + var self = this; + var handle = event.target.handle; + var current_line = this.model.getLine(handle); + this.model[_.str.camelize(event.name)](handle, event.data.data).then(function () { + var widget = self._getWidget(handle); + if (widget) { + widget.update(current_line); + } + if (current_line.mode !== 'inactive') { + _.each(self.model.lines, function (line, _handle) { + if (line.mode !== 'inactive' && _handle !== handle) { + self.model.changeMode(_handle, 'inactive'); + var widget = self._getWidget(_handle); + if (widget) { + widget.update(line); + } + } + }); + } + }); + }, + + /** + * @private + * @param {OdooEvent} ev + */ + _onSearch: function (ev) { + var self = this; + ev.stopPropagation(); + this.model.domain = ev.data.domain; + this.model.display_context = 'search'; + self.reload().then(function() { + self.renderer._updateProgressBar({ + 'valuenow': self.model.valuenow, + 'valuemax': self.model.valuemax, + }); + }); + }, + + _onActionPartialAmount: function(event) { + var self = this; + var handle = event.target.handle; + var line = this.model.getLine(handle); + var amount = this.model.getPartialReconcileAmount(handle, event.data); + self._getWidget(handle).updatePartialAmount(event.data.data, amount); + }, + + /** + * call 'closeStatement' model method + * + * @private + * @param {OdooEvent} event + */ + _onCloseStatement: function (event) { + var self = this; + return this.model.closeStatement().then(function (result) { + self.do_action({ + name: 'Bank Statements', + res_model: 'account.bank.statement.line', + res_id: result, + views: [[false, 'form']], + type: 'ir.actions.act_window', + view_mode: 'form', + }); + $('.o_reward').remove(); + }); + }, + /** + * Load more statement and render them + * + * @param {OdooEvent} event + */ + _onLoadMore: function (event) { + return this._loadMore(this.model.defaultDisplayQty); + }, + /** + * call 'validate' model method then destroy the + * validated lines and update the action renderer with the new status bar + * values and notifications then open the first available line + * + * @private + * @param {OdooEvent} event + */ + _onValidate: function (event) { + var self = this; + var handle = event.target.handle; + this.model.validate(handle).then(function (result) { + self.renderer.update({ + 'valuenow': self.model.valuenow, + 'valuemax': self.model.valuemax, + 'title': self.title, + 'time': Date.now()-self.time, + 'notifications': result.notifications, + 'context': self.model.getContext(), + }); + self._forceUpdate(); + _.each(result.handles, function (handle) { + var widget = self._getWidget(handle); + if (widget) { + widget.destroy(); + var index = _.findIndex(self.widgets, function (widget) {return widget.handle===handle;}); + self.widgets.splice(index, 1); + } + }); + // Get number of widget and if less than constant and if there are more to laod, load until constant + if (self.widgets.length < self.model.defaultDisplayQty + && self.model.valuemax - self.model.valuenow >= self.model.defaultDisplayQty) { + var toLoad = self.model.defaultDisplayQty - self.widgets.length; + self._loadMore(toLoad); + } + self._openFirstLine(handle); + }); + }, +}); + + +/** + * Widget used as action for 'account.move.line' and 'res.partner' for the + * manual reconciliation and mark data as reconciliate + */ +var ManualAction = StatementAction.extend({ + title: core._t('Journal Items to Reconcile'), + withSearchBar: false, + config: _.extend({}, StatementAction.prototype.config, { + Model: ReconciliationModel.ManualModel, + ActionRenderer: ReconciliationRenderer.ManualRenderer, + LineRenderer: ReconciliationRenderer.ManualLineRenderer, + params: ['company_ids', 'mode', 'partner_ids', 'account_ids'], + defaultDisplayQty: 30, + limitMoveLines: 15, + }), + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * call 'validate' model method then destroy the + * reconcilied lines, update the not reconcilied and update the action + * renderer with the new status bar values and notifications then open the + * first available line + * + * @private + * @param {OdooEvent} event + */ + _onValidate: function (event) { + var self = this; + var handle = event.target.handle; + var method = 'validate'; + this.model[method](handle).then(function (result) { + _.each(result.reconciled, function (handle) { + self._getWidget(handle).destroy(); + }); + _.each(result.updated, function (handle) { + self._getWidget(handle).update(self.model.getLine(handle)); + }); + self.renderer.update({ + valuenow: _.compact(_.invoke(self.widgets, 'isDestroyed')).length, + valuemax: self.widgets.length, + title: self.title, + time: Date.now()-self.time, + }); + if(!_.any(result.updated, function (handle) { + return self.model.getLine(handle).mode !== 'inactive'; + })) { + self._openFirstLine(handle); + } + }); + }, +}); + +core.action_registry.add('bank_statement_reconciliation_view', StatementAction); +core.action_registry.add('manual_reconciliation_view', ManualAction); + +return { + StatementAction: StatementAction, + ManualAction: ManualAction, +}; +}); |
