diff options
| author | wan <wan@odoo.com> | 2019-10-09 13:56:00 +0000 |
|---|---|---|
| committer | wan <wan@odoo.com> | 2019-10-18 12:53:41 +0000 |
| commit | 9ba8734f15e1a292ca27b1a026e8366a91b2a8c9 (patch) | |
| tree | 490e30cb5b674cd9ee82240e8be92e29f192d476 /addons/account/static | |
| parent | 48e563f0bfdc179852ed5ac101b8f46e866b83a5 (diff) | |
[MOV] account: reconciliation widget
Task 2034073
The reconciliation widget is now entirely part of the enterprise version
as it is an advanced accounting feature
closes odoo/odoo#38424
Signed-off-by: Quentin De Paoli (qdp) <qdp@openerp.com>
Diffstat (limited to 'addons/account/static')
6 files changed, 0 insertions, 5890 deletions
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_action.js b/addons/account/static/src/js/reconciliation/reconciliation_action.js deleted file mode 100644 index 3c7969ed1b4..00000000000 --- a/addons/account/static/src/js/reconciliation/reconciliation_action.js +++ /dev/null @@ -1,499 +0,0 @@ -odoo.define('account.ReconciliationClientAction', function (require) { -"use strict"; - -var AbstractAction = require('web.AbstractAction'); -var ReconciliationModel = require('account.ReconciliationModel'); -var ReconciliationRenderer = require('account.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.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}; - } - if (self.params.context.journal_id) { - self.model.context.active_id = self.params.context.journal_id; - } - if (self.model.context.active_id) { - var promise = self._rpc({ - model: 'account.journal', - method: 'read', - args: [self.model.context.active_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.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.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 () { - var self = this; - - var handle = _.compact(_.map(this.model.lines, function (line, handle) { - return line.reconciled ? 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(); - }); - }, -}); - - -/** - * 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(); - } - }); - }, -}); - -core.action_registry.add('bank_statement_reconciliation_view', StatementAction); -core.action_registry.add('manual_reconciliation_view', ManualAction); - -return { - StatementAction: StatementAction, - ManualAction: ManualAction, -}; -}); diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js deleted file mode 100644 index d76f9941153..00000000000 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ /dev/null @@ -1,1866 +0,0 @@ -odoo.define('account.ReconciliationModel', function (require) { -"use strict"; - -var BasicModel = require('web.BasicModel'); -var field_utils = require('web.field_utils'); -var utils = require('web.utils'); -var session = require('web.session'); -var WarningDialog = require('web.CrashManager').WarningDialog; -var core = require('web.core'); -var _t = core._t; - - -/** - * Model use to fetch, format and update 'account.reconciliation.widget', - * datas allowing reconciliation - * - * The statement internal structure:: - * - * { - * valuenow: integer - * valuenow: valuemax - * [bank_statement_line_id]: { - * id: integer - * display_name: string - * } - * reconcileModels: [object] - * accounts: {id: code} - * } - * - * The internal structure of each line is:: - * - * { - * balance: { - * type: number - show/hide action button - * amount: number - real amount - * amount_str: string - formated amount - * account_code: string - * }, - * st_line: { - * partner_id: integer - * partner_name: string - * } - * mode: string ('inactive', 'match_rp', 'match_other', 'create') - * reconciliation_proposition: { - * id: number|string - * partial_amount: number - * invalid: boolean - through the invalid line (without account, label...) - * is_tax: boolean - * account_code: string - * date: string - * date_maturity: string - * label: string - * amount: number - real amount - * amount_str: string - formated amount - * [already_paid]: boolean - * [partner_id]: integer - * [partner_name]: string - * [account_code]: string - * [journal_id]: { - * id: integer - * display_name: string - * } - * [ref]: string - * [is_partially_reconciled]: boolean - * [to_check]: boolean - * [amount_currency_str]: string|false (amount in record currency) - * } - * mv_lines_match_rp: object - idem than reconciliation_proposition - * mv_lines_match_other: object - idem than reconciliation_proposition - * offset: integer - * limitMoveLines: integer - * filter: string - * [createForm]: { - * account_id: { - * id: integer - * display_name: string - * } - * tax_ids: { - * id: integer - * display_name: string - * } - * analytic_account_id: { - * id: integer - * display_name: string - * } - * analytic_tag_ids: { - * } - * label: string - * amount: number, - * [journal_id]: { - * id: integer - * display_name: string - * } - * } - * } - */ -var StatementModel = BasicModel.extend({ - avoidCreate: false, - quickCreateFields: ['account_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'to_check'], - - // overridden in ManualModel - modes: ['create', 'match_rp', 'match_other'], - - /** - * @override - * - * @param {Widget} parent - * @param {object} options - */ - init: function (parent, options) { - this._super.apply(this, arguments); - this.reconcileModels = []; - this.lines = {}; - this.valuenow = 0; - this.valuemax = 0; - this.alreadyDisplayed = []; - this.domain = []; - this.defaultDisplayQty = options && options.defaultDisplayQty || 10; - this.limitMoveLines = options && options.limitMoveLines || 15; - this.display_context = 'init'; - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * add a reconciliation proposition from the matched lines - * We also display a warning if the user tries to add 2 line with different - * account type - * - * @param {string} handle - * @param {number} mv_line_id - * @returns {Promise} - */ - addProposition: function (handle, mv_line_id) { - var self = this; - var line = this.getLine(handle); - var prop = _.clone(_.find(line['mv_lines_'+line.mode], {'id': mv_line_id})); - this._addProposition(line, prop); - line.limit_override = (line.offset + 1) + this.limitMoveLines; - - // remove all non valid lines - line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (prop) {return !prop.invalid;}); - - // Onchange the partner if not already set on the statement line. - if(!line.st_line.partner_id && line.reconciliation_proposition - && line.reconciliation_proposition.length == 1 && prop.partner_id && line.type === undefined){ - return this.changePartner(handle, {'id': prop.partner_id, 'display_name': prop.partner_name}, true) - .then(function () { - return Promise.all([self._computeLine(line), self._performMoveLine(handle, 'match_rp'), self._performMoveLine(handle, 'match_other')]); - }); - } - - return Promise.all([this._computeLine(line), this._performMoveLine(handle, 'match_rp'), self._performMoveLine(handle, 'match_other')]); - }, - /** - * change the filter for the target line and fetch the new matched lines - * - * @param {string} handle - * @param {string} filter - * @returns {Promise} - */ - changeFilter: function (handle, filter) { - var line = this.getLine(handle); - line['filter_'+line.mode] = filter; - line.offset = 0; - return this._performMoveLine(handle, line.mode); - }, - /** - * change the mode line ('inactive', 'match_rp', 'match_other', 'create'), - * and fetch the new matched lines or prepare to create a new line - * - * ``match_rp`` - * display the matched lines from receivable/payable accounts, the user - * can select the lines to apply there as proposition - * ``match_other`` - * display the other matched lines, the user can select the lines to apply - * there as proposition - * ``create`` - * display fields and quick create button to create a new proposition - * for the reconciliation - * - * @param {string} handle - * @param {'inactive' | 'match_rp' | 'create'} mode - * @returns {Promise} - */ - changeMode: function (handle, mode) { - var self = this; - var line = this.getLine(handle); - if (mode === 'default') { - var match_requests = self.modes.filter(x => x.startsWith('match')).map(x => this._performMoveLine(handle, x)) - return Promise.all(match_requests).then(function() { - return self.changeMode(handle, self._getDefaultMode(handle)); - }); - } - if (mode === 'next') { - var available_modes = self._getAvailableModes(handle) - mode = available_modes[(available_modes.indexOf(line.mode) + 1) % available_modes.length]; - } - line.mode = mode; - if (['match_rp', 'match_other'].includes(line.mode)) { - if (!(line['mv_lines_' + line.mode] && line['mv_lines_' + line.mode].length)) { - return this._performMoveLine(handle, line.mode); - } else { - return this._formatMoveLine(handle, line.mode, line['mv_lines_' + line.mode]); - } - } - if (line.mode === 'create') { - return this.createProposition(handle); - } - return Promise.resolve(); - }, - /** - * change the offset for the matched lines, and fetch the new matched lines - * - * @param {string} handle - * @param {number} offset - * @returns {Promise} - */ - changeOffset: function (handle, offset) { - var line = this.getLine(handle); - line.offset += (offset > 0 ? 1 : -1) * this.limitMoveLines; - return this._performMoveLine(handle, line.mode); - }, - /** - * change the partner on the line and fetch the new matched lines - * - * @param {string} handle - * @param {bool} preserveMode - * @param {Object} partner - * @param {string} partner.display_name - * @param {number} partner.id - * @returns {Promise} - */ - changePartner: function (handle, partner, preserveMode) { - var self = this; - var line = this.getLine(handle); - line.st_line.partner_id = partner && partner.id; - line.st_line.partner_name = partner && partner.display_name || ''; - return Promise.resolve(partner && this._changePartner(handle, partner.id)) - .then(function() { - if(line.st_line.partner_id){ - _.each(line.reconciliation_proposition, function(prop){ - if(prop.partner_id != line.st_line.partner_id){ - line.reconciliation_proposition = []; - return false; - } - }); - } - return self._computeLine(line); - }) - .then(function () { - return self.changeMode(handle, preserveMode ? line.mode : 'default', true); - }) - - }, - /** - * close the statement - * @returns {Promise<number>} resolves to the res_id of the closed statements - */ - closeStatement: function () { - var self = this; - return this._rpc({ - model: 'account.bank.statement.line', - method: 'button_confirm_bank', - args: [self.bank_statement_line_id.id], - }) - .then(function () { - return self.bank_statement_line_id.id; - }); - }, - /** - * - * then open the first available line - * - * @param {string} handle - * @returns {Promise} - */ - createProposition: function (handle) { - var line = this.getLine(handle); - var prop = _.filter(line.reconciliation_proposition, '__focus'); - prop = this._formatQuickCreate(line); - line.reconciliation_proposition.push(prop); - line.createForm = _.pick(prop, this.quickCreateFields); - return this._computeLine(line); - }, - /** - * Return context information and journal_id - * @returns {Object} context - */ - getContext: function () { - return this.context; - }, - /** - * Return the lines that needs to be displayed by the widget - * - * @returns {Object} lines that are loaded and not yet displayed - */ - getStatementLines: function () { - var self = this; - var linesToDisplay = _.pick(this.lines, function(value, key, object) { - if (value.visible === true && self.alreadyDisplayed.indexOf(key) === -1) { - self.alreadyDisplayed.push(key); - return object; - } - }); - return linesToDisplay; - }, - /** - * Return a boolean telling if load button needs to be displayed or not - * overridden in ManualModel - * - * @returns {boolean} true if load more button needs to be displayed - */ - hasMoreLines: function () { - var notDisplayed = _.filter(this.lines, function(line) { return !line.visible; }); - if (notDisplayed.length > 0) { - return true; - } - return false; - }, - /** - * get the line data for this handle - * - * @param {Object} handle - * @returns {Object} - */ - getLine: function (handle) { - return this.lines[handle]; - }, - /** - * load data from - * - * - 'account.bank.statement' fetch the line id and bank_statement_id info - * - 'account.reconcile.model' fetch all reconcile model (for quick add) - * - 'account.account' fetch all account code - * - 'account.reconciliation.widget' fetch each line data - * - * overridden in ManualModel - * @param {Object} context - * @param {number[]} context.statement_line_ids - * @returns {Promise} - */ - load: function (context) { - var self = this; - this.context = context; - this.statement_line_ids = context.statement_line_ids; - if (this.statement_line_ids === undefined) { - // This could be undefined if the user pressed F5, take everything as fallback instead of rainbowman - return self._rpc({ - model: 'account.bank.statement.line', - method: 'search_read', - fields: ['id'], - domain: [['journal_id', '=?', context.active_id]], - }).then(function (result) { - self.statement_line_ids = result.map(r => r.id); - return self.reload() - }) - } else { - return self.reload(); - } - - }, - /** - * Load more bank statement line - * - * @param {integer} qty quantity to load - * @returns {Promise} - */ - loadMore: function(qty) { - if (qty === undefined) { - qty = this.defaultDisplayQty; - } - var ids = _.pluck(this.lines, 'id'); - ids = ids.splice(this.pagerIndex, qty); - this.pagerIndex += qty; - return this.loadData(ids, this._getExcludedIds()); - }, - /** - * RPC method to load informations on lines - * overridden in ManualModel - * - * @param {Array} ids ids of bank statement line passed to rpc call - * @param {Array} excluded_ids list of move_line ids that needs to be excluded from search - * @returns {Promise} - */ - loadData: function(ids, excluded_ids) { - var self = this; - return self._rpc({ - model: 'account.reconciliation.widget', - method: 'get_bank_statement_line_data', - args: [ids, excluded_ids], - context: self.context, - }) - .then(self._formatLine.bind(self)); - }, - /** - * Reload all data - */ - reload: function() { - var self = this; - self.alreadyDisplayed = []; - self.lines = {}; - self.pagerIndex = 0; - var def_statement = this._rpc({ - model: 'account.reconciliation.widget', - method: 'get_bank_statement_data', - kwargs: {"bank_statement_line_ids":self.statement_line_ids, "srch_domain":self.domain}, - context: self.context, - }) - .then(function (statement) { - self.statement = statement; - self.bank_statement_line_id = self.statement_line_ids.length === 1 ? {id: self.statement_line_ids[0], display_name: statement.statement_name} : false; - self.valuenow = self.valuenow || statement.value_min; - self.valuemax = self.valuemax || statement.value_max; - self.context.journal_id = statement.journal_id; - _.each(statement.lines, function (res) { - var handle = _.uniqueId('rline'); - self.lines[handle] = { - id: res.st_line.id, - partner_id: res.st_line.partner_id, - handle: handle, - reconciled: false, - mode: 'inactive', - mv_lines_match_rp: [], - mv_lines_match_other: [], - offset: 0, - filter_match_rp: "", - filter_match_other: "", - reconciliation_proposition: [], - reconcileModels: [], - }; - }); - }); - var domainReconcile = []; - if (self.context && self.context.company_ids) { - domainReconcile.push(['company_id', 'in', self.context.company_ids]); - } - if (self.context && self.context.active_model === 'account.journal' && self.context.active_ids) { - domainReconcile.push(['match_journal_ids', 'in', [false].concat(self.context.active_ids)]); - } - var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile}); - var def_account = this._rpc({ - model: 'account.account', - method: 'search_read', - fields: ['code'], - }) - .then(function (accounts) { - self.accounts = _.object(_.pluck(accounts, 'id'), _.pluck(accounts, 'code')); - }); - var def_taxes = self._loadTaxes(); - return Promise.all([def_statement, def_reconcileModel, def_account, def_taxes]).then(function () { - _.each(self.lines, function (line) { - line.reconcileModels = self.reconcileModels; - }); - var ids = _.pluck(self.lines, 'id'); - ids = ids.splice(0, self.defaultDisplayQty); - self.pagerIndex = ids.length; - return self._formatLine(self.statement.lines); - }); - }, - _readAnalyticTags: function (params) { - var self = this; - this.analyticTags = {}; - if (!params || !params.res_ids || !params.res_ids.length) { - return $.when(); - } - var fields = (params && params.fields || []).concat(['id', 'display_name']); - return this._rpc({ - model: 'account.analytic.tag', - method: 'read', - args: [ - params.res_ids, - fields, - ], - }).then(function (tags) { - for (var i=0; i<tags.length; i++) { - var tag = tags[i]; - self.analyticTags[tag.id] = tag; - } - }); - }, - _loadReconciliationModel: function (params) { - var self = this; - return this._rpc({ - model: 'account.reconcile.model', - method: 'search_read', - domain: params.domainReconcile || [], - }) - .then(function (reconcileModels) { - var analyticTagIds = []; - for (var i=0; i<reconcileModels.length; i++) { - var modelTags = reconcileModels[i].analytic_tag_ids || []; - for (var j=0; j<modelTags.length; j++) { - if (analyticTagIds.indexOf(modelTags[j]) === -1) { - analyticTagIds.push(modelTags[j]); - } - } - } - return self._readAnalyticTags({res_ids: analyticTagIds}).then(function () { - for (var i=0; i<reconcileModels.length; i++) { - var recModel = reconcileModels[i]; - var analyticTagData = []; - var modelTags = reconcileModels[i].analytic_tag_ids || []; - for (var j=0; j<modelTags.length; j++) { - var tagId = modelTags[j]; - analyticTagData.push([tagId, self.analyticTags[tagId].display_name]) - } - recModel.analytic_tag_ids = analyticTagData; - } - self.reconcileModels = reconcileModels; - }); - }); - }, - _loadTaxes: function(){ - var self = this; - self.taxes = {}; - return this._rpc({ - model: 'account.tax', - method: 'search_read', - fields: ['price_include', 'name'], - }).then(function (taxes) { - _.each(taxes, function(tax){ - self.taxes[tax.id] = { - price_include: tax.price_include, - display_name: tax.name, - }; - }); - return taxes; - }); - }, - /** - * Add lines into the propositions from the reconcile model - * Can add 2 lines, and each with its taxes. The second line become editable - * in the create mode. - * - * @see 'updateProposition' method for more informations about the - * 'amount_type' - * - * @param {string} handle - * @param {integer} reconcileModelId - * @returns {Promise} - */ - quickCreateProposition: function (handle, reconcileModelId) { - var self = this; - var line = this.getLine(handle); - var reconcileModel = _.find(this.reconcileModels, function (r) {return r.id === reconcileModelId;}); - var fields = ['account_id', 'amount', 'amount_type', 'analytic_account_id', 'journal_id', 'label', 'force_tax_included', 'tax_ids', 'analytic_tag_ids', 'to_check', 'amount_from_label_regex', 'decimal_separator']; - this._blurProposition(handle); - var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields)); - focus.reconcileModelId = reconcileModelId; - line.reconciliation_proposition.push(focus); - var defs = []; - if (reconcileModel.has_second_line) { - defs.push(self._computeLine(line).then(function() { - var second = {}; - _.each(fields, function (key) { - second[key] = ("second_"+key) in reconcileModel ? reconcileModel["second_"+key] : reconcileModel[key]; - }); - var second_focus = self._formatQuickCreate(line, second); - second_focus.reconcileModelId = reconcileModelId; - line.reconciliation_proposition.push(second_focus); - self._computeReconcileModels(handle, reconcileModelId); - })) - } - return Promise.all(defs).then(function() { - line.createForm = _.pick(focus, self.quickCreateFields); - return self._computeLine(line); - }) - }, - /** - * Remove a proposition and switch to an active mode ('create' or 'match_rp' or 'match_other') - * overridden in ManualModel - * - * @param {string} handle - * @param {number} id (move line id) - * @returns {Promise} - */ - removeProposition: function (handle, id) { - var self = this; - var line = this.getLine(handle); - var defs = []; - // new limit = previous limit + 1, the one put back - line.limit_override = (line.offset + 1) + this.limitMoveLines; - var prop = _.find(line.reconciliation_proposition, {'id' : id}); - if (prop) { - line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) { - return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link); - }); - if (prop['reconcileModelId'] === undefined) { - if (['receivable', 'payable', 'liquidity'].includes(prop.account_type)) { - line.mv_lines_match_rp.unshift(prop); - } else { - line.mv_lines_match_other.unshift(prop); - } - } - - // No proposition left and then, reset the st_line partner. - if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner) - defs.push(self.changePartner(line.handle)); - } - line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match_rp'; - defs.push(this._computeLine(line)); - return Promise.all(defs).then(function() { - return self.changeMode(handle, line.mode, true); - }) - }, - getPartialReconcileAmount: function(handle, data) { - var line = this.getLine(handle); - var prop = _.find(line.reconciliation_proposition, {'id': data.data}); - if (prop) { - var amount = prop.partial_amount || prop.amount; - // Check if we can get a partial amount that would directly set balance to zero - var partial = Math.abs(line.balance.amount + amount); - if (Math.abs(line.balance.amount) >= Math.abs(amount)) { - return Math.abs(amount); - } - if (partial <= Math.abs(prop.amount) && partial >= 0) { - return partial; - } - return Math.abs(amount); - } - }, - /** - * Force the partial reconciliation to display the reconciliate button. - * - * @param {string} handle - * @returns {Promise} - */ - partialReconcile: function(handle, data) { - var line = this.getLine(handle); - var prop = _.find(line.reconciliation_proposition, {'id' : data.mvLineId}); - if (prop) { - var amount = data.amount; - try { - amount = field_utils.parse.float(data.amount); - } - catch (err) { - amount = NaN; - } - // Amount can't be greater than line.amount and can not be negative and must be a number - // the amount we receive will be a string, so take sign of previous line amount in consideration in order to put - // the amount in the correct left or right column - if (amount >= Math.abs(prop.amount) || amount <= 0 || isNaN(amount)) { - delete prop.partial_amount_str; - delete prop.partial_amount; - if (isNaN(amount) || amount < 0) { - this.do_warn(_.str.sprintf(_t('The amount %s is not a valid partial amount'), data.amount)); - } - return this._computeLine(line); - } - else { - var format_options = { currency_id: line.st_line.currency_id }; - prop.partial_amount = (prop.amount > 0 ? 1 : -1)*amount; - prop.partial_amount_str = field_utils.format.monetary(Math.abs(prop.partial_amount), {}, format_options); - } - } - return this._computeLine(line); - }, - /** - * Change the value of the editable proposition line or create a new one. - * - * If the editable line comes from a reconcile model with 2 lines - * and their 'amount_type' is "percent" - * and their total equals 100% (this doesn't take into account the taxes - * who can be included or not) - * Then the total is recomputed to have 100%. - * - * @param {string} handle - * @param {*} values - * @returns {Promise} - */ - updateProposition: function (handle, values) { - var self = this; - var line = this.getLine(handle); - var prop = _.last(_.filter(line.reconciliation_proposition, '__focus')); - if ('to_check' in values && values.to_check === false) { - // check if we have another line with to_check and if yes don't change value of this proposition - prop.to_check = line.reconciliation_proposition.some(function(rec_prop, index) { - return rec_prop.id !== prop.id && rec_prop.to_check; - }); - } - if (!prop) { - prop = this._formatQuickCreate(line); - line.reconciliation_proposition.push(prop); - } - _.each(values, function (value, fieldName) { - if (fieldName === 'analytic_tag_ids') { - switch (value.operation) { - case "ADD_M2M": - if (!_.findWhere(prop.analytic_tag_ids, {id: value.ids.id})) { - prop.analytic_tag_ids.push(value.ids); - } - break; - case "FORGET": - var id = self.localData[value.ids[0]].ref; - prop.analytic_tag_ids = _.filter(prop.analytic_tag_ids, function (val) { - return val.id !== id; - }); - break; - } - } - else if (fieldName === 'tax_ids') { - switch(value.operation) { - case "ADD_M2M": - prop.__tax_to_recompute = true; - if (!_.findWhere(prop.tax_ids, {id: value.ids.id})) { - value.ids.price_include = self.taxes[value.ids.id] ? self.taxes[value.ids.id].price_include : false; - prop.tax_ids.push(value.ids); - } - break; - case "FORGET": - prop.__tax_to_recompute = true; - var id = self.localData[value.ids[0]].ref; - prop.tax_ids = _.filter(prop.tax_ids, function (val) { - return val.id !== id; - }); - break; - } - } - else { - prop[fieldName] = values[fieldName]; - } - }); - if ('account_id' in values) { - prop.account_code = prop.account_id ? this.accounts[prop.account_id.id] : ''; - } - if ('amount' in values) { - prop.base_amount = values.amount; - if (prop.reconcileModelId) { - this._computeReconcileModels(handle, prop.reconcileModelId); - } - } - if ('force_tax_included' in values || 'amount' in values || 'account_id' in values) { - prop.__tax_to_recompute = true; - } - line.createForm = _.pick(prop, this.quickCreateFields); - // If you check/uncheck the force_tax_included box, reset the createForm amount. - if(prop.base_amount) - line.createForm.amount = prop.base_amount; - if (prop.tax_ids.length !== 1 ) { - // When we have 0 or more than 1 taxes, reset the base_amount and force_tax_included, otherwise weird behavior can happen - prop.amount = prop.base_amount; - line.createForm.force_tax_included = false; - } - return this._computeLine(line); - }, - /** - * Format the value and send it to 'account.reconciliation.widget' model - * Update the number of validated lines - * overridden in ManualModel - * - * @param {(string|string[])} handle - * @returns {Promise<Object>} resolved with an object who contains - * 'handles' key - */ - validate: function (handle) { - var self = this; - this.display_context = 'validate'; - var handles = []; - if (handle) { - handles = [handle]; - } else { - _.each(this.lines, function (line, handle) { - if (!line.reconciled && line.balance && !line.balance.amount && line.reconciliation_proposition.length) { - handles.push(handle); - } - }); - } - var ids = []; - var values = []; - var handlesPromises = []; - _.each(handles, function (handle) { - var line = self.getLine(handle); - var props = _.filter(line.reconciliation_proposition, function (prop) {return !prop.invalid;}); - var computeLinePromise; - if (props.length === 0) { - // Usability: if user has not chosen any lines and click validate, it has the same behavior - // as creating a write-off of the same amount. - props.push(self._formatQuickCreate(line, { - account_id: [line.st_line.open_balance_account_id, self.accounts[line.st_line.open_balance_account_id]], - })); - // update balance of line otherwise it won't be to zero and another line will be added - line.reconciliation_proposition.push(props[0]); - computeLinePromise = self._computeLine(line); - } - ids.push(line.id); - handlesPromises.push(Promise.resolve(computeLinePromise).then(function() { - var values_dict = { - "partner_id": line.st_line.partner_id, - "counterpart_aml_dicts": _.map(_.filter(props, function (prop) { - return !isNaN(prop.id) && !prop.already_paid; - }), self._formatToProcessReconciliation.bind(self, line)), - "payment_aml_ids": _.pluck(_.filter(props, function (prop) { - return !isNaN(prop.id) && prop.already_paid; - }), 'id'), - "new_aml_dicts": _.map(_.filter(props, function (prop) { - return isNaN(prop.id) && prop.display; - }), self._formatToProcessReconciliation.bind(self, line)), - "to_check": line.to_check, - }; - - // If the lines are not fully balanced, create an unreconciled amount. - // line.st_line.currency_id is never false here because its equivalent to - // statement_line.currency_id or statement_line.journal_id.currency_id or statement_line.journal_id.company_id.currency_id (Python-side). - // see: get_statement_line_for_reconciliation_widget method in account/models/account_bank_statement.py for more details - var currency = session.get_currency(line.st_line.currency_id); - var balance = line.balance.amount; - if (!utils.float_is_zero(balance, currency.digits[1])) { - var unreconciled_amount_dict = { - 'account_id': line.st_line.open_balance_account_id, - 'credit': balance > 0 ? balance : 0, - 'debit': balance < 0 ? -balance : 0, - 'name': line.st_line.name + ' : ' + _t("Open balance"), - }; - values_dict['new_aml_dicts'].push(unreconciled_amount_dict); - } - values.push(values_dict); - line.reconciled = true; - self.valuenow++; - })); - - _.each(self.lines, function(other_line) { - if (other_line != line) { - var filtered_prop = other_line.reconciliation_proposition.filter(p => !line.reconciliation_proposition.map(l => l.id).includes(p.id)); - if (filtered_prop.length != other_line.reconciliation_proposition.length) { - other_line.need_update = true; - other_line.reconciliation_proposition = filtered_prop; - } - self._computeLine(line); - } - }) - }); - - return Promise.all(handlesPromises).then(function() { - return self._rpc({ - model: 'account.reconciliation.widget', - method: 'process_bank_statement_line', - args: [ids, values], - context: self.context, - }) - .then(self._validatePostProcess.bind(self)) - .then(function () { - return {handles: handles}; - }); - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * add a line proposition after checking receivable and payable accounts constraint - * - * @private - * @param {Object} line - * @param {Object} prop - */ - _addProposition: function (line, prop) { - line.reconciliation_proposition.push(prop); - }, - /** - * stop the editable proposition line and remove it if it's invalid then - * compute the line - * - * See :func:`_computeLine` - * - * @private - * @param {string} handle - * @returns {Promise} - */ - _blurProposition: function (handle) { - var line = this.getLine(handle); - line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (l) { - l.__focus = false; - return !l.invalid; - }); - }, - /** - * When changing partner, read property_account_receivable and payable - * of that partner because the counterpart account might cahnge depending - * on the partner - * - * @private - * @param {string} handle - * @param {integer} partner_id - * @returns {Promise} - */ - _changePartner: function (handle, partner_id) { - var self = this; - return this._rpc({ - model: 'res.partner', - method: 'read', - args: [partner_id, ["property_account_receivable_id", "property_account_payable_id"]], - }).then(function (result) { - if (result.length > 0) { - var line = self.getLine(handle); - self.lines[handle].st_line.open_balance_account_id = line.balance.amount < 0 ? result[0]['property_account_payable_id'][0] : result[0]['property_account_receivable_id'][0]; - } - }); - }, - /** - * Calculates the balance; format each proposition amount_str and mark as - * invalid the line with empty account_id, amount or label - * Check the taxes server side for each updated propositions with tax_ids - * extended by ManualModel - * - * @private - * @param {Object} line - * @returns {Promise} - */ - _computeLine: function (line) { - //balance_type - var self = this; - - // compute taxes - var tax_defs = []; - var reconciliation_proposition = []; - var formatOptions = { - currency_id: line.st_line.currency_id, - }; - line.to_check = false; - _.each(line.reconciliation_proposition, function (prop) { - if (prop.to_check) { - // If one of the proposition is to_check, set the global to_check flag to true - line.to_check = true; - } - if (prop.is_tax) { - if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) { - reconciliation_proposition.push(prop); - } - return; - } - if (!prop.already_paid && parseInt(prop.id)) { - prop.is_move_line = true; - } - reconciliation_proposition.push(prop); - - if (prop.tax_ids && prop.tax_ids.length && prop.__tax_to_recompute && prop.base_amount) { - reconciliation_proposition = _.filter(reconciliation_proposition, function (p) { - return !p.is_tax || p.link !== prop.id; - }); - var args = [prop.tax_ids.map(function(el){return el.id;}), prop.base_amount, formatOptions.currency_id]; - var add_context = {'round': true}; - if(prop.tax_ids.length === 1 && line.createForm.force_tax_included) - add_context.force_price_include = true; - tax_defs.push(self._rpc({ - model: 'account.tax', - method: 'json_friendly_compute_all', - args: args, - context: $.extend({}, self.context || {}, add_context), - }) - .then(function (result) { - _.each(result.taxes, function(tax){ - var tax_prop = self._formatQuickCreate(line, { - 'link': prop.id, - 'tax_ids': [tax.id], - 'amount': tax.amount, - 'label': prop.label ? prop.label + " " + tax.name : tax.name, - 'date': prop.date, - 'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id, - 'analytic': tax.analytic, - 'is_tax': true, - '__focus': false - }); - - prop.tax_exigible = tax.tax_exigibility === 'on_payment' ? true : undefined; - prop.amount = tax.base; - prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions); - prop.invalid = !self._isValid(prop); - - tax_prop.amount_str = field_utils.format.monetary(Math.abs(tax_prop.amount), {}, formatOptions); - tax_prop.invalid = prop.invalid; - - reconciliation_proposition.push(tax_prop); - }); - })); - } else { - prop.amount_str = field_utils.format.monetary(Math.abs(prop.amount), {}, formatOptions); - prop.display = self._isDisplayedProposition(prop); - prop.invalid = !self._isValid(prop); - } - }); - - return Promise.all(tax_defs).then(function () { - _.each(reconciliation_proposition, function (prop) { - prop.__tax_to_recompute = false; - }); - line.reconciliation_proposition = reconciliation_proposition; - - var amount_currency = 0; - var total = line.st_line.amount || 0; - var isOtherCurrencyId = _.uniq(_.pluck(_.reject(reconciliation_proposition, 'invalid'), 'currency_id')); - isOtherCurrencyId = isOtherCurrencyId.length === 1 && !total && isOtherCurrencyId[0] !== formatOptions.currency_id ? isOtherCurrencyId[0] : false; - - _.each(reconciliation_proposition, function (prop) { - if (!prop.invalid) { - total -= prop.partial_amount || prop.amount; - if (isOtherCurrencyId) { - amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency); - } - } - }); - var company_currency = session.get_currency(line.st_line.currency_id); - var company_precision = company_currency && company_currency.digits[1] || 2; - total = utils.round_decimals(total, company_precision) || 0; - if(isOtherCurrencyId){ - var other_currency = session.get_currency(isOtherCurrencyId); - var other_precision = other_currency && other_currency.digits[1] || 2; - amount_currency = utils.round_decimals(amount_currency, other_precision); - } - line.balance = { - amount: total, - amount_str: field_utils.format.monetary(Math.abs(total), {}, formatOptions), - currency_id: isOtherCurrencyId, - amount_currency: isOtherCurrencyId ? amount_currency : total, - amount_currency_str: isOtherCurrencyId ? field_utils.format.monetary(Math.abs(amount_currency), {}, { - currency_id: isOtherCurrencyId - }) : false, - account_code: self.accounts[line.st_line.open_balance_account_id], - }; - line.balance.show_balance = line.balance.amount_currency != 0; - line.balance.type = line.balance.amount_currency ? (line.st_line.partner_id ? 0 : -1) : 1; - }); - }, - /** - * - * - * @private - * @param {string} handle - * @param {integer} reconcileModelId - */ - _computeReconcileModels: function (handle, reconcileModelId) { - var line = this.getLine(handle); - // if quick create with 2 lines who use 100%, change the both values in same time - var props = _.filter(line.reconciliation_proposition, {'reconcileModelId': reconcileModelId, '__focus': true}); - if (props.length === 2 && props[0].percent && props[1].percent) { - if (props[0].percent + props[1].percent === 100) { - props[0].base_amount = props[0].amount = line.st_line.amount - props[1].base_amount; - props[0].__tax_to_recompute = true; - } - } - }, - /** - * format a name_get into an object {id, display_name}, idempotent - * - * @private - * @param {Object|Array} [value] data or name_get - */ - _formatNameGet: function (value) { - return value ? (value.id ? value : {'id': value[0], 'display_name': value[1]}) : false; - }, - _formatMany2ManyTags: function (value) { - var res = []; - for (var i=0, len=value.length; i<len; i++) { - res[i] = {'id': value[i][0], 'display_name': value[i][1]}; - } - return res; - }, - _formatMany2ManyTagsTax: function(value) { - var res = []; - for (var i=0; i<value.length; i++) { - res.push({id: value[i], display_name: this.taxes[value[i]] ? this.taxes[value[i]].display_name : ''}); - } - return res; - }, - /** - * Format each propositions (amount, label, account_id) - * extended in ManualModel - * - * @private - * @param {Object} line - * @param {Object[]} props - */ - _formatLineProposition: function (line, props) { - var self = this; - if (props.length) { - _.each(props, function (prop) { - prop.amount = prop.debit || -prop.credit; - prop.label = prop.name; - prop.account_id = self._formatNameGet(prop.account_id || line.account_id); - prop.is_partially_reconciled = prop.amount_str !== prop.total_amount_str; - prop.to_check = !!prop.to_check; - }); - } - }, - /** - * Format each server lines and propositions and compute all lines - * overridden in ManualModel - * - * @see '_computeLine' - * - * @private - * @param {Object[]} lines - * @returns {Promise} - */ - _formatLine: function (lines) { - var self = this; - var defs = []; - _.each(lines, function (data) { - var line = _.find(self.lines, function (l) { - return l.id === data.st_line.id; - }); - line.visible = true; - line.limitMoveLines = self.limitMoveLines; - _.extend(line, data); - self._formatLineProposition(line, line.reconciliation_proposition); - if (!line.reconciliation_proposition.length) { - delete line.reconciliation_proposition; - } - - // No partner set on st_line and all matching amls have the same one: set it on the st_line. - defs.push( - self._computeLine(line) - .then(function(){ - if(!line.st_line.partner_id && line.reconciliation_proposition.length > 0){ - var hasDifferentPartners = function(prop){ - return !prop.partner_id || prop.partner_id != line.reconciliation_proposition[0].partner_id; - }; - - if(!_.any(line.reconciliation_proposition, hasDifferentPartners)){ - return self.changePartner(line.handle, { - 'id': line.reconciliation_proposition[0].partner_id, - 'display_name': line.reconciliation_proposition[0].partner_name, - }, true); - } - }else if(!line.st_line.partner_id && line.partner_id && line.partner_name){ - return self.changePartner(line.handle, { - 'id': line.partner_id, - 'display_name': line.partner_name, - }, true); - } - return true; - }) - .then(function(){ - return data.write_off ? self.quickCreateProposition(line.handle, data.model_id) : true; - }) - .then(function() { - // If still no partner set, take the one from context, if it exists - if (!line.st_line.partner_id && self.context.partner_id && self.context.partner_name) { - return self.changePartner(line.handle, { - 'id': self.context.partner_id, - 'display_name': self.context.partner_name, - }, true); - } - return true; - }) - ); - }); - return Promise.all(defs); - }, - /** - * Format the server value then compute the line - * overridden in ManualModel - * - * @see '_computeLine' - * - * @private - * @param {string} handle - * @param {Object[]} mv_lines - * @returns {Promise} - */ - _formatMoveLine: function (handle, mode, mv_lines) { - var self = this; - var line = this.getLine(handle); - if (line.offset === 0 || line.limit_override) { - line['mv_lines_'+mode] = mv_lines; - delete line.limit_override; - } else { - line['mv_lines_'+mode] = line['mv_lines_'+mode].concat(mv_lines); - } - this._formatLineProposition(line, mv_lines); - - if ((line.mode == 'match_other' || line.mode == "match_rp") && !line['mv_lines_'+mode].length && !line['filter_'+mode].length) { - line.mode = self._getDefaultMode(handle); - if (line.mode !== 'match_rp' && line.mode !== 'match_other' && line.mode !== 'inactive') { - return this._computeLine(line).then(function () { - return self.createProposition(handle); - }); - } - } else { - return this._computeLine(line); - } - }, - /** - * overridden in ManualModel - */ - _getDefaultMode: function(handle) { - var line = this.getLine(handle); - if (line.balance.amount === 0 - && (!line.st_line.mv_lines_match_rp || line.st_line.mv_lines_match_rp.length === 0) - && (!line.st_line.mv_lines_match_other || line.st_line.mv_lines_match_other.length === 0)) { - return 'inactive'; - } - if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) { - return 'match_rp'; - } - if (line.mv_lines_match_other && line.mv_lines_match_other.length) { - return 'match_other'; - } - return 'create'; - }, - _getAvailableModes: function(handle) { - var line = this.getLine(handle); - var modes = [] - if (line.mv_lines_match_rp && line.mv_lines_match_rp.length) { - modes.push('match_rp') - } - if (line.mv_lines_match_other && line.mv_lines_match_other.length) { - modes.push('match_other') - } - modes.push('create') - return modes - }, - /** - * Apply default values for the proposition, format datas and format the - * base_amount with the decimal number from the currency - * extended in ManualModel - * - * @private - * @param {Object} line - * @param {Object} values - * @returns {Object} - */ - _formatQuickCreate: function (line, values) { - values = values || {}; - var today = new moment().utc().format(); - var account = this._formatNameGet(values.account_id); - var formatOptions = { - currency_id: line.st_line.currency_id, - }; - var amount; - switch(values.amount_type) { - case 'percentage': - amount = line.balance.amount * values.amount / 100; - break; - case 'regex': - var matching = line.st_line.name.match(new RegExp(values.amount_from_label_regex)) - amount = 0; - if (matching && matching.length == 2) { - matching = matching[1].replace(new RegExp('\\D' + values.decimal_separator, 'g'), ''); - matching = matching.replace(values.decimal_separator, '.'); - amount = parseFloat(matching) || 0; - amount = line.balance.amount > 0 ? amount : -amount; - } - break; - case 'fixed': - amount = values.amount; - break; - default: - amount = values.amount !== undefined ? values.amount : line.balance.amount; - } - - - var prop = { - 'id': _.uniqueId('createLine'), - 'label': values.label || line.st_line.name, - 'account_id': account, - 'account_code': account ? this.accounts[account.id] : '', - 'analytic_account_id': this._formatNameGet(values.analytic_account_id), - 'analytic_tag_ids': this._formatMany2ManyTags(values.analytic_tag_ids || []), - 'journal_id': this._formatNameGet(values.journal_id), - 'tax_ids': this._formatMany2ManyTagsTax(values.tax_ids || []), - 'debit': 0, - 'credit': 0, - 'date': values.date ? values.date : field_utils.parse.date(today, {}, {isUTC: true}), - 'force_tax_included': values.force_tax_included || false, - 'base_amount': amount, - 'percent': values.amount_type === "percentage" ? values.amount : null, - 'link': values.link, - 'display': true, - 'invalid': true, - 'to_check': !!values.to_check, - '__tax_to_recompute': true, - 'is_tax': values.is_tax, - '__focus': '__focus' in values ? values.__focus : true, - }; - if (prop.base_amount) { - // Call to format and parse needed to round the value to the currency precision - var sign = prop.base_amount < 0 ? -1 : 1; - var amount = field_utils.format.monetary(Math.abs(prop.base_amount), {}, formatOptions); - prop.base_amount = sign * field_utils.parse.monetary(amount, {}, formatOptions); - } - - prop.amount = prop.base_amount; - return prop; - }, - /** - * Return list of account_move_line that has been selected and needs to be removed - * from other calls. - * - * @private - * @returns {Array} list of excluded ids - */ - _getExcludedIds: function () { - var excludedIds = []; - _.each(this.lines, function(line) { - if (line.reconciliation_proposition) { - _.each(line.reconciliation_proposition, function(prop) { - if (parseInt(prop['id'])) { - excludedIds.push(prop['id']); - } - }); - } - }); - return excludedIds; - }, - /** - * Defined whether the line is to be displayed or not. Here, we only display - * the line if it comes from the server or if an account is defined when it - * is created - * extended in ManualModel - * - * @private - * @param {object} prop - * @returns {Boolean} - */ - _isDisplayedProposition: function (prop) { - return !isNaN(prop.id) || !!prop.account_id; - }, - /** - * extended in ManualModel - * @private - * @param {object} prop - * @returns {Boolean} - */ - _isValid: function (prop) { - return !isNaN(prop.id) || prop.account_id && prop.amount && prop.label && !!prop.label.length; - }, - /** - * Fetch 'account.reconciliation.widget' propositions. - * overridden in ManualModel - * - * @see '_formatMoveLine' - * - * @private - * @param {string} handle - * @returns {Promise} - */ - _performMoveLine: function (handle, mode) { - var line = this.getLine(handle); - var excluded_ids = _.map(line.reconciliation_proposition, function (prop) { - return _.isNumber(prop.id) ? prop.id : null; - }).filter(id => id != null); - var filter = line['filter_'+mode] || ""; - var limit = this.limitMoveLines; - var offset = line.offset; - if (line.limit_override) { - // If we have a limit_override, it means we are either adding/removing - // a line from the matching table, hence keep same number of displayed - // proposition below by setting offset to 0 and limit to the current - // number of proposition loaded - offset = 0; - limit = line.limit_override; - } - return this._rpc({ - model: 'account.reconciliation.widget', - method: 'get_move_lines_for_bank_statement_line', - args: [line.id, line.st_line.partner_id, excluded_ids, filter, offset, limit, mode === 'match_rp' ? 'rp' : 'other'], - context: this.context, - }) - .then(this._formatMoveLine.bind(this, handle, mode)); - }, - /** - * format the proposition to send information server side - * extended in ManualModel - * - * @private - * @param {object} line - * @param {object} prop - * @returns {object} - */ - _formatToProcessReconciliation: function (line, prop) { - var amount = -prop.amount; - if (prop.partial_amount) { - amount = -prop.partial_amount; - } - - var result = { - name : prop.label, - debit : amount > 0 ? amount : 0, - credit : amount < 0 ? -amount : 0, - tax_exigible: prop.tax_exigible, - analytic_tag_ids: [[6, null, _.pluck(prop.analytic_tag_ids, 'id')]] - }; - if (!isNaN(prop.id)) { - result.counterpart_aml_id = prop.id; - } else { - result.account_id = prop.account_id.id; - if (prop.journal_id) { - result.journal_id = prop.journal_id.id; - } - } - if (!isNaN(prop.id)) result.counterpart_aml_id = prop.id; - if (prop.analytic_account_id) result.analytic_account_id = prop.analytic_account_id.id; - if (prop.tax_ids && prop.tax_ids.length && !prop.is_tax) result.tax_ids = [[6, null, _.pluck(prop.tax_ids, 'id')]]; - if (prop.tax_ids && prop.tax_ids.length && prop.is_tax) result.tax_line_id = prop.tax_ids[0].id; - if (prop.reconcileModelId) result.reconcile_model_id = prop.reconcileModelId - return result; - }, - /** - * Hook to handle return values of the validate's line process. - * - * @private - * @param {Object} data - * @param {Object[]} data.moves list of processed account.move - * @returns {Deferred} - */ - _validatePostProcess: function (data) { - var self = this; - return Promise.resolve(); - }, -}); - - -/** - * Model use to fetch, format and update 'account.move.line' and 'res.partner' - * datas allowing manual reconciliation - */ -var ManualModel = StatementModel.extend({ - quickCreateFields: ['account_id', 'journal_id', 'amount', 'analytic_account_id', 'label', 'tax_ids', 'force_tax_included', 'analytic_tag_ids', 'date', 'to_check'], - - modes: ['create', 'match'], - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Return a boolean telling if load button needs to be displayed or not - * - * @returns {boolean} true if load more button needs to be displayed - */ - hasMoreLines: function () { - if (this.manualLines.length > this.pagerIndex) { - return true; - } - return false; - }, - /** - * load data from - * - 'account.reconciliation.widget' fetch the lines to reconciliate - * - 'account.account' fetch all account code - * - * @param {Object} context - * @param {string} [context.mode] 'customers', 'suppliers' or 'accounts' - * @param {integer[]} [context.company_ids] - * @param {integer[]} [context.partner_ids] used for 'customers' and - * 'suppliers' mode - * @returns {Promise} - */ - load: function (context) { - var self = this; - this.context = context; - - var domain_account_id = []; - if (context && context.company_ids) { - domain_account_id.push(['company_id', 'in', context.company_ids]); - } - - var def_account = this._rpc({ - model: 'account.account', - method: 'search_read', - domain: domain_account_id, - fields: ['code'], - }) - .then(function (accounts) { - self.account_ids = _.pluck(accounts, 'id'); - self.accounts = _.object(self.account_ids, _.pluck(accounts, 'code')); - }); - - var domainReconcile = []; - var company_ids = context && context.company_ids || [session.company_id]; - if (company_ids) { - domainReconcile.push(['company_id', 'in', company_ids]); - } - var def_reconcileModel = this._loadReconciliationModel({domainReconcile: domainReconcile}); - var def_taxes = this._loadTaxes(); - - return Promise.all([def_reconcileModel, def_account, def_taxes]).then(function () { - switch(context.mode) { - case 'customers': - case 'suppliers': - var mode = context.mode === 'customers' ? 'receivable' : 'payable'; - var args = ['partner', context.partner_ids || null, mode]; - return self._rpc({ - model: 'account.reconciliation.widget', - method: 'get_data_for_manual_reconciliation', - args: args, - context: context, - }) - .then(function (result) { - self.manualLines = result; - self.valuenow = 0; - self.valuemax = Object.keys(self.manualLines).length; - var lines = self.manualLines.splice(0, self.defaultDisplayQty); - self.pagerIndex = lines.length; - return self.loadData(lines); - }); - case 'accounts': - return self._rpc({ - model: 'account.reconciliation.widget', - method: 'get_data_for_manual_reconciliation', - args: ['account', context.account_ids || self.account_ids], - context: context, - }) - .then(function (result) { - self.manualLines = result; - self.valuenow = 0; - self.valuemax = Object.keys(self.manualLines).length; - var lines = self.manualLines.splice(0, self.defaultDisplayQty); - self.pagerIndex = lines.length; - return self.loadData(lines); - }); - default: - var partner_ids = context.partner_ids || null; - var account_ids = context.account_ids || self.account_ids || null; - return self._rpc({ - model: 'account.reconciliation.widget', - method: 'get_all_data_for_manual_reconciliation', - args: [partner_ids, account_ids], - context: context, - }) - .then(function (result) { - // Flatten the result - self.manualLines = [].concat(result.accounts, result.customers, result.suppliers); - self.valuenow = 0; - self.valuemax = Object.keys(self.manualLines).length; - var lines = self.manualLines.splice(0, self.defaultDisplayQty); - self.pagerIndex = lines.length; - return self.loadData(lines); - }); - } - }); - }, - /** - * Load more partners/accounts - * overridden in ManualModel - * - * @param {integer} qty quantity to load - * @returns {Promise} - */ - loadMore: function(qty) { - if (qty === undefined) { - qty = this.defaultDisplayQty; - } - var lines = this.manualLines.splice(this.pagerIndex, qty); - this.pagerIndex += qty; - return this.loadData(lines); - }, - /** - * Method to load informations on lines - * - * @param {Array} lines manualLines to load - * @returns {Promise} - */ - loadData: function(lines) { - var self = this; - var defs = []; - _.each(lines, function (l) { - defs.push(self._formatLine(l.mode, l)); - }); - return Promise.all(defs); - - }, - /** - * Mark the account or the partner as reconciled - * - * @param {(string|string[])} handle - * @returns {Promise<Array>} resolved with the handle array - */ - validate: function (handle) { - var self = this; - var handles = []; - if (handle) { - handles = [handle]; - } else { - _.each(this.lines, function (line, handle) { - if (!line.reconciled && !line.balance.amount && line.reconciliation_proposition.length) { - handles.push(handle); - } - }); - } - - var def = Promise.resolve(); - var process_reconciliations = []; - var reconciled = []; - _.each(handles, function (handle) { - var line = self.getLine(handle); - if(line.reconciled) { - return; - } - var props = line.reconciliation_proposition; - if (!props.length) { - self.valuenow++; - reconciled.push(handle); - line.reconciled = true; - process_reconciliations.push({ - id: line.type === 'accounts' ? line.account_id : line.partner_id, - type: line.type, - mv_line_ids: [], - new_mv_line_dicts: [], - }); - } else { - var mv_line_ids = _.pluck(_.filter(props, function (prop) {return !isNaN(prop.id);}), 'id'); - var new_mv_line_dicts = _.map(_.filter(props, function (prop) {return isNaN(prop.id) && prop.display;}), self._formatToProcessReconciliation.bind(self, line)); - process_reconciliations.push({ - id: null, - type: null, - mv_line_ids: mv_line_ids, - new_mv_line_dicts: new_mv_line_dicts - }); - } - line.reconciliation_proposition = []; - }); - if (process_reconciliations.length) { - def = self._rpc({ - model: 'account.reconciliation.widget', - method: 'process_move_lines', - args: [process_reconciliations], - }); - } - - return def.then(function() { - var defs = []; - var account_ids = []; - var partner_ids = []; - _.each(handles, function (handle) { - var line = self.getLine(handle); - if (line.reconciled) { - return; - } - line.filter = ""; - line.offset = 0; - defs.push(self._performMoveLine(handle, 'match').then(function () { - if(!line.mv_lines_match.length) { - self.valuenow++; - reconciled.push(handle); - line.reconciled = true; - if (line.type === 'accounts') { - account_ids.push(line.account_id.id); - } else { - partner_ids.push(line.partner_id.id); - } - } - })); - }); - return Promise.all(defs).then(function () { - if (partner_ids.length) { - self._rpc({ - model: 'res.partner', - method: 'mark_as_reconciled', - args: [partner_ids], - }); - } - return {reconciled: reconciled, updated: _.difference(handles, reconciled)}; - }); - }); - }, - removeProposition: function (handle, id) { - var self = this; - var line = this.getLine(handle); - var defs = []; - // new limit = previous limit + 1, the one put back - var prop = _.find(line.reconciliation_proposition, {'id' : id}); - if (prop) { - line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) { - return p.id !== prop.id && p.id !== prop.link && p.link !== prop.id && (!p.link || p.link !== prop.link); - }); - line.mv_lines_match.unshift(prop); - - // No proposition left and then, reset the st_line partner. - if(line.reconciliation_proposition.length == 0 && line.st_line.has_no_partner) - defs.push(self.changePartner(line.handle)); - } - line.mode = (id || line.mode !== "create") && isNaN(id) ? 'create' : 'match'; - defs.push(this._computeLine(line)); - return Promise.all(defs).then(function() { - return self.changeMode(handle, line.mode, true); - }) - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * override change the balance type to display or not the reconcile button - * - * @override - * @private - * @param {Object} line - * @returns {Promise} - */ - _computeLine: function (line) { - return this._super(line).then(function () { - var props = _.reject(line.reconciliation_proposition, 'invalid'); - _.each(line.reconciliation_proposition, function(p) { - delete p.is_move_line; - }); - line.balance.type = -1; - if (!line.balance.amount_currency && props.length) { - line.balance.type = 1; - } else if(_.any(props, function (prop) {return prop.amount > 0;}) && - _.any(props, function (prop) {return prop.amount < 0;})) { - line.balance.type = 0; - } - }); - }, - /** - * Format each server lines and propositions and compute all lines - * - * @see '_computeLine' - * - * @private - * @param {'customers' | 'suppliers' | 'accounts'} type - * @param {Object} data - * @returns {Promise} - */ - _formatLine: function (type, data) { - var line = this.lines[_.uniqueId('rline')] = _.extend(data, { - type: type, - reconciled: false, - mode: 'inactive', - offset: 0, - limitMoveLines: this.limitMoveLines, - filter: "", - reconcileModels: this.reconcileModels, - account_id: this._formatNameGet([data.account_id, data.account_name]), - st_line: data, - visible: true - }); - this._formatLineProposition(line, line.reconciliation_proposition); - if (!line.reconciliation_proposition.length) { - delete line.reconciliation_proposition; - } - return this._computeLine(line); - }, - /** - * override to add journal_id - * - * @override - * @private - * @param {Object} line - * @param {Object} props - */ - _formatLineProposition: function (line, props) { - var self = this; - this._super(line, props); - if (props.length) { - _.each(props, function (prop) { - var tmp_value = prop.debit || prop.credit; - prop.credit = prop.credit !== 0 ? 0 : tmp_value; - prop.debit = prop.debit !== 0 ? 0 : tmp_value; - prop.amount = -prop.amount; - prop.journal_id = self._formatNameGet(prop.journal_id || line.journal_id); - prop.to_check = !!prop.to_check; - }); - } - }, - /** - * override to add journal_id on tax_created_line - * - * @private - * @param {Object} line - * @param {Object} values - * @returns {Object} - */ - _formatQuickCreate: function (line, values) { - // Add journal to created line - if (values && values.journal_id === undefined && line && line.createForm && line.createForm.journal_id) { - values.journal_id = line.createForm.journal_id; - } - return this._super(line, values); - }, - /** - * @override - * @param {object} prop - * @returns {Boolean} - */ - _isDisplayedProposition: function (prop) { - return !!prop.journal_id && this._super(prop); - }, - /** - * @override - * @param {object} prop - * @returns {Boolean} - */ - _isValid: function (prop) { - return prop.journal_id && this._super(prop); - }, - /** - * Fetch 'account.move.line' propositions. - * - * @see '_formatMoveLine' - * - * @override - * @private - * @param {string} handle - * @returns {Promise} - */ - _performMoveLine: function (handle, mode) { - var line = this.getLine(handle); - var excluded_ids = _.map(line.reconciliation_proposition, function (prop) { - return _.isNumber(prop.id) ? prop.id : null; - }).filter(id => id != null); - var filter = line.filter || ""; - var limit = this.limitMoveLines; - var offset = line.offset; - if (line.limit_override) { - limit = line.limit_override; - offset = 0; - } - var args = [line.account_id.id, line.partner_id, excluded_ids, filter, offset, limit]; - return this._rpc({ - model: 'account.reconciliation.widget', - method: 'get_move_lines_for_manual_reconciliation', - args: args, - context: this.context, - }) - .then(this._formatMoveLine.bind(this, handle, '')); - }, - - _formatToProcessReconciliation: function (line, prop) { - var result = this._super(line, prop); - result['date'] = prop.date; - return result; - }, - _getDefaultMode: function(handle) { - var line = this.getLine(handle); - if (line.balance.amount === 0 && (!line.st_line.mv_lines_match || line.st_line.mv_lines_match.length === 0)) { - return 'inactive'; - } - return line.mv_lines_match.length > 0 ? 'match' : 'create'; - }, - _formatMoveLine: function (handle, mode, mv_lines) { - var self = this; - var line = this.getLine(handle); - if (line.offset === 0 || line.limit_override) { - line.mv_lines_match = mv_lines; - delete line.limit_override; - } else { - line.mv_lines_match = line.mv_lines_match.concat(mv_lines); - } - this._formatLineProposition(line, mv_lines); - - if (line.mode !== 'create' && !line.mv_lines_match.length && !line.filter.length) { - line.mode = this.avoidCreate || !line.balance.amount ? 'inactive' : 'create'; - if (line.mode === 'create') { - return this._computeLine(line).then(function () { - return self.createProposition(handle); - }); - } - } else { - return this._computeLine(line); - } - }, -}); - -return { - StatementModel: StatementModel, - ManualModel: ManualModel, -}; -}); diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js deleted file mode 100644 index d8291ddbef7..00000000000 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ /dev/null @@ -1,924 +0,0 @@ -odoo.define('account.ReconciliationRenderer', function (require) { -"use strict"; - -var Widget = require('web.Widget'); -var FieldManagerMixin = require('web.FieldManagerMixin'); -var relational_fields = require('web.relational_fields'); -var basic_fields = require('web.basic_fields'); -var core = require('web.core'); -var time = require('web.time'); -var session = require('web.session'); -var qweb = core.qweb; -var _t = core._t; - - -/** - * rendering of the bank statement action contains progress bar, title and - * auto reconciliation button - */ -var StatementRenderer = Widget.extend(FieldManagerMixin, { - template: 'reconciliation.statement', - events: { - 'click *[rel="do_action"]': '_onDoAction', - 'click button.js_load_more': '_onLoadMore', - }, - /** - * @override - */ - init: function (parent, model, state) { - this._super(parent); - this.model = model; - this._initialState = state; - }, - /** - * display iniial state and create the name statement field - * - * @override - */ - start: function () { - var self = this; - var defs = [this._super.apply(this, arguments)]; - this.time = Date.now(); - this.$progress = $(''); - - return Promise.all(defs); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - /* - * hide the button to load more statement line - */ - hideLoadMoreButton: function (show) { - if (!show) { - this.$('.js_load_more').show(); - } - else { - this.$('.js_load_more').hide(); - } - }, - showRainbowMan: function (state) { - if (this.model.display_context !== 'validate') { - return - } - var dt = Date.now()-this.time; - var $done = $(qweb.render("reconciliation.done", { - 'duration': moment(dt).utc().format(time.getLangTimeFormat()), - 'number': state.valuenow, - 'timePerTransaction': Math.round(dt/1000/state.valuemax), - 'context': state.context, - })); - $done.find('*').addClass('o_reward_subcontent'); - $done.find('.button_close_statement').click(this._onCloseBankStatement.bind(this)); - $done.find('.button_back_to_statement').click(this._onGoToBankStatement.bind(this)); - // display rainbowman after full reconciliation - if (session.show_effect) { - this.trigger_up('show_effect', { - type: 'rainbow_man', - fadeout: 'no', - message: $done, - }); - this.$el.css('min-height', '450px'); - } else { - $done.appendTo(this.$el); - } - }, - /** - * update the statement rendering - * - * @param {object} state - statement data - * @param {integer} state.valuenow - for the progress bar - * @param {integer} state.valuemax - for the progress bar - * @param {string} state.title - for the progress bar - * @param {[object]} [state.notifications] - */ - update: function (state) { - var self = this; - this._updateProgressBar(state); - - if (state.valuenow === state.valuemax && !this.$('.done_message').length) { - this.showRainbowMan(state); - } - - if (state.notifications) { - this._renderNotifications(state.notifications); - } - }, - _updateProgressBar: function(state) { - this.$progress.find('.valuenow').text(state.valuenow); - this.$progress.find('.valuemax').text(state.valuemax); - this.$progress.find('.progress-bar') - .attr('aria-valuenow', state.valuenow) - .attr('aria-valuemax', state.valuemax) - .css('width', (state.valuenow/state.valuemax*100) + '%'); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - /** - * render the notifications - * - * @param {[object]} notifications - */ - _renderNotifications: function(notifications) { - this.$(".notification_area").empty(); - for (var i=0; i<notifications.length; i++) { - var $notification = $(qweb.render("reconciliation.notification", notifications[i])).hide(); - $notification.appendTo(this.$(".notification_area")).slideDown(300); - } - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * Click on close bank statement button, this will - * close and then open form view of bank statement - * @param {MouseEvent} event - */ - _onCloseBankStatement: function (e) { - this.trigger_up('close_statement'); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onDoAction: function(e) { - e.preventDefault(); - var name = e.currentTarget.dataset.action_name; - var model = e.currentTarget.dataset.model; - if (e.currentTarget.dataset.ids) { - var ids = e.currentTarget.dataset.ids.split(",").map(Number); - var domain = [['id', 'in', ids]]; - } else { - var domain = e.currentTarget.dataset.domain; - } - var context = e.currentTarget.dataset.context; - var tag = e.currentTarget.dataset.tag; - if (tag) { - this.do_action({ - type: 'ir.actions.client', - tag: tag, - context: context, - }) - } else { - this.do_action({ - name: name, - res_model: model, - domain: domain, - context: context, - views: [[false, 'list'], [false, 'form']], - type: 'ir.actions.act_window', - view_mode: "list" - }); - } - }, - /** - * Open the list view for account.bank.statement model - * @private - * @param {MouseEvent} event - */ - _onGoToBankStatement: function (e) { - var journalId = $(e.target).attr('data_journal_id'); - if (journalId) { - journalId = parseInt(journalId); - } - $('.o_reward').remove(); - this.do_action({ - name: 'Bank Statements', - res_model: 'account.bank.statement', - views: [[false, 'list'], [false, 'form']], - type: 'ir.actions.act_window', - context: {search_default_journal_id: journalId, 'journal_type':'bank'}, - view_mode: 'form', - }); - }, - /** - * Load more statement lines for reconciliation - * @private - * @param {MouseEvent} event - */ - _onLoadMore: function (e) { - this.trigger_up('load_more'); - }, -}); - - -/** - * rendering of the bank statement line, contains line data, proposition and - * view for 'match' and 'create' mode - */ -var LineRenderer = Widget.extend(FieldManagerMixin, { - template: "reconciliation.line", - events: { - 'click .accounting_view caption .o_buttons button': '_onValidate', - 'click .accounting_view tfoot': '_onChangeTab', - 'click': '_onTogglePanel', - 'click .o_field_widget': '_onStopPropagation', - 'keydown .o_input, .edit_amount_input': '_onStopPropagation', - 'click .o_notebook li a': '_onChangeTab', - 'click .cell': '_onEditAmount', - 'change input.filter': '_onFilterChange', - 'click .match .load-more a': '_onLoadMore', - 'click .match .mv_line td': '_onSelectMoveLine', - 'click .accounting_view tbody .mv_line td': '_onSelectProposition', - 'click .o_reconcile_models button': '_onQuickCreateProposition', - 'click .create .add_line': '_onCreateProposition', - 'click .reconcile_model_create': '_onCreateReconcileModel', - 'click .reconcile_model_edit': '_onEditReconcileModel', - 'keyup input': '_onInputKeyup', - 'blur input': '_onInputKeyup', - 'keydown': '_onKeydown', - }, - custom_events: _.extend({}, FieldManagerMixin.custom_events, { - 'field_changed': '_onFieldChanged', - }), - _avoidFieldUpdate: {}, - MV_LINE_DEBOUNCE: 200, - - _onKeydown: function (ev) { - switch (ev.which) { - case $.ui.keyCode.ENTER: - this.trigger_up('navigation_move', {direction: 'validate', handle: this.handle}); - break; - case $.ui.keyCode.UP: - ev.stopPropagation(); - ev.preventDefault(); - this.trigger_up('navigation_move', {direction: 'up', handle: this.handle}); - break; - case $.ui.keyCode.DOWN: - ev.stopPropagation(); - ev.preventDefault(); - this.trigger_up('navigation_move', {direction: 'down', handle: this.handle}); - break; - } - }, - - /** - * create partner_id field in editable mode - * - * @override - */ - init: function (parent, model, state) { - this._super(parent); - FieldManagerMixin.init.call(this); - - this.model = model; - this._initialState = state; - if (this.MV_LINE_DEBOUNCE) { - this._onSelectMoveLine = _.debounce(this._onSelectMoveLine, this.MV_LINE_DEBOUNCE, true); - } else { - this._onSelectMoveLine = this._onSelectMoveLine; - } - }, - /** - * @override - */ - start: function () { - var self = this; - var def1 = this._makePartnerRecord(this._initialState.st_line.partner_id, this._initialState.st_line.partner_name).then(function (recordID) { - self.fields = { - partner_id : new relational_fields.FieldMany2One(self, - 'partner_id', - self.model.get(recordID), { - mode: 'edit', - attrs: { - placeholder: self._initialState.st_line.communication_partner_name || _t('Select Partner'), - } - } - ) - }; - self.fields.partner_id.insertAfter(self.$('.accounting_view caption .o_buttons')); - }); - $('<span class="line_info_button fa fa-info-circle"/>') - .appendTo(this.$('thead .cell_info_popover')) - .attr("data-content", qweb.render('reconciliation.line.statement_line.details', {'state': this._initialState})); - this.$el.popover({ - 'selector': '.line_info_button', - 'placement': 'left', - 'container': this.$el, - 'html': true, - // disable bootstrap sanitizer because we use a table that has been - // rendered using qweb.render so it is safe and also because sanitizer escape table by default. - 'sanitize': false, - 'trigger': 'hover', - 'animation': false, - 'toggle': 'popover' - }); - var def2 = this._super.apply(this, arguments); - return Promise.all([def1, def2]); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * update the statement line rendering - * - * @param {object} state - statement line - */ - update: function (state) { - var self = this; - // isValid - var to_check_checked = !!(state.to_check); - this.$('caption .o_buttons button.o_validate').toggleClass('d-none', !!state.balance.type && !to_check_checked); - this.$('caption .o_buttons button.o_reconcile').toggleClass('d-none', state.balance.type <= 0 || to_check_checked); - this.$('caption .o_buttons .o_no_valid').toggleClass('d-none', state.balance.type >= 0); - self.$('caption .o_buttons button.o_validate').toggleClass('text-warning', to_check_checked); - - // partner_id - this._makePartnerRecord(state.st_line.partner_id, state.st_line.partner_name).then(function (recordID) { - self.fields.partner_id.reset(self.model.get(recordID)); - self.$el.attr('data-partner', state.st_line.partner_id); - }); - - // mode - this.$el.data('mode', state.mode).attr('data-mode', state.mode); - this.$('.o_notebook li a').attr('aria-selected', false); - this.$('.o_notebook li a').removeClass('active'); - this.$('.o_notebook .tab-content .tab-pane').removeClass('active'); - this.$('.o_notebook li a[href*="notebook_page_' + state.mode + '"]').attr('aria-selected', true); - this.$('.o_notebook li a[href*="notebook_page_' + state.mode + '"]').addClass('active'); - this.$('.o_notebook .tab-content .tab-pane[id*="notebook_page_' + state.mode + '"]').addClass('active'); - this.$('.create, .match').each(function () { - $(this).removeAttr('style'); - }); - - // reconciliation_proposition - var $props = this.$('.accounting_view tbody').empty(); - - // Search propositions that could be a partial credit/debit. - var props = []; - var balance = state.balance.amount_currency; - _.each(state.reconciliation_proposition, function (prop) { - if (prop.display) { - props.push(prop); - } - }); - - _.each(props, function (line) { - var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state, 'proposition': true})); - if (!isNaN(line.id)) { - $('<span class="line_info_button fa fa-info-circle"/>') - .appendTo($line.find('.cell_info_popover')) - .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); - } - $props.append($line); - }); - - // mv_lines - var matching_modes = self.model.modes.filter(x => x.startsWith('match')); - for (let i = 0; i < matching_modes.length; i++) { - var stateMvLines = state['mv_lines_'+matching_modes[i]] || []; - var recs_count = stateMvLines.length > 0 ? stateMvLines[0].recs_count : 0; - var remaining = recs_count - stateMvLines.length; - var $mv_lines = this.$('div[id*="notebook_page_' + matching_modes[i] + '"] .match table tbody').empty(); - this.$('.o_notebook li a[href*="notebook_page_' + matching_modes[i] + '"]').parent().toggleClass('d-none', stateMvLines.length === 0 && !state['filter_'+matching_modes[i]]); - - _.each(stateMvLines, function (line) { - var $line = $(qweb.render("reconciliation.line.mv_line", {'line': line, 'state': state})); - if (!isNaN(line.id)) { - $('<span class="line_info_button fa fa-info-circle"/>') - .appendTo($line.find('.cell_info_popover')) - .attr("data-content", qweb.render('reconciliation.line.mv_line.details', {'line': line})); - } - $mv_lines.append($line); - }); - this.$('div[id*="notebook_page_' + matching_modes[i] + '"] .match div.load-more').toggle(remaining > 0); - this.$('div[id*="notebook_page_' + matching_modes[i] + '"] .match div.load-more span').text(remaining); - } - - // balance - this.$('.popover').remove(); - this.$('table tfoot').html(qweb.render("reconciliation.line.balance", {'state': state})); - - // create form - if (state.createForm) { - var createPromise; - if (!this.fields.account_id) { - createPromise = this._renderCreate(state); - } - Promise.resolve(createPromise).then(function(){ - var data = self.model.get(self.handleCreateRecord).data; - return self.model.notifyChanges(self.handleCreateRecord, state.createForm) - .then(function () { - // FIXME can't it directly written REPLACE_WITH ids=state.createForm.analytic_tag_ids - return self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'REPLACE_WITH', ids: []}}) - }) - .then(function (){ - var defs = []; - _.each(state.createForm.analytic_tag_ids, function (tag) { - defs.push(self.model.notifyChanges(self.handleCreateRecord, {analytic_tag_ids: {operation: 'ADD_M2M', ids: tag}})); - }); - return Promise.all(defs); - }) - .then(function () { - return self.model.notifyChanges(self.handleCreateRecord, {tax_ids: {operation: 'REPLACE_WITH', ids: []}}) - }) - .then(function (){ - var defs = []; - _.each(state.createForm.tax_ids, function (tag) { - defs.push(self.model.notifyChanges(self.handleCreateRecord, {tax_ids: {operation: 'ADD_M2M', ids: tag}})); - }); - return Promise.all(defs); - }) - .then(function () { - var record = self.model.get(self.handleCreateRecord); - _.each(self.fields, function (field, fieldName) { - if (self._avoidFieldUpdate[fieldName]) return; - if (fieldName === "partner_id") return; - if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) { - field.reset(record); - } - if (fieldName === 'tax_ids') { - if (!state.createForm[fieldName].length || state.createForm[fieldName].length > 1) { - $('.create_force_tax_included').addClass('d-none'); - } - else { - $('.create_force_tax_included').removeClass('d-none'); - var price_include = state.createForm[fieldName][0].price_include; - var force_tax_included = state.createForm[fieldName][0].force_tax_included; - self.$('.create_force_tax_included input').prop('checked', force_tax_included); - self.$('.create_force_tax_included input').prop('disabled', price_include); - } - } - }); - if (state.to_check) { - // Set the to_check field to true if global to_check is set - self.$('.create_to_check input').prop('checked', state.to_check).change(); - } - return true; - }); - }); - } - this.$('.create .add_line').toggle(!!state.balance.amount_currency); - }, - - updatePartialAmount: function(line_id, amount) { - var $line = this.$('.mv_line[data-line-id='+line_id+']'); - $line.find('.edit_amount').addClass('d-none'); - $line.find('.edit_amount_input').removeClass('d-none'); - $line.find('.edit_amount_input').focus(); - $line.find('.edit_amount_input').val(amount.toFixed(2)); - $line.find('.line_amount').addClass('d-none'); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - * @param {jQueryElement} $el - */ - _destroyPopover: function ($el) { - var popover = $el.data('bs.popover'); - if (popover) { - popover.dispose(); - } - }, - /** - * @private - * @param {integer} partnerID - * @param {string} partnerName - * @returns {string} local id of the dataPoint - */ - _makePartnerRecord: function (partnerID, partnerName) { - var field = { - relation: 'res.partner', - type: 'many2one', - name: 'partner_id', - }; - if (partnerID) { - field.value = [partnerID, partnerName]; - } - return this.model.makeRecord('account.bank.statement.line', [field], { - partner_id: { - domain: ["|", ["is_company", "=", true], ["parent_id", "=", false]], - options: { - no_open: true - } - } - }); - }, - - /** - * create account_id, tax_ids, analytic_account_id, analytic_tag_ids, label and amount fields - * - * @private - * @param {object} state - statement line - * @returns {Promise} - */ - _renderCreate: function (state) { - var self = this; - return this.model.makeRecord('account.bank.statement.line', [{ - relation: 'account.account', - type: 'many2one', - name: 'account_id', - domain: [['company_id', '=', state.st_line.company_id]], - }, { - relation: 'account.journal', - type: 'many2one', - name: 'journal_id', - domain: [['company_id', '=', state.st_line.company_id]], - }, { - relation: 'account.tax', - type: 'many2many', - name: 'tax_ids', - domain: [['company_id', '=', state.st_line.company_id]], - }, { - relation: 'account.analytic.account', - type: 'many2one', - name: 'analytic_account_id', - }, { - relation: 'account.analytic.tag', - type: 'many2many', - name: 'analytic_tag_ids', - }, { - type: 'boolean', - name: 'force_tax_included', - }, { - type: 'char', - name: 'label', - }, { - type: 'float', - name: 'amount', - }, { - type: 'char', //TODO is it a bug or a feature when type date exists ? - name: 'date', - }, { - type: 'boolean', - name: 'to_check', - }], { - account_id: { - string: _t("Account"), - domain: [['deprecated', '=', false]], - }, - label: {string: _t("Label")}, - amount: {string: _t("Account")}, - }).then(function (recordID) { - self.handleCreateRecord = recordID; - var record = self.model.get(self.handleCreateRecord); - - self.fields.account_id = new relational_fields.FieldMany2One(self, - 'account_id', record, {mode: 'edit', attrs: {can_create:false}}); - - self.fields.journal_id = new relational_fields.FieldMany2One(self, - 'journal_id', record, {mode: 'edit'}); - - self.fields.tax_ids = new relational_fields.FieldMany2ManyTags(self, - 'tax_ids', record, {mode: 'edit', additionalContext: {append_type_to_tax_name: true}}); - - self.fields.analytic_account_id = new relational_fields.FieldMany2One(self, - 'analytic_account_id', record, {mode: 'edit'}); - - self.fields.analytic_tag_ids = new relational_fields.FieldMany2ManyTags(self, - 'analytic_tag_ids', record, {mode: 'edit'}); - - self.fields.force_tax_included = new basic_fields.FieldBoolean(self, - 'force_tax_included', record, {mode: 'edit'}); - - self.fields.label = new basic_fields.FieldChar(self, - 'label', record, {mode: 'edit'}); - - self.fields.amount = new basic_fields.FieldFloat(self, - 'amount', record, {mode: 'edit'}); - - self.fields.date = new basic_fields.FieldDate(self, - 'date', record, {mode: 'edit'}); - - self.fields.to_check = new basic_fields.FieldBoolean(self, - 'to_check', record, {mode: 'edit'}); - - var $create = $(qweb.render("reconciliation.line.create", {'state': state})); - self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field')) - .then(addRequiredStyle.bind(self, self.fields.account_id)); - self.fields.journal_id.appendTo($create.find('.create_journal_id .o_td_field')); - self.fields.tax_ids.appendTo($create.find('.create_tax_id .o_td_field')); - self.fields.analytic_account_id.appendTo($create.find('.create_analytic_account_id .o_td_field')); - self.fields.analytic_tag_ids.appendTo($create.find('.create_analytic_tag_ids .o_td_field')); - self.fields.force_tax_included.appendTo($create.find('.create_force_tax_included .o_td_field')); - self.fields.label.appendTo($create.find('.create_label .o_td_field')) - .then(addRequiredStyle.bind(self, self.fields.label)); - self.fields.amount.appendTo($create.find('.create_amount .o_td_field')) - .then(addRequiredStyle.bind(self, self.fields.amount)); - self.fields.date.appendTo($create.find('.create_date .o_td_field')); - self.fields.to_check.appendTo($create.find('.create_to_check .o_td_field')); - self.$('.create').append($create); - - function addRequiredStyle(widget) { - widget.$el.addClass('o_required_modifier'); - } - }); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - /** - * The event on the partner m2o widget was propagated to the bank statement - * line widget, causing it to expand and the others to collapse. This caused - * the dropdown to be poorly placed and an unwanted update of this widget. - * - * @private - */ - _onStopPropagation: function(ev) { - ev.stopPropagation(); - }, - - /** - * @private - * @param {MouseEvent} event - */ - _onCreateReconcileModel: function (event) { - event.preventDefault(); - var self = this; - this.do_action({ - type: 'ir.actions.act_window', - res_model: 'account.reconcile.model', - views: [[false, 'form']], - target: 'current' - }, - { - on_reverse_breadcrumb: function() {self.trigger_up('reload');}, - }); - }, - _editAmount: function (event) { - event.stopPropagation(); - var $line = $(event.target); - var moveLineId = $line.closest('.mv_line').data('line-id'); - this.trigger_up('partial_reconcile', {'data': {mvLineId: moveLineId, 'amount': $line.val()}}); - }, - _onEditAmount: function (event) { - event.preventDefault(); - event.stopPropagation(); - // Don't call when clicking inside the input field - if (! $(event.target).hasClass('edit_amount_input')){ - var $line = $(event.target); - this.trigger_up('getPartialAmount', {'data': $line.closest('.mv_line').data('line-id')}); - } - }, - /** - * @private - * @param {MouseEvent} event - */ - _onEditReconcileModel: function (event) { - event.preventDefault(); - var self = this; - this.do_action({ - type: 'ir.actions.act_window', - res_model: 'account.reconcile.model', - views: [[false, 'list'], [false, 'form']], - view_mode: "list", - target: 'current' - }, - { - on_reverse_breadcrumb: function() {self.trigger_up('reload');}, - }); - }, - /** - * @private - * @param {OdooEvent} event - */ - _onFieldChanged: function (event) { - event.stopPropagation(); - var fieldName = event.target.name; - if (fieldName === 'partner_id') { - var partner_id = event.data.changes.partner_id; - this.trigger_up('change_partner', {'data': partner_id}); - } else { - if (event.data.changes.amount && isNaN(event.data.changes.amount)) { - return; - } - this.trigger_up('update_proposition', {'data': event.data.changes}); - } - }, - /** - * @private - */ - _onTogglePanel: function () { - if (this.$el[0].getAttribute('data-mode') == 'inactive') - this.trigger_up('change_mode', {'data': 'default'}); - }, - /** - * @private - */ - _onChangeTab: function(event) { - if (event.currentTarget.nodeName === 'TFOOT') { - this.trigger_up('change_mode', {'data': 'next'}); - } else { - var modes = this.model.modes; - var selected_mode = modes.find(function(e) {return event.target.getAttribute('href').includes(e)}); - if (selected_mode) { - this.trigger_up('change_mode', {'data': selected_mode}); - } - } - }, - /** - * @private - * @param {input event} event - */ - _onFilterChange: function (event) { - this.trigger_up('change_filter', {'data': _.str.strip($(event.target).val())}); - }, - /** - * @private - * @param {keyup event} event - */ - _onInputKeyup: function (event) { - var target_partner_id = $(event.target).parents('[name="partner_id"]'); - if (target_partner_id.length === 1) { - return; - } - if(event.keyCode === 13) { - if ($(event.target).hasClass('edit_amount_input')) { - $(event.target).blur(); - return; - } - var created_lines = _.findWhere(this.model.lines, {mode: 'create'}); - if (created_lines && created_lines.balance.amount) { - this._onCreateProposition(); - } - return; - } - if ($(event.target).hasClass('edit_amount_input')) { - if (event.type === 'keyup') { - return; - } - else { - return this._editAmount(event); - } - } - - var self = this; - for (var fieldName in this.fields) { - var field = this.fields[fieldName]; - if (!field.$el.is(event.target)) { - continue; - } - this._avoidFieldUpdate[field.name] = event.type !== 'focusout'; - field.value = false; - field._setValue($(event.target).val()).then(function () { - self._avoidFieldUpdate[field.name] = false; - }); - break; - } - }, - /** - * @private - */ - _onLoadMore: function (ev) { - ev.preventDefault(); - this.trigger_up('change_offset', {'data': 1}); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onSelectMoveLine: function (event) { - var $el = $(event.target); - $el.prop('disabled', true); - this._destroyPopover($el); - var moveLineId = $el.closest('.mv_line').data('line-id'); - this.trigger_up('add_proposition', {'data': moveLineId}); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onSelectProposition: function (event) { - var $el = $(event.target); - this._destroyPopover($el); - var moveLineId = $el.closest('.mv_line').data('line-id'); - this.trigger_up('remove_proposition', {'data': moveLineId}); - }, - /** - * @private - * @param {MouseEvent} event - */ - _onQuickCreateProposition: function (event) { - document.activeElement && document.activeElement.blur(); - this.trigger_up('quick_create_proposition', {'data': $(event.target).data('reconcile-model-id')}); - }, - /** - * @private - */ - _onCreateProposition: function () { - document.activeElement && document.activeElement.blur(); - var invalid = []; - _.each(this.fields, function (field) { - if (!field.isValid()) { - invalid.push(field.string); - } - }); - if (invalid.length) { - this.do_warn(_t("Some fields are undefined"), invalid.join(', ')); - return; - } - this.trigger_up('create_proposition'); - }, - /** - * @private - */ - _onValidate: function () { - this.trigger_up('validate'); - } -}); - - -/** - * rendering of the manual reconciliation action contains progress bar, title - * and auto reconciliation button - */ -var ManualRenderer = StatementRenderer.extend({ - template: "reconciliation.manual.statement", - -}); - - -/** - * rendering of the manual reconciliation, contains line data, proposition and - * view for 'match' mode - */ -var ManualLineRenderer = LineRenderer.extend({ - template: "reconciliation.manual.line", - /** - * @override - * @param {string} handle - * @param {number} proposition id (move line id) - * @returns {Promise} - */ - removeProposition: function (handle, id) { - if (!id) { - return Promise.resolve(); - } - return this._super(handle, id); - }, - /** - * move the partner field - * - * @override - */ - start: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - return self.model.makeRecord('account.move.line', [{ - relation: 'account.account', - type: 'many2one', - name: 'account_id', - value: [self._initialState.account_id.id, self._initialState.account_id.display_name], - }]).then(function (recordID) { - self.fields.title_account_id = new relational_fields.FieldMany2One(self, - 'account_id', - self.model.get(recordID), - {mode: 'readonly'} - ); - }).then(function () { - return self.fields.title_account_id.appendTo(self.$('.accounting_view thead td:eq(0) span:first')); - }); - }); - }, - /** - * @override - */ - update: function (state) { - this._super(state); - var props = _.filter(state.reconciliation_proposition, {'display': true}); - if (!props.length) { - var $line = $(qweb.render("reconciliation.line.mv_line", {'line': {}, 'state': state})); - this.$('.accounting_view tbody').append($line); - } - }, - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - /** - * display journal_id field - * - * @override - */ - _renderCreate: function (state) { - var self = this; - var parentPromise = this._super(state).then(function() { - self.$('.create .create_journal_id').show(); - self.$('.create .create_date').removeClass('d-none'); - self.$('.create .create_journal_id .o_input').addClass('o_required_modifier'); - }); - return parentPromise; - }, - -}); - - -return { - StatementRenderer: StatementRenderer, - ManualRenderer: ManualRenderer, - LineRenderer: LineRenderer, - ManualLineRenderer: ManualLineRenderer, -}; -}); diff --git a/addons/account/static/src/xml/account_reconciliation.xml b/addons/account/static/src/xml/account_reconciliation.xml deleted file mode 100644 index e1ac928a8e6..00000000000 --- a/addons/account/static/src/xml/account_reconciliation.xml +++ /dev/null @@ -1,400 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<templates xml:space="preserve"> - -<div t-name="reconciliation" class="o_reconciliation"> - <div class="o_form_view"> - <div class="o_form_sheet_bg"> - <div class="o_form_sheet"/> - </div> - </div> -</div> - -<t t-name="reconciliation.control.pager"> - <div class="progress progress-reconciliation"> - <div aria-valuemin="0" t-att-aria-valuenow="widget._initialState.valuenow" t-att-aria-valuemax="widget._initialState.valuemax" class="progress-bar" role="progressbar" style="width: 0%;"><span class="valuenow"><t t-esc="widget._initialState.valuenow"/></span> / <span class="valuemax"><t t-esc="widget._initialState.valuemax"/></span></div> - </div> -</t> - -<t t-name="reconciliation.statement"> - <div t-if="widget._initialState.valuemax"> - <div class="notification_area"/> - <div class="o_reconciliation_lines"/> - <div t-if="widget._initialState.valuemax > widget._initialState.defaultDisplayQty"> - <button class="btn btn-secondary js_load_more">Load more</button> - </div> - </div> - <div t-else="" class="o_view_noreconciliation"> - <p>Nothing to do!</p> - <p>This page displays all the bank transactions that are to be reconciled and provides with a neat interface to do so.</p> - </div> -</t> - -<t t-name="reconciliation.manual.statement" t-extend="reconciliation.statement"> - <t t-jquery="div:first" t-operation="attributes"> - <attribute name="class" value="o_manual_statement" /> - </t> - <t t-jquery=".o_view_noreconciliation p" t-operation="replace"></t> - <t t-jquery=".o_filter_input_wrapper" t-operation="replace"></t> - <t t-jquery=".o_view_noreconciliation" t-operation="append"> - <p><b>Good Job!</b> There is nothing to reconcile.</p> - <p>All invoices and payments have been matched, your accounts' balances are clean.</p> - <p> - From now on, you may want to: - <ul> - <li>Check that you have no bank statement lines to <a href="#" - rel="do_action" - data-tag="bank_statement_reconciliation_view">reconcile</a></li> - <li>Verify <a href="#" - rel="do_action" - data-action_name="Unpaid Customer Invoices" - data-model="account.move" - data-domain="[('type', 'in', ('out_invoice', 'out_refund'))]" - data-context="{'search_default_unpaid': 1}">unpaid invoices</a> and follow-up customers</li> - <li>Pay your <a href="#" - rel="do_action" - data-action_name="Unpaid Vendor Bills" - data-model="account.move" - data-domain="[('type', 'in', ('in_invoice', 'in_refund'))]" - data-context="{'search_default_unpaid': 1}">vendor bills</a></li> - <li>Check all <a href="#" - rel="do_action" - data-action_name="Unreconciled Entries" - data-model="account.move.line" - data-context="{'search_default_unreconciled': 1}">unreconciled entries</a></li> - </ul> - </p> - </t> -</t> - -<div t-name="reconciliation.done" class="done_message"> - <h2>Congrats, you're all done!</h2> - <p>You reconciled <strong><t t-esc="number"/></strong> transactions in <strong><t t-esc="duration"/></strong>. - <t t-if="number > 1"> - <br/>That's on average <t t-esc="timePerTransaction"/> seconds per transaction. - </t> - </p> - <t t-if="context && context.active_model"> - <p t-if="context['active_model'] === 'account.journal' || context['active_model'] === 'account.bank.statement' || context['active_model'] === 'account.bank.statement.import'" class="actions_buttons"> - <t t-if="context.journal_id"> - <button class="button_back_to_statement btn btn-secondary" t-att-data_journal_id='context.journal_id'>Go to bank statement(s)</button> - </t> - <t t-if="context['active_model'] === 'account.bank.statement'"> - <button class="button_close_statement btn btn-primary" style="display: inline-block;">Close statement</button> - </t> - </p> - </t> -</div> - -<t t-name="reconciliation.line"> - <t t-set="state" t-value="widget._initialState"/> - <div class="o_reconciliation_line" t-att-data-mode="state.mode" tabindex="0"> - <table class="accounting_view"> - <caption style="caption-side: top;"> - <div class="float-right o_buttons"> - <button t-attf-class="o_no_valid btn btn-secondary #{state.balance.type < 0 ? '' : 'd-none'}" disabled="disabled" data-toggle="tooltip" title="Select a partner or choose a counterpart" accesskey="">Validate</button> - <button t-attf-class="o_validate btn btn-secondary #{!state.balance.type ? '' : 'd-none'}">Validate</button> - <button t-attf-class="o_reconcile btn btn-primary #{state.balance.type > 0 ? '' : 'd-none'}">Validate</button> - </div> - </caption> - <thead> - <tr> - <td class="cell_account_code"><t t-esc="state.st_line.account_code"/></td> - <td class="cell_due_date"><t t-esc="state.st_line.date"/></td> - <td class="cell_label"><t t-if="state.st_line.name" t-esc="state.st_line.name"/> <t t-if="state.st_line.amount_currency_str"> (<t t-esc="state.st_line.amount_currency_str"/>)</t></td> - <td class="cell_left"><t t-if="state.st_line.amount > 0"><t t-raw="state.st_line.amount_str"/></t></td> - <td class="cell_right"><t t-if="state.st_line.amount < 0"><t t-raw="state.st_line.amount_str"/></t></td> - <td class="cell_info_popover"></td> - </tr> - </thead> - <tbody> - <t t-foreach="state.reconciliation_proposition" t-as="line"><t t-call="reconciliation.line.mv_line"/></t> - </tbody> - <tfoot> - <t t-call="reconciliation.line.balance"/> - </tfoot> - </table> - <div class="o_notebook"> - <ul class="nav nav-tabs ml-0 mr-0"> - <li class="nav-item" t-attf-title="{{'Match statement with existing lines on receivable/payable accounts<br>* Black line: existing journal entry that should be matched<br>* Blue lines: existing payment that should be matched'}}" data-toggle="tooltip"><a data-toggle="tab" disable_anchor="true" t-attf-href="#notebook_page_match_rp_#{state.st_line.id}" class="nav-link active nav-match_rp" role="tab" aria-selected="true">Customer/Vendor Matching</a></li> - <li class="nav-item" title="Match with entries that are not from receivable/payable accounts" data-toggle="tooltip"><a data-toggle="tab" disable_anchor="true" t-attf-href="#notebook_page_match_other_#{state.st_line.id}" class="nav-link nav-match_other" role="tab" aria-selected="false">Miscellaneous Matching</a></li> - <li class="nav-item" title="Create a counterpart" data-toggle="tooltip"><a data-toggle="tab" disable_anchor="true" t-attf-href="#notebook_page_create_#{state.st_line.id}" class="nav-link nav-create" role="tab" aria-selected="false">Manual Operations</a></li> - </ul> - <div class="tab-content"> - <div class="tab-pane active" t-attf-id="notebook_page_match_rp_#{state.st_line.id}"> - <div class="match"> - <t t-call="reconciliation.line.match"/> - </div> - </div> - <div class="tab-pane" t-attf-id="notebook_page_match_other_#{state.st_line.id}"> - <div class="match"> - <t t-call="reconciliation.line.match"/> - </div> - </div> - <div class="tab-pane" t-attf-id="notebook_page_create_#{state.st_line.id}"> - <div class="create"></div> - </div> - </div> - </div> - </div> -</t> - -<t t-name="reconciliation.manual.line" t-extend="reconciliation.line"> - <t t-jquery=".o_buttons" t-operation="replace"> - <div class="float-right o_buttons"> - <button t-attf-class="o_validate btn btn-secondary #{!state.balance.type ? '' : 'd-none'}">Reconcile</button> - <button t-attf-class="o_reconcile btn btn-primary #{state.balance.type > 0 ? '' : 'd-none'}">Reconcile</button> - <button t-attf-class="o_no_valid btn btn-secondary #{state.balance.type < 0 ? '' : 'd-none'}">Skip</button> - </div> - </t> - <t t-jquery=".accounting_view tbody" t-operation="append"> - <t t-if='!_.filter(state.reconciliation_proposition, {"display": true}).length'> - <t t-set="line" t-value='{}'/> - <t t-call="reconciliation.line.mv_line"/> - </t> - </t> - <t t-jquery=".accounting_view thead tr" t-operation="replace"> - <tr> - <td colspan="3"><span/><span t-if="state.last_time_entries_checked">Last Reconciliation: <t t-esc="state.last_time_entries_checked"/></span></td> - <td colspan="2"><t t-esc="state.st_line.account_code"/></td> - <td class="cell_info_popover"></td> - </tr> - </t> - <t t-jquery='div[t-attf-id*="notebook_page_match_rp"]' t-operation="replace"/> - <t t-jquery='a[t-attf-href*="notebook_page_match_rp"]' t-operation="replace"/> -</t> - -<t t-name="reconciliation.line.balance"> - <tr t-if="state.balance.show_balance"> - <td class="cell_account_code"><t t-esc="state.balance.account_code"/></td> - <td class="cell_due_date"></td> - <td class="cell_label"><t t-if="state.st_line.partner_id">Open balance</t><t t-else="">Choose counterpart or Create Write-off</t></td> - <td class="cell_left"><t t-if="state.balance.amount_currency < 0"><span role="img" t-if="state.balance.amount_currency_str" t-attf-class="o_multi_currency o_multi_currency_color_#{state.balance.currency_id%8} line_info_button fa fa-money" t-att-data-content="state.balance.amount_currency_str" t-att-aria-label="state.balance.amount_currency_str" t-att-title="state.balance.amount_currency_str"/><t t-raw="state.balance.amount_str"/></t></td> - <td class="cell_right"><t t-if="state.balance.amount_currency > 0"><span role="img" t-if="state.balance.amount_currency_str" t-attf-class="o_multi_currency o_multi_currency_color_#{state.balance.currency_id%8} line_info_button fa fa-money" t-att-data-content="state.balance.amount_currency_str" t-att-aria-label="state.balance.amount_currency_str" t-att-title="state.balance.amount_currency_str"/><t t-raw="state.balance.amount_str"/></t></td> - <td class="cell_info_popover"></td> - </tr> -</t> - - -<div t-name="reconciliation.line.match"> - <div class="match_controls"> - <span><input class="filter o_input" placeholder="Filter on account, label, partner, amount,..." type="text" t-att-value="state['filter_{{state.mode}}']"/></span> - <button class="btn btn-secondary btn-sm fa fa-search" type="button"></button> - </div> - <table> - <tbody> - </tbody> - </table> - <div class="load-more text-center"> - <a href="#">Load more... (<span></span> remaining)</a> - </div> -</div> - - -<div t-name="reconciliation.line.create"> - <div class="quick_add"> - <div class="btn-group o_reconcile_models" t-if="state.reconcileModels"> - <t t-foreach="state.reconcileModels" t-as="reconcileModel"> - <button class="btn btn-primary" - t-if="reconcileModel.rule_type === 'writeoff_button' && (reconcileModel.match_journal_ids.length == 0 || reconcileModel.match_journal_ids.includes(state.st_line.journal_id))" - t-att-data-reconcile-model-id="reconcileModel.id"> - <t t-esc="reconcileModel.name"/> - </button> - </t> - <p t-if="!state.reconcileModels.length" style="color: #bbb;">To speed up reconciliation, define <a style="cursor: pointer;" class="reconcile_model_create">reconciliation models</a>.</p> - </div> - <div class="dropdown float-right"> - <a data-toggle="dropdown" href="#"><span class="fa fa-cog" role="img" aria-label="Settings"/></a> - <div class="dropdown-menu dropdown-menu-right" role="menu" aria-label="Presets config"> - <a role="menuitem" class="dropdown-item reconcile_model_create" href="#">Create model</a> - <a role="menuitem" class="dropdown-item reconcile_model_edit" href="#">Modify models</a> - </div> - </div> - </div> - <div class="clearfix o_form_sheet"> - <div class="o_group"> - <table class="o_group o_inner_group o_group_col_6"> - <tbody> - <tr class="create_account_id"> - <td class="o_td_label"><label class="o_form_label">Account</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_tax_id"> - <td class="o_td_label"><label class="o_form_label">Taxes</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_analytic_account_id" groups="analytic.group_analytic_accounting"> - <td class="o_td_label"><label class="o_form_label">Analytic Acc.</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_analytic_tag_ids" groups="analytic.group_analytic_tags"> - <td class="o_td_label"><label class="o_form_label">Analytic Tags.</label></td> - <td class="o_td_field"></td> - </tr> - </tbody> - </table> - <table class="o_group o_inner_group o_group_col_6"> - <tbody> - <tr class="create_journal_id" style="display: none;"> - <td class="o_td_label"><label class="o_form_label">Journal</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_label"> - <td class="o_td_label"><label class="o_form_label">Label</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_amount"> - <td class="o_td_label"><label class="o_form_label">Amount</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_force_tax_included d-none"> - <td class="o_td_label"><label class="o_form_label">Tax Included in Price</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_date d-none"> - <td class="o_td_label"><label class="o_form_label">Writeoff Date</label></td> - <td class="o_td_field"></td> - </tr> - <tr class="create_to_check"> - <td class="o_td_label"><label class="o_form_label">To Check</label></td> - <td class="o_td_field"></td> - </tr> - </tbody> - </table> - </div> -</div> - <div class="add_line_container"> - <a class="add_line" t-att-style="!state.balance.amout ? 'display: none;' : null"><i class="fa fa-plus-circle"/> Save and New</a> - </div> -</div> - - -<t t-name="reconciliation.line.mv_line.amount"> - <span t-att-class="(line.is_move_line && proposition == true) ? 'cell' : ''"> - <span class="line_amount"> - <span t-if="line.amount_currency_str" - t-attf-class="o_multi_currency o_multi_currency_color_#{line.currency_id%8} line_info_button fa fa-money" - t-att-data-content="line.amount_currency_str"/> - <span t-if="line.partial_amount && line.partial_amount != line.amount" class="strike_amount text-muted"> - <t t-raw="line.amount_str"/> - <br/> - </span> - </span> - <t t-if="line.is_move_line && proposition == true"> - <i class="fa fa-pencil edit_amount"></i> - <input class="edit_amount_input text-right d-none"/> - </t> - <span class="line_amount"> - <t t-if="!line.partial_amount_str" t-raw="line.amount_str"/> - <t t-if="line.partial_amount_str && line.partial_amount != line.amount" t-raw="line.partial_amount_str"/> - </span> - </span> -</t> - - -<t t-name="reconciliation.line.mv_line"> - <tr t-if="line.display !== false" t-attf-class="mv_line #{line.already_paid ? ' already_reconciled' : ''} #{line.__invalid ? 'invalid' : ''} #{line.is_tax ? 'is_tax' : ''}" t-att-data-line-id="line.id" t-att-data-selected="selected"> - <td class="cell_account_code"><t t-esc="line.account_code"/>​</td> <!-- zero width space to make empty lines the height of the text --> - <td class="cell_due_date"> - <t t-if="typeof(line.id) != 'number' && line.id"> - <span class="badge badge-secondary">New</span> - </t> - <t t-else="" t-esc="line.date_maturity || line.date"/> - </td> - <td class="cell_label"> - <t t-if="line.partner_id && line.partner_id !== state.st_line.partner_id"> - <t t-if="line.partner_name.length"> - <span class="font-weight-bold" t-esc="line.partner_name"/>: - </t> - </t> - <t t-esc="line.label || line.name"/> - <t t-if="line.ref && line.ref.length"> : </t> - <t t-esc="line.ref"/> - </td> - <td class="cell_left"> - <t t-if="line.amount < 0"> - <t t-call="reconciliation.line.mv_line.amount"/> - </t> - </td> - <td class="cell_right"> - <t t-if="line.amount > 0"> - <t t-call="reconciliation.line.mv_line.amount"/> - </t> - </td> - <td class="cell_info_popover"></td> - </tr> -</t> - - -<t t-name="reconciliation.line.mv_line.details"> - <table class='details'> - <tr t-if="line.account_code"><td>Account</td><td><t t-esc="line.account_code"/> <t t-esc="line.account_name"/></td></tr> - <tr><td>Date</td><td><t t-esc="line.date"/></td></tr> - <tr><td>Due Date</td><td><t t-esc="line.date_maturity || line.date"/></td></tr> - <tr><td>Journal</td><td><t t-esc="line.journal_id[1]"/></td></tr> - <tr t-if="line.partner_id"><td>Partner</td><td><t t-esc="line.partner_name"/></td></tr> - <tr><td>Label</td><td><t t-esc="line.label"/></td></tr> - <tr t-if="line.ref"><td>Ref</td><td><t t-esc="line.ref"/></td></tr> - <tr><td>Amount</td><td><t t-raw="line.total_amount_str"/><t t-if="line.total_amount_currency_str"> (<t t-esc="line.total_amount_currency_str"/>)</t></td></tr> - <tr t-if="line.is_partially_reconciled"><td>Residual</td><td> - <t t-raw="line.amount_str"/><t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t> - </td></tr> - <tr class="one_line_info" t-if='line.already_paid'> - <td colspan="2">This payment is registered but not reconciled.</td> - </tr> - </table> -</t> - - -<t t-name="reconciliation.line.statement_line.details"> - <table class='details'> - <tr><td>Date</td><td><t t-esc="state.st_line.date"/></td></tr> - <tr t-if="state.st_line.partner_name"><td>Partner</td><td><t t-esc="state.st_line.partner_name"/></td></tr> - <tr t-if="state.st_line.ref"><td>Transaction</td><td><t t-esc="state.st_line.ref"/></td></tr> - <tr><td>Description</td><td><t t-esc="state.st_line.name"/></td></tr> - <tr><td>Amount</td><td><t t-raw="state.st_line.amount_str"/><t t-if="state.st_line.amount_currency_str"> (<t t-esc="state.st_line.amount_currency_str"/>)</t></td></tr> - <tr><td>Account</td><td><t t-esc="state.st_line.account_code"/> <t t-esc="state.st_line.account_name"/></td></tr> - <tr t-if="state.st_line.note"><td>Note</td><td style="white-space: pre;"><t t-esc="state.st_line.note"/></td></tr> - </table> -</t> - - -<t t-name="reconciliation.notification.reconciled"> - <t t-if="details !== undefined"> - <a rel="do_action" href="#" aria-label="External link" title="External link" - t-att-data-action_name="details.name" - t-att-data-model="details.model" - t-att-data-ids="details.ids"> - <t t-esc="nb_reconciled_lines"/> - statement lines - </a> - have been reconciled automatically. - </t> -</t> - - -<t t-name="reconciliation.notification.default"> - <t t-esc="message"/> - <t t-if="details !== undefined"> - <a class="fa fa-external-link" rel="do_action" href="#" aria-label="External link" title="External link" - t-att-data-action_name="details.name" - t-att-data-model="details.model" - t-att-data-ids="details.ids"> - </a> - </t> -</t> - - -<t t-name="reconciliation.notification"> - <div t-att-class="'notification alert-dismissible alert alert-' + type" role="alert"> - <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span title="Close" class="fa fa-times"></span></button> - <t t-if="template"> - <t t-call="{{template}}"/> - </t> - <t t-else=""> - <t t-call="reconciliation.notification.default"/> - </t> - </div> -</t> - -</templates> diff --git a/addons/account/static/tests/reconciliation_tests.js b/addons/account/static/tests/reconciliation_tests.js deleted file mode 100644 index a1e2e215126..00000000000 --- a/addons/account/static/tests/reconciliation_tests.js +++ /dev/null @@ -1,2093 +0,0 @@ -odoo.define('account.reconciliation_tests.data', function () { -"use strict"; - -/* - * Debug tip: - * To be able to "see" the test in the browser: - * var $body = $('body'); - * $body.addClass('debug'); - * clientAction.appendTo($body); - */ - -var Datas = {}; - -var db = { - 'res.company': { - fields: { - id: {string: "ID", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, - }, - records: [ - {id: 1, display_name: "company 1"}, - ], - }, - 'res.partner': { - fields: { - id: {string: "ID", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, - image: {string: "image", type: 'integer'}, - parent_id: {string: "Parent", type: 'boolean'}, - is_company: {string: "Is company", type: 'boolean'}, - property_account_receivable_id: {string: 'Account receivable', type: 'many2one', relation: 'account.account'}, - property_account_payable_id: {string: 'Account payable', type: 'many2one', relation: 'account.account'}, - }, - records: [ - {id: 1, display_name: "partner 1", image: 'AAA'}, - {id: 2, display_name: "partner 2", image: 'BBB'}, - {id: 3, display_name: "partner 3", image: 'CCC'}, - {id: 4, display_name: "partner 4", image: 'DDD'}, - {id: 8, display_name: "Agrolait", image: 'EEE'}, - {id: 12, display_name: "Camptocamp", image: 'FFF', property_account_receivable_id: 287, property_account_payable_id: 287}, - // add more to have 'Search More' option - {id: 98, display_name: "partner 98", image: 'YYY'}, - {id: 99, display_name: "partner 99", image: 'ZZZ'}, - ], - mark_as_reconciled: function () { - return Promise.resolve(); - }, - }, - 'account.account': { - fields: { - id: {string: "ID", type: 'integer'}, - code: {string: "code", type: 'integer'}, - name: {string: "Displayed name", type: 'char'}, - company_id: {string: "Company", type: 'many2one', relation: 'res.company'}, - deprecated: {string: "Deprecated", type: 'boolean'}, - }, - records: [ - {id: 282, code: 100000, name: "100000 Fixed Asset Account", company_id: 1}, - {id: 283, code: 101000, name: "101000 Current Assets", company_id: 1}, - {id: 284, code: 101110, name: "101110 Stock Valuation Account", company_id: 1}, - {id: 285, code: 101120, name: "101120 Stock Interim Account (Received)", company_id: 1}, - {id: 286, code: 101130, name: "101130 Stock Interim Account (Delivered)", company_id: 1}, - {id: 287, code: 101200, name: "101200 Account Receivable", company_id: 1}, - {id: 288, code: 101300, name: "101300 Tax Paid", company_id: 1}, - {id: 308, code: 101401, name: "101401 Bank", company_id: 1}, - {id: 499, code: 499001, name: "499001 Suspense Account", company_id: 1}, - {id: 500, code: 500, name: "500 Account", company_id: 1}, - {id: 501, code: 501, name: "501 Account", company_id: 1}, - {id: 502, code: 502, name: "502 Account", company_id: 1}, - {id: 503, code: 503, name: "503 Account", company_id: 1}, - {id: 504, code: 504, name: "504 Account", company_id: 1}, - ], - mark_as_reconciled: function () { - return Promise.resolve(); - }, - }, - 'account.tax': { - fields: { - id: {string: "ID", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, - amount: {string: "amout", type: 'float'}, - price_include: {string: "Included in Price", type: 'boolean'}, - account_id: {string: "partner", type: 'many2one', relation: 'account.account'}, - company_id: {string: "Company", type: 'many2one', relation: 'res.company'}, - amount_type: {string: "type", type: 'selection'} - }, - records: [ - {id: 6, display_name: "Tax 20.00%", amount: 20, amount_type: 'percent', price_include: false, company_id: 1}, - {id: 7, display_name: "Tax 10.00% include", amount: 10, amount_type: 'percent', price_include: true, account_id: 288, company_id: 1}, - ], - json_friendly_compute_all: function (args) { - var tax = _.find(db['account.tax'].records, {'id': args[0][0]}); - var amount = args[1]; - var tax_base = tax.price_include ? amount*100/(100+tax.amount) : amount; - return Promise.resolve({ - "base": amount, - "taxes": [{ - 'id': tax.id, - 'amount': tax_base*tax.amount/100, - "base": tax_base, - 'name': tax.display_name, - "analytic": false, - "refund_account_id": false, - 'account_id': tax.account_id - }], - "total_excluded": amount/100*(100-tax.amount), - "total_included": amount, - }); - }, - }, - 'account.journal': { - fields: { - id: {string: "ID", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, - company_id: {string: "Company", type: 'many2one', relation: 'res.company'}, - }, - records: [ - {id: 8, display_name: "company 1 journal", company_id: 1} - ] - }, - 'account.analytic.account': { - fields: { - id: {string: "ID", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, - }, - records: [ - {id: 16, display_name: "Administrative"}, - {id: 7, display_name: "Agrolait - Agrolait"}, - {id: 8, display_name: "Asustek - ASUSTeK"}, - {id: 15, display_name: "Camp to Camp - Camptocamp"}, - {id: 6, display_name: "CampToCamp - Camptocamp"}, - {id: 17, display_name: "Commercial & Marketing"}, - {id: 23, display_name: "Data Import/Export Plugin - Delta PC"}, - {id: 9, display_name: "Delta PC - Delta PC"}, - ] - }, - 'account.analytic.tag': { - fields: { - id: {string: 'id', type: 'integer'}, - display_name: {string: 'display_name', type: 'char'}, - }, - records: [ - {id: 1, display_name: 'Come together'}, - {id: 2, display_name: 'Right now'}, - ], - }, - 'account.bank.statement': { - fields: {}, - }, - 'account.bank.statement.line': { - fields: { - id: {string: "ID", type: 'integer'}, - display_name: {string: "Displayed name", type: 'char'}, - partner_id: {string: "partner", type: 'many2one', relation: 'res.partner'}, - company_id: {string: "Company", type: 'many2one', relation: 'res.company'}, - }, - records: [ - {id: 5, display_name: "SAJ/2014/002 and SAJ/2014/003", company_id: 1}, - {id: 6, display_name: "Bank fees", company_id: 1}, - {id: 7, display_name: "Prepayment", company_id: 1}, - {id: 8, display_name: "First 2000 \u20ac of SAJ/2014/001", company_id: 1}, - ], - }, - 'account.move.line': { - fields: {}, - }, - 'account.reconcile.model': { - fields: { - id: {string: "ID", type: 'integer'}, - name: {string: "Button Label", type: 'char'}, - rule_type: {string: "Type", type: 'selection', selection: [['writeoff_button', 'Create a Button'], ['writeoff_suggestion', 'Write off Suggestion'], ['invoice_matching', 'Invoice matching']], default:'writeoff_button'}, - has_second_line: {string: "Add a second line", type: 'boolean'}, - account_id: {string: "Account", type: 'many2one', relation:'account.account'}, - journal_id: {string: "Journal", type: 'many2one', relation:'account.journal'}, - label: {string: "Journal Item Label", type: 'char'}, - amount_type: {string: 'amount_type', type: 'selection', selection: [['fixed', 'Fixed'], ['percentage', 'Percentage of balance']], default:'percentage'}, - amount: {string: "Amount", type: 'float', digits:0, help:"Fixed amount will count as a debit if it is negative, as a credit if it is positive.", default:100.0}, - tax_ids: {string: "Tax", type: 'many2many', relation:'account.tax'}, - analytic_account_id: {string: "Analytic Account", type: 'many2one', relation:'account.analytic.account'}, - second_account_id: {string: "Second Account", type: 'many2one', relation:'account.account', domain:[('deprecated', '=', false)]}, - second_journal_id: {string: "Second Journal", type: 'many2one', relation:'account.journal', help:"This field is ignored in a bank statement reconciliation."}, - second_label: {string: "Second Journal Item Label", type: 'char'}, - second_amount_type: {string: "Second amount_type", type: 'selection', selection: [['fixed', 'Fixed'], ['percentage', 'Percentage of balance']], default:'percentage'}, - second_amount: {string: "Second Amount", type: 'float', digits:0, help:"Fixed amount will count as a debit if it is negative, as a credit if it is positive.", default:100.0}, - second_tax_ids: {string: "Second Tax", type: 'many2many', relation: 'account.tax'}, - second_analytic_account_id: {string: "Second Analytic Account", type: 'many2one', relation:'account.analytic.account'}, - match_journal_ids: {string: "Journal Ids", type: 'many2many', relation: 'account.journal'}, - analytic_tag_ids: {string: 'Analytic tags', type: 'many2many', relation: 'account.analytic.tag'}, - }, - records: [ - {'second_analytic_account_id': false, 'second_amount_type': "percentage", 'second_journal_id': false, 'id': 4, 'analytic_account_id': false, 'display_name': "Int\u00e9rrets", 'rule_type': 'writeoff_button', 'second_tax_ids': [], 'has_second_line': false, 'journal_id': false, 'label': false, 'second_label': false, 'second_account_id': false, 'account_id': 282, 'company_id': [1, "Demo SPRL"], 'tax_ids': [], 'amount_type': "fixed", 'name': "Int\u00e9rrets", 'amount': 0.0, 'second_amount': 100.0, 'match_journal_ids': []}, - {'second_analytic_account_id': false, 'second_amount_type': "percentage", 'second_journal_id': false, 'id': 2, 'analytic_account_id': false, 'display_name': "Perte et Profit", 'rule_type': 'writeoff_button', 'second_tax_ids': [], 'has_second_line': false, 'journal_id': false, 'label': false, 'second_label': false, 'second_account_id': false, 'account_id': 283, 'company_id': [1, "Demo SPRL"], 'tax_ids': [], 'amount_type': "percentage", 'name': "Perte et Profit", 'amount': 100.0, 'second_amount': 100.0, 'match_journal_ids': []}, - {'second_analytic_account_id': false, 'second_amount_type': "percentage", 'second_journal_id': false, 'id': 5, 'analytic_account_id': false, 'display_name': "Fs bank", 'rule_type': 'writeoff_button', 'second_tax_ids': [], 'has_second_line': false, 'journal_id': false, 'label': false, 'second_label': false, 'second_account_id': false, 'account_id': 284, 'company_id': [1, "Demo SPRL"], 'tax_ids': [], 'amount_type': "percentage", 'name': "Fs bank", 'amount': 100.0, 'second_amount': 100.0}, - {'second_analytic_account_id': false, 'second_amount_type': "percentage", 'second_journal_id': false, 'id': 8, 'analytic_account_id': false, 'display_name': "Caisse Sand.", 'rule_type': 'writeoff_button', 'second_tax_ids': [], 'has_second_line': false, 'journal_id': false, 'label': "Caisse Sand.", 'second_label': false, 'second_account_id': false, 'account_id': 308, 'company_id': [1, "Demo SPRL"], 'tax_ids': [], 'amount_type': "percentage", 'name': "Caisse Sand.", 'amount': 100.0, 'second_amount': 100.0, 'match_journal_ids': []}, - {'second_analytic_account_id': false, 'second_amount_type': "percentage", 'second_journal_id': false, 'id': 3, 'analytic_account_id': false, 'display_name': "ATOS", 'rule_type': 'writeoff_button', 'second_tax_ids': [7], 'has_second_line': true, 'journal_id': false, 'label': "ATOS Banque", 'second_label': "ATOS Frais", 'second_account_id': 286, 'account_id': 285, 'company_id': [1, "Demo SPRL"], 'tax_ids': [6], 'amount_type': "percentage", 'name': "ATOS", 'amount': 97.5, 'second_amount': -14.75}, - {'second_analytic_account_id': false, 'second_amount_type': "percentage", 'second_journal_id': false, 'id': 10, 'analytic_account_id': false, 'display_name': "Double", 'rule_type': 'writeoff_button', 'second_tax_ids': [], 'has_second_line': true, 'journal_id': false, 'label': "Double Banque", 'second_label': "Double Frais", 'second_account_id': 286, 'account_id': 285, 'company_id': [1, "Demo SPRL"], 'tax_ids': [], 'amount_type': "percentage", 'name': "Double", 'amount': 97.5, 'second_amount': 100, 'match_journal_ids': [], 'analytic_tag_ids': [1,2]}, - ] - }, - 'account.reconciliation.widget': { - fields: {}, - auto_reconcile: function () { - return Promise.resolve(Datas.used.auto_reconciliation); - }, - process_bank_statement_line: function (args) { - var datas = args[1]; - var ids = _.flatten(_.pluck(_.pluck(datas, 'counterpart_aml_dicts'), 'counterpart_aml_id')); - ids = ids.concat(_.flatten(_.pluck(datas, 'payment_aml_ids'))); - ids = _.compact(ids); - - for (var key in Datas.used.move_lines_for_manual_reconciliation) { - Datas.used.move_lines_for_manual_reconciliation[key] = _.filter(Datas.used.move_lines_for_manual_reconciliation[key], function (mv_line) { - return ids.indexOf(mv_line.id) === -1; - }); - } - return Promise.resolve(); - }, - get_move_lines_for_bank_statement_line: function (args) { - var partner_id = args.splice(1, 1)[0]; - var excluded_ids = args.splice(1, 1)[0]; - var mode = args.splice(-1, 1) [0] - if (mode === 'other') return Promise.resolve([]) - args.splice(-1,1); // ignore limit - var key = JSON.stringify(args); - if (!Datas.used.mv_lines[key]) { - throw new Error("Unknown parameters for get_move_lines_for_bank_statement_line: '"+ key + "'"); - } - var lines = Datas.used.mv_lines[key] - .filter(function (line) { - return excluded_ids.indexOf(line.id) === -1 && (!partner_id || partner_id === line.partner_id); - }) - .map(function (line, i, src) { - line.recs_count = src.length; - return line; - }) - .slice(0, options.params.limitMoveLines); - return Promise.resolve(lines); - }, - get_bank_statement_line_data: function (args) { - var ids = args[0]; - var results = { - value_min: 0, - value_max: ids.length, - lines: _.filter(Datas.used.data_widget, function (w) {return _.contains(ids, w.st_line.id);}) - }; - return Promise.resolve(results); - }, - get_bank_statement_data: function () { - var results = Datas.used.data_preprocess; - results.lines = _.filter(Datas.used.data_widget, function (w) {return _.contains(results.st_lines_ids, w.st_line.id);}); - return Promise.resolve(results); - }, - get_move_lines_for_manual_reconciliation: function (args) { - var excluded_ids = args.splice(2, 1)[0]; - args.splice(-1,1); // ignore limit - var key = JSON.stringify(args); - if (!Datas.used.move_lines_for_manual_reconciliation[key]) { - throw new Error("Unknown parameters for get_move_lines_for_manual_reconciliation: '"+ key + "'"); - } - var lines = Datas.used.move_lines_for_manual_reconciliation[key] - .filter(function (line) { - return excluded_ids.indexOf(line.id) === -1; - }) - .map(function (line, i, src) { - line.recs_count = src.length; - return line; - }) - .slice(0, options.params.limitMoveLines); - return Promise.resolve(lines); - }, - get_all_data_for_manual_reconciliation: function (args) { - var key = JSON.stringify(args); - if (!Datas.used.data_for_manual_reconciliation_widget[key]) { - throw new Error("Unknown parameters for get_all_data_for_manual_reconciliation: '"+ key + "'"); - } - return Promise.resolve(Datas.used.data_for_manual_reconciliation_widget[key]); - }, - process_move_lines: function (args) { - var datas = args[0]; - for (var i in datas) { - var data = datas[i]; - for (var key in Datas.used.move_lines_for_manual_reconciliation) { - Datas.used.move_lines_for_manual_reconciliation[key] = _.filter(Datas.used.move_lines_for_manual_reconciliation[key], function (mv_line) { - return data.mv_line_ids.indexOf(mv_line.id) === -1; - }); - } - } - return Promise.resolve(); - }, - } -}; - -var data_preprocess = { - value_min: 0, - value_max: 4, - notifications: [], - num_already_reconciled_lines: 0, - st_lines_ids: [5, 6, 7, 8], - statement_name: 'BNK/2014/001', -}; - -var data_widget = [ - { - 'st_line': { - 'currency_id': 3, - 'communication_partner_name': false, - 'open_balance_account_id': 287, - 'name': "SAJ/2014/002 and SAJ/2014/003", - 'partner_name': "Agrolait", - 'partner_id': 8, - 'has_no_partner': false, - 'journal_id': 84, - 'account_name': "Bank", - 'note': "", - 'amount': 1175.0, - 'amount_str': "$ 1,175.00", - 'amount_currency_str': "", - 'date': "2017-01-01", - 'account_code': "101401", - 'ref': "", - 'id': 5, - 'statement_id': 2, - 'company_id': 1, - }, - 'reconciliation_proposition': [] - }, - { - 'st_line': { - 'currency_id': 3, - 'communication_partner_name': false, - 'name': "Bank fees", - 'partner_name': false, - 'partner_id': false, - 'has_no_partner': true, - 'journal_id': 84, - 'account_name': "Bank", - 'note': "", - 'amount': -32.58, - 'amount_str': "$ 32.58", - 'amount_currency_str': "", - 'date': "2017-01-01", - 'account_code': "101401", - 'ref': "", - 'id': 6, - 'statement_id': 2, - 'company_id': 1, - }, - 'reconciliation_proposition': [] - }, - { - 'st_line': { - 'currency_id': 3, - 'communication_partner_name': false, - 'open_balance_account_id': 287, - 'name': "Prepayment", - 'partner_name': "Camptocamp", - 'partner_id': 12, - 'has_no_partner': false, - 'journal_id': 84, - 'account_name': "Bank", - 'note': "", - 'amount': 650.0, - 'amount_str': "$ 650.00", - 'amount_currency_str': "", - 'date': "2017-01-01", - 'account_code': "101401", - 'ref': "", - 'id': 7, - 'statement_id': 2, - 'company_id': 1, - }, - 'reconciliation_proposition': [ - { - 'account_type': "receivable", - 'amount_currency_str': "", - 'currency_id': false, - 'date_maturity': "2017-02-07", - 'date': "2017-01-08", - 'total_amount_str': "$ 650.00", - 'partner_id': 12, - 'account_name': "101200 Account Receivable", - 'name': "INV/2017/0012", - 'partner_name': "Camptocamp", - 'total_amount_currency_str': "", - 'id': 133, - 'credit': 0.0, - 'journal_id': [1, "Customer Invoices"], - 'amount_str': "$ 650.00", - 'debit': 650.0, - 'account_id': [287, "101200 Account Receivable"], - 'account_code': "101200", - 'ref': "", - 'already_paid': false - }, - ] - }, - { - 'st_line': { - 'currency_id': 3, - 'communication_partner_name': false, - 'open_balance_account_id': 285, - 'name': "First 2000 \u20ac of SAJ/2014/001", - 'partner_name': "Camptocamp", - 'partner_id': 12, - 'has_no_partner': false, - 'journal_id': 84, - 'account_name': "Bank", - 'note': "", - 'amount': 2000.0, - 'amount_str': "$ 2,000.00", - 'amount_currency_str': "", - 'date': "2017-01-01", - 'account_code': "101401", - 'ref': "", - 'id': 8, - 'statement_id': 2, - 'company_id': 1, - }, - 'reconciliation_proposition': [] - }, -]; - -var mv_lines = { - '[]': [], - '[5,"",0]': [ - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 109, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 112, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0012", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 134, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_id': [287, "101200 Account Receivable"], 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 4,610.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 106, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 4,610.00", 'debit': 4610.0, 'account_id': [287, "101200 Account Receivable"], 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_id': [284, "101110 Stock Valuation Account"], 'account_code': "111100", 'ref': "", 'already_paid': false} - ], - '[5,"b",0]': [ - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 100.00", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0003: CUST.IN/2017/0001", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 394, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101401", 'ref': "", 'already_paid': true}, - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 525.50", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0004: CUST.IN/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 396, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 525.50", 'debit': 525.5, 'account_code': "101401", 'ref': "INV/2017/0003", 'already_paid': true}, - ], - '[6,"",0]': [ - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 376.00", 'partner_id': 7, 'account_name': "Bank", 'name': "BNK1/2017/0002: SUPP.OUT/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 392, 'credit': 376.0, 'journal_id': "Bank", 'amount_str': "$ 376.00", 'debit': 0.0, 'account_code': "101401", 'ref': "BILL/2017/0003", 'already_paid': true}, - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 100.00", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0003: CUST.IN/2017/0001", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 394, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101401", 'ref': "", 'already_paid': true}, - {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 525.50", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0004: CUST.IN/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 396, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 525.50", 'debit': 525.5, 'account_code': "101401", 'ref': "INV/2017/0003", 'already_paid': true}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 109, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-22", 'date': "2017-01-23", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 399, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 4,610.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 106, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 4,610.00", 'debit': 4610.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-15", 'total_amount_str': "$ 5,749.99", 'partner_id': 7, 'account_name': "Account Payable", 'name': "BILL/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 117, 'credit': 5749.99, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 5,749.99", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false} - ], - '[6,"",5]': [ - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 4,610.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 106, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 4,610.00", 'debit': 4610.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-15", 'total_amount_str': "$ 5,749.99", 'partner_id': 7, 'account_name': "Account Payable", 'name': "BILL/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 117, 'credit': 5749.99, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 5,749.99", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false} - ], - '[7,"",0]': [ - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0012", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 133, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_id': [287, "101200 Account Receivable"], 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 4,610.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 106, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 4,610.00", 'debit': 4610.0, 'account_id': [287, "101200 Account Receivable"], 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "payable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-28", 'date': "2017-01-01", 'total_amount_str': "$ 10,000.00", 'partner_id': 12, 'account_name': "Account Payable", 'name': "BILL/2017/0001", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 114, 'credit': 10000.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 10,000.00", 'debit': 0.0, 'account_id': [284, "101110 Stock Valuation Account"], 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 376.00", 'partner_id': 7, 'account_name': "Bank", 'name': "BNK1/2017/0002: SUPP.OUT/2017/0002", 'partner_name': "ASUSTeK", 'total_amount_currency_str': "", 'id': 392, 'credit': 376.0, 'journal_id': "Bank", 'amount_str': "$ 376.00", 'debit': 0.0, 'account_code': "101401", 'ref': "BILL/2017/0003", 'already_paid': true}, - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 100.00", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0003: CUST.IN/2017/0001", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 394, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101401", 'ref': "", 'already_paid': true}, - {'account_type': "liquidity", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-01-23", 'date': "2017-01-23", 'total_amount_str': "$ 525.50", 'partner_id': 8, 'account_name': "Bank", 'name': "BNK1/2017/0004: CUST.IN/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 396, 'credit': 0.0, 'journal_id': "Bank", 'amount_str': "$ 525.50", 'debit': 525.5, 'account_code': "101401", 'ref': "INV/2017/0003", 'already_paid': true}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-07", 'date': "2017-01-08", 'total_amount_str': "$ 650.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0002", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 109, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 650.00", 'debit': 650.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-22", 'date': "2017-01-23", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 399, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - ], - '[8,"",0]': [], -}; - -var auto_reconciliation = { - 'num_already_reconciled_lines': 1, - 'notifications': [{ - 'message': "1 transaction was automatically reconciled.", - 'type': "info", - 'details': { - 'model': "account.move", - 'name': "Automatically reconciled items", - 'ids': [143] - } - }], - 'st_lines_ids': [5, 6, 8], - 'statement_name': false -}; - -var data_for_manual_reconciliation_widget = { - '[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]': { - 'customers': [ - {'account_id': 287, 'partner_name': "Agrolait", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-14 12:30:31", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'mode': "customers"}, - {'account_id': 7, 'partner_name': "Camptocamp", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-13 14:24:55", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'mode': "customers"} - ], - 'accounts': [ - { - 'account_id': 283, 'account_name': "101000 Current Assets", 'currency_id': 3, 'max_date': "2017-02-16 14:32:04", 'last_time_entries_checked': "2017-02-16", 'account_code': "101000", 'mode': "accounts", - 'reconciliation_proposition': [ - {'account_id': 283, 'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-16", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101000 Current Assets", 'name': "BNK1/2017/0006: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 399, 'credit': 1000.0, 'journal_id': [3, "Bank"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "101000", 'ref': "", 'already_paid': false}, - {'account_id': 283, 'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-03-18", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101000 Current Assets", 'name': "INV/2017/0006", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 402, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 1000.0, 'account_code': "101000", 'ref': "", 'already_paid': false} - ] - } - ], - 'suppliers': [ - { - 'account_id': 284, 'partner_name': "Agrolait", - 'reconciliation_proposition': [ - {'account_id': 284, 'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-16", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101000 Current Assets", 'name': "BNK1/999: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 999, 'credit': 1000.0, 'journal_id': [3, "Bank"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_id': 284, 'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-03-18", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101000 Current Assets", 'name': "INV/998", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 998, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 1000.0, 'account_code': "111100", 'ref': "", 'already_paid': false} - ], - 'currency_id': 3, 'max_date': "2017-02-14 12:36:05", 'last_time_entries_checked': null, 'account_code': "111100", 'partner_id': 8, 'account_name': "Account Payable", 'mode': "suppliers" - }, { - 'account_id': 284, 'partner_name': "Camptocamp", - 'reconciliation_proposition': [ - {'account_id': 284, 'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-16", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 12, 'account_name': "101000 Current Assets", 'name': "BNK1/1999: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 1999, 'credit': 1000.0, 'journal_id': [3, "Bank"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_id': 284, 'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-03-18", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 12, 'account_name': "101000 Current Assets", 'name': "INV/1998", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 1998, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 1000.0, 'account_code': "111100", 'ref': "", 'already_paid': false} - ], - 'currency_id': 3, 'max_date': "2017-02-14 12:36:05", 'last_time_entries_checked': null, 'account_code': "111100", 'partner_id': 12, 'account_name': "Account Payable", 'mode': "suppliers" - } - ] - }, - '["partner",null,"receivable"]': [ - {'account_id': 287, 'partner_name': "Agrolait", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-14 12:30:31", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'mode': "customers"}, - {'account_id': 287, 'partner_name': "Camptocamp", 'reconciliation_proposition': [], 'currency_id': 3, 'max_date': "2017-02-13 14:24:55", 'last_time_entries_checked': null, 'account_code': "101200", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'mode': "customers"} - ] -}; - -var move_lines_for_manual_reconciliation = { - '[287,8,"",0]': [ - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "10,222.00 €", 'currency_id': 1, 'date_maturity': "2017-02-08", 'date': "2017-02-08", 'total_amount_str': "$ 11,000.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 17, 'credit': 11000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 11,000.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [7, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0005: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 19, 'credit': 1000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 180.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 21, 'credit': 180.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 180.00", 'debit': 0.0, 'account_code': "101200", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 90.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0006: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 23, 'credit': 90.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 90.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-03-10", 'date': "2017-02-08", 'total_amount_str': "$ 650.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0012", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 6, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1000.00", 'debit': 1000.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-03-10", 'date': "2017-02-08", 'total_amount_str': "$ 525.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 9, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 525.00", 'debit': 525.0, 'account_code': "101200", 'ref': "", 'already_paid': false} - ], - '[7,12,"",0]': [ - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-08", 'date': "2017-02-08", 'total_amount_str': "$ 11,000.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 17, 'credit': 11000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 11,000.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [7, "101200 Account Receivable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 1,000.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0005: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 19, 'credit': 1000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "101200", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 100, 'amount_currency_str': "100.00 €", 'currency_id': 1, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 170.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 21, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 170.00", 'debit': 170.0, 'account_code': "101200", 'ref': "INV fddfgfdgfdgsdfg", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 100, 'amount_currency_str': "100.00 €", 'currency_id': 1, 'date_maturity': "2017-02-10", 'date': "2017-02-10", 'total_amount_str': "$ 180.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 22, 'credit': 180.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 180.00", 'debit': 0.0, 'account_code': "101200", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 170, 'amount_currency_str': "170.00 €", 'currency_id': 1, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 100.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 23, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 100.00", 'debit': 100.0, 'account_code': "101200", 'ref': "INV fddfgfdgfdgsdfg", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [287, "101200 Account Receivable"], 'amount_currency': 180, 'amount_currency_str': "180.00 €", 'currency_id': 1, 'date_maturity': "2017-02-10", 'date': "2017-02-10", 'total_amount_str': "$ 100.00", 'partner_id': 12, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Camptocamp", 'total_amount_currency_str': "", 'id': 24, 'credit': 100.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 100.00", 'debit': 0.0, 'account_code': "101200", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, - ], - '[284,8,"",0]': [ - {'account_type': "receivable", 'account_id': [284, "111100 Account Payable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-08", 'date': "2017-02-08", 'total_amount_str': "$ 11,000.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0004: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 17, 'credit': 11000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 11,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [284, "111100 Account Payable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "INV/2017/0005: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 19, 'credit': 1000.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "111100", 'ref': "", 'already_paid': false}, - {'account_type': "receivable", 'account_id': [284, "111100 Account Payable"], 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-09", 'date': "2017-02-09", 'total_amount_str': "$ 180.00", 'partner_id': 8, 'account_name': "101200 Account Receivable", 'name': "BILL/2017/0003: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 21, 'credit': 180.0, 'journal_id': [2, "Vendor Bills"], 'amount_str': "$ 180.00", 'debit': 0.0, 'account_code': "111100", 'ref': "fddfgfdgfdgsdfg", 'already_paid': false}, - ], - '[283,null,"",0]': [ - {'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-02-16", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101000 Current Assets", 'name': "BNK1/2017/0006: Customer Payment", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 399, 'credit': 1000.0, 'journal_id': [3, "Bank"], 'amount_str': "$ 1,000.00", 'debit': 0.0, 'account_code': "101000", 'ref': "", 'already_paid': false}, - {'account_type': "other", 'amount_currency_str': "", 'currency_id': false, 'date_maturity': "2017-03-18", 'date': "2017-02-16", 'total_amount_str': "$ 1,000.00", 'partner_id': 8, 'account_name': "101000 Current Assets", 'name': "INV/2017/0006", 'partner_name': "Agrolait", 'total_amount_currency_str': "", 'id': 402, 'credit': 0.0, 'journal_id': [1, "Customer Invoices"], 'amount_str': "$ 1,000.00", 'debit': 1000.0, 'account_code': "101000", 'ref': "", 'already_paid': false} - ], - '[284,12,"",0]': [], -}; - -var session = { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } -}; - -var options = { - context: { - statement_line_ids: [4] - }, - params: { - limitMoveLines: 5, - }, -}; - -Datas.params = { - data: db, - data_preprocess: data_preprocess, - data_widget: data_widget, - mv_lines: mv_lines, - auto_reconciliation: auto_reconciliation, - data_for_manual_reconciliation_widget: data_for_manual_reconciliation_widget, - move_lines_for_manual_reconciliation: move_lines_for_manual_reconciliation, - session: session, - options: options, -}; - // this is the main function for this module. Its job is to export (and clone) all data for a test. -Datas.getParams = function () { - return (this.used = $.extend(true, {}, this.params)); -}; -return Datas; -}); - -odoo.define('account.reconciliation_tests', function (require) { -"use strict"; - -var ReconciliationClientAction = require('account.ReconciliationClientAction'); -var ReconciliationRenderer = require('account.ReconciliationRenderer'); -var demoData = require('account.reconciliation_tests.data'); - -var testUtils = require('web.test_utils'); -var testUtilsDom = require('web.test_utils_dom'); -var testUtilsMock = require('web.test_utils_mock'); - -QUnit.module('account', { - beforeEach: function () { - this.params = demoData.getParams(); - testUtils.patch(ReconciliationRenderer.LineRenderer, { - MV_LINE_DEBOUNCE: 0, - }); - }, - afterEach: function () { - testUtils.unpatch(ReconciliationRenderer.LineRenderer); - }, -}, function () { - QUnit.module('Reconciliation'); - - QUnit.test('Reconciliation basic rendering', async function (assert) { - assert.expect(10); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - var widget = clientAction.widgets[0]; - - assert.hasClass(widget.$el,'o_reconciliation_line', "should instance of widget reconciliation"); - assert.containsOnce(widget, '.accounting_view', "should have one view"); - assert.containsN(widget, '[id*="notebook_page_match"]', 2, "should have 'match_rp' and 'match_other' panel"); - assert.containsOnce(widget, '.create', "should have 'create' panel"); - - assert.strictEqual(widget.$('thead').text().replace(/[\n\r\s]+/g, ' '), " 101401 2017-01-01 SAJ/2014/002 and SAJ/2014/003 $ 1,175.00 ", "should display the line information"); - assert.ok(widget.$('caption .o_field_many2one').length, "should display the many2one with to select a partner"); - - assert.containsN(clientAction, '[data-mode="inactive"]', 3, "should be as 'inactive' mode by default"); - assert.strictEqual(widget.$el.data('mode'), 'match_rp', "the first one should automatically switch to match_rp mode"); - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - assert.strictEqual(widget.$el.data('mode'), 'create', "should switch to 'create' mode"); - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_match_rp"]')); - assert.strictEqual(widget.$el.data('mode'), 'match_rp', "should switch to 'match_rp' mode"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation fields', async function (assert) { - assert.expect(6); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); - assert.strictEqual(clientAction.widgets[2].$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp"); - await testUtils.dom.click(widget.$('.accounting_view tfoot td:first')); - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - assert.containsN(widget, '.create input.o_input', 8, - "create panel should contain 8 fields (account_id, tax_id, journal_id, analytic_account_id, analytic_tag_ids, label, amount, date)"); - assert.containsN(widget, '.create .create_account_id .o_required_modifier, .create .create_label .o_required_modifier, .create .create_amount .o_required_modifier', 3, - "account_id, label and amount should be required fields"); - assert.strictEqual(widget.$('.create .create_label input').val(), 'SAJ/2014/002 and SAJ/2014/003', - "should use the name of the reconciliation line for the default label value"); - assert.strictEqual(widget.$('.create .create_amount input').val(), '1175.00', - "should have the balance amout as default value for the amout field"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation basic data', async function (assert) { - assert.expect(17); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - var widget = clientAction.widgets[0]; - - assert.containsN(widget, '.match:first .mv_line', 2, "should display 2 account move lines"); - assert.strictEqual(widget.$('.match:first .mv_line').text().replace(/[\n\r\s]+/g, ' ').replace(/[\u200B]/g, ''), - " 101200 2017-02-07 INV/2017/0002 $ 650.00 101200 2017-02-07 INV/2017/0003 $ 525.00 ", - "should display 4 account move lines who contains the account_code, due_date, label and the credit"); - assert.strictEqual(widget.$('.match:first .mv_line .cell_right:contains(".")').length, 2, "should display only the credit account move lines (hide the debit)"); - - await testUtils.dom.click(clientAction.widgets[1].$('.accounting_view thead td:first')); - assert.containsN(clientAction.widgets[1], '.mv_line', 5, "should display 5 account move lines"); - assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_right:contains(".")').length, 3, "should display only the credit account move lines (hide the debit)"); - assert.containsN(clientAction.widgets[1], '.mv_line.already_reconciled', 3, "should display 3 already reconciled account move lines"); - assert.strictEqual(clientAction.widgets[1].$('.mv_line').text().replace(/[\n\r\s]+/g, ' ').replace(/[\u200B]/g, ''), - " 101401 2017-01-23 ASUSTeK: BNK1/2017/0002: SUPP.OUT/2017/0002 : BILL/2017/0003 $ 376.00 101401 2017-01-23 Agrolait: BNK1/2017/0003: CUST.IN/2017/0001 $ 100.00 111100 2017-02-28 Camptocamp: BILL/2017/0001 $ 10,000.00 101401 2017-01-23 Agrolait: BNK1/2017/0004: CUST.IN/2017/0002 : INV/2017/0003 $ 525.50 101200 2017-02-07 Agrolait: INV/2017/0002 $ 650.00 ", - "should display 4 account move lines who contains the account_code, due_date, label and the credit"); - assert.strictEqual(clientAction.widgets[1].$('.mv_line .cell_left:contains(".")').length, 2, "should display only 2 debit account move lines"); - - // load more - assert.ok(clientAction.widgets[1].$('.match:first div.load-more a:visible').length, "should display the 'load more' button"); - assert.equal(clientAction.widgets[1].$('.match:first div.load-more span').text(), 3, "should display 3 items remaining"); - await testUtils.dom.click(clientAction.widgets[1].$('.match:first div.load-more a')); - assert.containsN(clientAction.widgets[1], '.mv_line', 8, "should load 3 more records"); - assert.notOk(clientAction.widgets[1].$('.match:first div.load-more a:visible').length, "should not display the 'load more' button anymore"); - - assert.ok(clientAction.widgets[0].$('caption button.btn-secondary:visible').length, "should display the secondary 'Validate' button"); - assert.equal(clientAction.widgets[1].$('caption button:disabled:visible').length, 1,"button should be disabled"); - assert.ok(clientAction.widgets[2].$('caption button.btn-primary:visible').length, "should display the primary 'Validate' button"); - - await testUtils.dom.click(clientAction.widgets[3].$('.accounting_view thead td:first')); - assert.strictEqual(clientAction.widgets[3].$el.data('mode'), 'create', "should switch to 'create' mode instead of 'match_rp' mode when 'match_rp' mode is empty"); - - // open the first line - await testUtils.dom.click(widget.$('.accounting_view thead td:first')); - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_match_rp"]')); - // select propositions - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - - // await testUtils.dom.click(widget.$('caption')); //why is it inactive? - - testUtils.mock.intercept(clientAction, 'call_service', function (event) { - if (event.data.args[1].method == 'process_bank_statement_line') { - assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, counterpart_aml_dicts: [{ - "counterpart_aml_id": 109, - "credit": 650, - "debit": 0, - "name": "INV/2017/0002", - "analytic_tag_ids": [[6, null, []]] - }, - { - "counterpart_aml_id": 112, - "credit": 525, - "debit": 0, - "name": "INV/2017/0003", - "analytic_tag_ids": [[6, null, []]] - }], - payment_aml_ids: [], new_aml_dicts: [], to_check: false}]], - "Should call process_bank_statement_line with args"); - var def = testUtils.makeTestPromise(); - def.abort = function () {}; - event.data.callback(def); - } - }); - - // click on reconcile button - await testUtils.dom.click(widget.$('.o_reconcile:visible')); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation validate without proposition', async function (assert) { - assert.expect(1); - // Test added to prevent this issue happening again: https://github.com/odoo/odoo/commit/3549688b21eb65e16b9c3f2b6462eb8d8b52cd47 - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - // Ensure that when we validate a line without any selection, it is the same - // as when we manually create a line with the line.balance and that only one - // line is send back to server. - testUtils.mock.intercept(clientAction, 'call_service', function (event) { - assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, to_check: false, counterpart_aml_dicts: [], - payment_aml_ids: [], - to_check: false, - new_aml_dicts: [{ - account_id: 287, - credit: 1175, - debit: 0, - name: 'SAJ/2014/002 and SAJ/2014/003', - analytic_tag_ids: [[6, null, []]] - }]}]], - "Should call process_bank_statement_line with ids"); - var def = testUtils.makeTestPromise(); - def.abort = function () {}; - event.data.callback(def); - }); - - // click on validate button - await testUtils.dom.click(widget.$('button.o_validate:not(:hidden)')); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation validate with proposition', async function (assert) { - assert.expect(1); - // Test added to check this functionality: https://github.com/odoo/odoo/commit/2f3b469dee6f18cbccce1cdf2a81cfe57960c533 - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - var widget = clientAction.widgets[0]; - // Add a line as proposition - // open the first line - await testUtils.dom.click(widget.$('.accounting_view thead td:first'),{allowInvisible:true}); - await testUtils.nextTick(); - // select propositions - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first'),{allowInvisible:true}); - await testUtils.nextTick(); - - // Ensure that when we validate a line with propositions and that there is a remaining balance - // We also create a line which is the open balance. - testUtils.mock.intercept(clientAction, 'call_service', function (event) { - assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, to_check: false, - counterpart_aml_dicts: [{ - counterpart_aml_id: 109, - credit: 650, - debit: 0, - name: 'INV/2017/0002', - analytic_tag_ids: [[6, null, []]] - }], - payment_aml_ids: [], - new_aml_dicts: [{ - account_id: 287, - credit: 525, - debit: 0, - name: 'SAJ/2014/002 and SAJ/2014/003 : Open balance' - }]}]], - "Should call process_bank_statement_line with ids"); - var def = testUtils.makeTestPromise(); - def.abort = function () {}; - event.data.callback(def); - }); - - // click on validate button - await testUtils.dom.click(widget.$('button.o_validate:not(:hidden)')); - clientAction.destroy(); - }); - - QUnit.test('Reconciliation partial [REQUIRE FOCUS]', async function (assert) { - assert.expect(8); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: function (route, args) { - console.log(args.method); - if (args.method === 'process_bank_statement_line') { - var lines = args.args['1']; - console.log(args.arsg); - assert.deepEqual(args.args, [ - [6], - [{ - partner_id: lines.length == 1 ? lines[0].partner_id : false, - counterpart_aml_dicts:[{ - "analytic_tag_ids": [[6, null, []]], - "counterpart_aml_id": 114, - "credit": 0, - "debit": 32.58, - "name": "BILL/2017/0001" - }], - payment_aml_ids: [], - new_aml_dicts: [], - to_check: false, - }] - ], "should call process_bank_statement_line with partial reconcile values"); - } - return this._super(route, args); - }, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - var widget = clientAction.widgets[0]; - - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.equal( widget.$('.cell_right .edit_amount').length, 1, "should display the edition pencil"); - - widget = clientAction.widgets[1]; - - await testUtils.dom.click(widget.$('.accounting_view thead td:first')); - assert.strictEqual(widget.$('.match:first .mv_line[data-line-id=114] .cell_account_code:first()').length, 1, 'Should have line') - await testUtils.dom.click(widget.$('.match:first .mv_line[data-line-id=114] .cell_account_code')); - - assert.equal( widget.$('.accounting_view tbody .cell_left .edit_amount').length, 1, "should display the edition pencil"); - - // The partner has been set automatically, remove it. - await testUtils.dom.click(widget.$('.o_input_dropdown input')); - await testUtils.fields.editAndTrigger(widget.$('.o_input_dropdown input'),'',['keyup','blur']); - - assert.equal(clientAction.widgets[1].$('caption button:disabled:visible').length, 1,"button should be disabled"); - await testUtils.dom.click(widget.$('.accounting_view .cell_left .edit_amount')); - assert.strictEqual(widget.$('.accounting_view .cell_left .edit_amount_input:not(.d-none)').length, 1, "should display the input field to edit amount"); - // Edit amount - await testUtils.fields.editAndTrigger(widget.$('.accounting_view .cell_left .edit_amount_input:not(.d-none)'),'32.58',['change','blur']); - assert.strictEqual(widget.$('.accounting_view .cell_left .line_amount').text().replace(/[\n\r\s]+/g, ' '), " $ 10000.00 $ 32.58 ", "should display previous amount and new amount") - - assert.strictEqual(widget.$('button.btn-primary:visible').length, 1, "should display the reconcile button"); - await testUtils.dom.click(widget.$('button.btn-primary:visible')); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation currencies', async function (assert) { - assert.expect(2); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: this.params.session, - translateParameters: { - date_format: "%m/%d/%Y", - direction:"ltr", - name:"English", - thousands_sep: ",", - time_format: "%H:%M:%S", - decimal_point: ".", - id:1, - grouping: [3,0], - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - - assert.strictEqual(clientAction.$('.accounting_view tfoot .cell_right, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s]+/g, ' '), - "$ 1,175.00$ 32.58$ 2,000.00", "should display the different amounts with the currency"); - // await testUtils.dom.click(widget.$('.accounting_view thead .mv_line td:first')); - - assert.strictEqual(clientAction.$('.accounting_view tbody').text().replace(/[\n\r\s]+/g, ' ').replace(/[\u200B]/g, ''), - " 101200 2017-02-07 INV/2017/0012 $ 650.00 ", "should display the created reconciliation line with the currency"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation change partner', async function (assert) { - assert.expect(17); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - archs: { - 'res.partner,false,list': '<tree string="Partners"><field name="display_name"/></tree>', - 'res.partner,false,search': '<search string="Partners">' + - '<field name="display_name" string="Name"/>' + - '</search>', - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - var widget = clientAction.widgets[0]; - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Agrolait", "the partner many2one should display agrolait"); - assert.containsN(widget, '.match:first table tr', 2, "agrolait should have 2 propositions for reconciliation"); - - // Adding the two propositions - // This is in order to try that after changing partner the propositions are emptied - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.containsN(widget, '.accounting_view tbody tr', 2, "Both proposition should be selected"); - - // Similate changing partner to one that does not have propositions to see if create mode is open after - await testUtils.dom.click(widget.$('.o_input_dropdown input')); - $('.ui-autocomplete .ui-menu-item a:contains(partner 1)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - clientAction._onAction({target: widget, name: 'change_partner', data: {data: {display_name: 'partner 1', id: 1}}, stopped: false}); - await testUtils.nextTick(); - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "partner 1", "the partner many2one should display partner 1"); - assert.containsNone(widget, '.match:first table tr.mv_line', "partner 1 should have 0 propositions for reconciliation"); - assert.strictEqual(widget.$el.data('mode'), 'create', "widget should be in create mode"); - - // Simulate changing partner - await testUtils.dom.clickFirst(widget.$('.o_input_dropdown input')); - $('.ui-autocomplete .ui-menu-item a:contains(Camptocamp)').trigger('mouseenter').trigger('click'); - clientAction._onAction({target: widget, name: 'change_partner', data: {data: {display_name: 'Camptocamp', id: 12}}, stopped: false}); - await testUtils.nextTick(); - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp"); - assert.containsN(widget, '.match:first table tr.mv_line', 3, "camptocamp should have 3 propositions for reconciliation"); - - // Simulate changing partner with SelectCreateDialog - widget = clientAction.widgets[1]; - assert.strictEqual($('.modal').length, 0, "shouldn't have any opened modal"); - await testUtils.dom.click(widget.$('.o_input_dropdown input')); - $('.ui-autocomplete .ui-menu-item a:contains(Search More):eq(1)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - assert.strictEqual($('.modal').length, 1, "should open a SelectCreateDialog"); - await testUtils.dom.click($('.modal table.o_list_table td:contains(Camptocamp)')); - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display Camptocamp"); - - widget = clientAction.widgets[2]; - await testUtils.dom.click(widget.$('.accounting_view thead td:first')); - await testUtils.dom.click(widget.$('.accounting_view .mv_line .cell_label')); - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "Camptocamp", "the partner many2one should display agrolait"); - assert.containsN(widget, '.match:first table tr', 3, "Camptocamp should have 3 propositions for reconciliation"); - assert.notOk(widget.$('.match:first div.load-more a:visible').length, "should not display the load more button"); - - // Simulate remove partner - await testUtils.dom.click(widget.$('.o_input_dropdown input')); - await testUtils.fields.editAndTrigger(widget.$('.o_input_dropdown input'),'',['keyup','blur']); - - assert.strictEqual(widget.$('.o_input_dropdown input').val(), "", "the partner many2one should be empty"); - assert.containsN(widget, '.match:first table tr.mv_line', 5, "should have 5 propositions for reconciliation if partner is false"); - assert.ok(widget.$('.match:first div.load-more a:visible').length, "should display the load more button"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation create line', async function (assert) { - assert.expect(23); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - assert.strictEqual(clientAction.$('.accounting_view tfoot .cell_right, .accounting_view tfoot .cell_left').text().replace(/[$, ]+/g, ''), " 1175.00 32.58 2000.00", "should display the open balance values"); - - var widget = clientAction.widgets[0]; - - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), 'Open balance', "should display 'Open Balance' line with the rest to reconcile"); - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - await testUtils.dom.click(widget.$('.create .create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(101200 Account Receivable)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - - assert.notOk(widget.$('.accounting_view tfoot .cell_label').text(), "should not display 'Open Balance' line because the rest to reconcile is null"); - assert.containsOnce(widget, '.accounting_view tbody tr', "should have only the created reconcile line"); - assert.strictEqual(widget.$('.accounting_view tbody tr').text().replace(/[\n\r\s$,]+/g, ' ').replace(/[\u200B]/g, ''), " 101200 New SAJ/2014/002 and SAJ/2014/003 1175.00 ", - "the new line should have the selected account, name and amout"); - assert.ok(widget.$('caption button.btn-primary:visible').length, "should display the 'Reconcile' button"); - - testUtils.mock.intercept(clientAction, 'do_action', function (event) { - assert.strictEqual(JSON.stringify(event.data.action), - '{"type":"ir.actions.act_window","res_model":"account.reconcile.model","views":[[false,"form"]],"target":"current"}', - "should open the reconcile model form view"); - }); - await testUtils.dom.click(widget.$('.create .reconcile_model_create'),{allowInvisible:true}); - - testUtils.mock.intercept(clientAction, 'do_action', function (event) { - assert.strictEqual(JSON.stringify(event.data.action), - '{"type":"ir.actions.act_window","res_model":"account.reconcile.model","views":[[false,"list"],[false,"form"]],"view_mode":"list","target":"current"}', - "should open the reconcile model list view"); - }); - await testUtils.dom.click(widget.$('.create .reconcile_model_edit'),{allowInvisible:true}); - - await testUtils.fields.editInput(widget.$('.create .create_amount input'), '1100.00'); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1100.00", "should display the value 1100.00 in right column"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$ 75.00", "should display 'Open Balance' line because the rest to reconcile is 75.00"); - assert.containsOnce(widget, '.accounting_view tbody tr', "should have ever only the created reconcile line"); - assert.strictEqual(widget.$('.accounting_view tbody tr').text().replace(/[\n\r\s$,]+/g, ' ').replace(/[\u200B]/g, ''), " 101200 New SAJ/2014/002 and SAJ/2014/003 1100.00 ", - "the new line should be update the amout"); - assert.ok(widget.$('caption button.btn-secondary:visible').length, "should display the 'validate' button"); - - await testUtils.dom.click(widget.$('.create .add_line'),{allowInvisible:true}); - await testUtils.fields.editInput(widget.$('.create .create_amount input'), '-100'); - await testUtils.dom.click(widget.$('.create .create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - await testUtils.fields.editInput(widget.$('.create .create_label input'), 'test0'); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_left:last').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 100.00", "should display the value 100.00 in left column"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$ 175.00", "should display 'Open Balance' line because the rest to reconcile is 175.00"); - assert.containsN(widget, '.accounting_view tbody tr', 2, "should have 2 created reconcile lines"); - assert.strictEqual(widget.$('.accounting_view tbody tr:eq(1)').text().replace(/[\n\r\s$,]+/g, ' ').replace(/[\u200B]/g, ''), " 101000 New test0 100.00 ", - "the new line should have the selected account, name and amout"); - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - - assert.strictEqual(widget.$('.create .create_amount input').val(), "175.00", "should have '175.00' as default amount value"); - - await testUtils.fields.editInput(widget.$('.create .create_amount input'), '200'); - widget.$('.create .create_account_id input').trigger('click'); - $('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - await testUtils.fields.editInput(widget.$('.create .create_label input'), 'test1'); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 200.00", "should display the value 200.00 in left column"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$ 25.00", "should display 'Open balance' with 25.00 in left column"); - assert.containsN(widget, '.accounting_view tbody tr', 3, "should have 3 created reconcile lines"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation create line (many2one test)', async function (assert) { - assert.expect(5); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - var def = testUtils.makeTestPromise(); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - "account.account,false,list": '<tree string="Account"><field name="code"/><field name="name"/></tree>', - "account.account,false,search": '<search string="Account"><field name="code"/></search>', - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - mockRPC: function (route, args) { - if (args.method === 'name_get') { - return def.then(this._super.bind(this, route, args)); - } - return this._super(route, args); - }, - }); - - await clientAction.prependTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - - // open the first line in write-off mode - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - - // select an account with the many2one (drop down) - await testUtils.dom.click(widget.$('.create .create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(101200)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Display the selected account"); - assert.strictEqual(widget.$('tbody:first .cell_account_code').text().replace(/[\u200B]/g, ''), "101200", "Display the code of the selected account"); - - // use the many2one select dialog to change the account - await testUtils.dom.click(widget.$('.create .create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(Search)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - // select the account who does not appear in the drop drown - await testUtils.dom.click($('.modal tr.o_data_row:contains(502)')); - assert.strictEqual(widget.$('.create .create_account_id input').val(), "101200 Account Receivable", "Selected account does not change"); - // wait the name_get to render the changes - def.resolve(); - await testUtils.nextTick(); - assert.strictEqual(widget.$('.create .create_account_id input').val(), "502 Account", "Display the selected account"); - assert.strictEqual(widget.$('tbody:first .cell_account_code').text().replace(/[\u200B]/g, ''), "502", "Display the code of the selected account"); - clientAction.destroy(); - }); - - QUnit.test('Reconciliation create line with taxes', async function (assert) { - assert.expect(13); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - await testUtils.dom.click(widget.$('.create .create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - await testUtils.fields.editInput(widget.$('.create .create_label input'), 'test1'); - await testUtils.fields.editInput(widget.$('.create .create_amount input'), '1100'); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_right:last').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1100.00", "should display the value 1100.00 in left column"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column"); - assert.containsOnce(widget, '.accounting_view tbody tr', "should have 1 created reconcile lines"); - - await testUtils.dom.click(widget.$('.create .create_tax_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(10.00%)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1000.00 $ 100.00", "should have 2 created reconcile lines with right column values"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open Balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_right').text(), "$\u00a075.00", "should display 'Open Balance' with 75.00 in right column"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "", "should display 'Open Balance' without any value in left column"); - assert.containsN(widget, '.accounting_view tbody tr', 2, "should have 2 created reconcile lines"); - await testUtils.dom.click(widget.$('[name="tax_ids"] a.o_delete')); - widget.$('.create .create_tax_id input').val('').trigger('keyup').trigger('blur'); - await testUtils.dom.click(widget.$('.create .create_tax_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(20.00%)').trigger('mouseenter').trigger('click'); - await testUtils.nextTick(); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), "$ 1100.00 $ 220.00", "should have 2 created reconcile lines with right column values"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label').text(), "Open balance", "should display 'Open balance'"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_left').text(), "$\u00a0145.00", "should display 'Open balance' with 145.00 in right column"); - assert.containsN(widget, '.accounting_view tbody tr', 2, "should have 2 created reconcile lines"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation create line from reconciliation model', async function (assert) { - assert.expect(6); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - await testUtils.dom.click(widget.$('.create .quick_add button:contains(ATOS)')); - - assert.strictEqual(widget.$('.accounting_view tbody .cell_label, .accounting_view tbody .cell_right').text().replace(/[\n\r\s$,]+/g, ' '), - " ATOS Banque 1145.63 ATOS Banque Tax 20.00% 229.13 ATOS Frais 26.78 ATOS Frais Tax 10.00% include 2.68 ", "should display 4 lines"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ''), - "Openbalance229.22", "should display the 'Open balance' line with value in left column"); - - await testUtils.fields.editAndTrigger(widget.$('.create .create_amount input'), '100',['input']); - - assert.strictEqual(widget.$('.accounting_view tbody').text().replace(/[\n\r\s$,]+/g, ' ').replace(/[\u200B]/g, ''), - " 101120 New ATOS Banque 1145.63 101120 New ATOS Banque Tax 20.00% 229.13 101130 New ATOS Frais 90.91 101300 New ATOS Frais Tax 10.00% include 9.09 ", - "should update the value of the 2 lines (because the line + its tax must have 100% of the value)"); - assert.strictEqual(widget.$('.accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left').text().replace(/[\n\r\s$,]+/g, ''), - "Openbalance299.76", "should change the 'Open balance' line because the 20.00% tax is not an include tax"); - - await testUtils.dom.click(widget.$('.accounting_view tbody .cell_account_code:first')); - await testUtils.dom.click(widget.$('.accounting_view tbody .cell_label:first')); - - assert.strictEqual(widget.$('.accounting_view tbody').text().replace(/[\n\r\s$,]+/g, ' '), "", "should removed every line"); - - await testUtils.dom.click(widget.$('.create .quick_add button:contains(Double)')); - - assert.strictEqual(widget.$('.accounting_view tbody').text().replace(/[\n\r\s$,]+/g, ' ').replace(/[\u200B]/g, ''), - " 101120 New Double Banque 1145.63 101130 New Double Frais 29.37 ", - "should have a sum of reconciliation proposition amounts equal to the line amount"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation fetch correct reconciliation models', async function (assert) { - assert.expect(1); - - testUtilsMock.patch(this.params.options.context, { - active_model: 'account.journal', // On account dashboard, click "Reconcile" on a journal - active_ids: [1,2], // Active journals - company_ids: [3,4], // Active companies - }); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: async function (route, args) { - if (args.model === 'account.reconcile.model' && args.method === 'search_read') { - assert.deepEqual( - args.kwargs.domain, - [['company_id', 'in', [3,4]], ['match_journal_ids', 'in', [false, 1, 2]]], - 'The domain to get reconcile models should contain the right fields and values' - ); - } - return this._super.apply(this, arguments); - } - }); - clientAction.appendTo($('#qunit-fixture')); - testUtilsMock.unpatch(this.params.options.context); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation manual', async function (assert) { - assert.expect(13); - - var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: this.params.session, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - assert.strictEqual(clientAction.$('.accounting_view:first thead').text().replace(/[\n\r\s]+/g, ' '), - " 101000 Current AssetsLast Reconciliation: 2017-02-16 101000 ", - "should display the account as title"); - assert.strictEqual(clientAction.$('.o_reconciliation_line:first').data('mode'), "inactive", "should be in 'inactive' mode because no line to displayed and the balance amount is null"); - assert.containsN(clientAction, '.accounting_view:first tbody tr', 2, "should have 2 propositions"); - assert.containsOnce(clientAction, '.accounting_view:first .o_reconcile:visible', "should display the reconcile button"); - - await testUtils.dom.click(clientAction.$('.accounting_view:first .o_reconcile:visible')); - - assert.strictEqual(clientAction.$('.accounting_view:first thead').text().replace(/[\n\r\s]+/g, ' '), - " 101200 Account Receivable 101200 ", - "should display the account and the account code as title"); - - assert.strictEqual(clientAction.$('.o_reconciliation_line:first .match:first tr:first .cell_right').text().trim().replace(/[\n\r\s\u00a0]+/g, ' '), - "$ 11,000.00", "sould display the line in $"); - assert.strictEqual(clientAction.$('.o_reconciliation_line:first .match:first tr:first .cell_right .o_multi_currency').data('content'), - "10,222.00 €", "sould display the monetary information in €"); - - assert.containsOnce(clientAction, '.accounting_view:first .o_no_valid:visible', "should display the skip button"); - - await testUtils.dom.click(clientAction.$('.o_reconciliation_line:eq(1) .accounting_view')) - await testUtils.dom.click(clientAction.$('.accounting_view:eq(1) thead td:first')); - await testUtils.dom.click(clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="21"] .cell_label')); - await testUtils.dom.click(clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="22"] .cell_label')); - - assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) tfoot tr').length, 0, "should not display the 'Write-off' line because the balance is null in Euro"); - assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) .o_reconcile:visible').length, 1, "should display 'Reconcile' button in green"); - - await testUtils.dom.click(clientAction.$('.o_reconciliation_line:eq(1) .o_reconcile:visible')); - - assert.containsOnce(clientAction, '.o_reconciliation_line[data-mode!="inactive"]', "should have only one line open"); - - await testUtils.dom.click(clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="23"] .cell_label')); - await testUtils.dom.click(clientAction.$('.o_reconciliation_line:eq(1) [data-line-id="24"] .cell_label')); - - assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) tfoot tr').length, 1, "should display the 'Write-off' line because the balance is not null in Euro"); - assert.strictEqual(clientAction.$('.o_reconciliation_line:eq(1) .o_validate:visible').length, 1, "should display 'Reconcile' button"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation: Payment < inv1 + inv2(partial)', async function (assert) { - assert.expect(3); - - /* - * One payment: $1175 - * Two Invoices - * The first invoice will be fully reconciled $650 - * The second invoice will be partially paid with the rest of the payment $999 - */ - - // modify the second line that is already in db to put it at $999 - var indexModif = _.findIndex(this.params.mv_lines['[5,"",0]'], function (line) {return line.id === 112}); - this.params.mv_lines['[5,"",0]'][indexModif] = - {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", - total_amount_str: "$ 999.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0003", - partner_name: "Agrolait", total_amount_currency_str: "", id: 112, credit: 0.0, journal_id: [1, "Customer Invoices"], - amount_str: "$ 999.00", debit: 999.0, account_code: "101200", ref: "", already_paid: false}; - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: function (route, args) { - if (args.method === 'process_bank_statement_line') { - assert.deepEqual(args.args, - [ - [5], // Id of the bank statement line - - [{counterpart_aml_dicts: - [{name:"INV/2017/0002", - debit: 0, - credit: 650, - analytic_tag_ids: [[6, null, []]], - counterpart_aml_id: 109}, - - {name: "INV/2017/0003", - debit: 0, - credit: 525, - analytic_tag_ids: [[6, null, []]], - counterpart_aml_id: 112}], - - payment_aml_ids: [], - partner_id: 8, - to_check: false, - new_aml_dicts: []}] - ], "should call process_bank_statement_line with partial reconcile values"); - } - return this._super(route, args); - }, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - // The first reconciliation "line" is where it happens - var widget = clientAction.widgets[0]; - - // Add first invoice to reconcile fully - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.ok( widget.$('.cell_right .edit_amount').length, - "should display the pencil to edit amount"); - - // Add second invoice to reconcile partially - await testUtils.dom.click( widget.$('.match:first .cell_account_code:first')); - - // Edit amount on last invoice - await testUtils.dom.click(widget.$('.edit_amount:last()')); - await testUtils.fields.editAndTrigger(widget.$('.edit_amount_input:last()'),'525',['blur']); - - var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); - - assert.equal($buttonReconcile.length, 1, - 'The reconcile button must be visible'); - - await testUtils.dom.click($buttonReconcile); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation: payment and 2 partials', async function (assert) { - assert.expect(6); - - /* - * One payment: $1175 - * Two Invoices as Inv1 = 1200; Inv2 = 1200: - * Payment < Inv1 AND Payment < Inv2 - * No partial reconcile is possible, as a write-off of 1225 is necessary - */ - - // modify the invoice line to have their amount > payment - var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0]'], function (line) {return line.id === 109}); - this.params.mv_lines['[5,"",0]'][indexInv1] = - {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", - total_amount_str: "$ 1200.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0002", partner_name: "Agrolait", - total_amount_currency_str: "", id: 109, credit: 0.0, journal_id: [1, "Customer Invoices"], amount_str: "$ 1200.00", debit: 1200.0, - account_code: "101200", ref: "", already_paid: false}; - - var indexInv2 = _.findIndex(this.params.mv_lines['[5,"",0]'], function (line) {return line.id === 112}); - this.params.mv_lines['[5,"",0]'][indexInv2] = - {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", - total_amount_str: "$ 1200.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0003", - partner_name: "Agrolait", total_amount_currency_str: "", id: 112, credit: 0.0, journal_id: [1, "Customer Invoices"], - amount_str: "$ 1200.00", debit: 1200.0, account_code: "101200", ref: "", already_paid: false}; - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: function (route, args) { - if (args.method === 'process_bank_statement_line') { - assert.deepEqual(args.args, - [ - [5], // Id of the bank statement line - - [{counterpart_aml_dicts: - [{name:"INV/2017/0002", - debit: 0, - credit: 1200, - analytic_tag_ids: [[6, null, []]], - counterpart_aml_id: 109}, - - {name: "INV/2017/0003", - debit: 0, - credit: 1200, - analytic_tag_ids: [[6, null, []]], - counterpart_aml_id: 112}], - - payment_aml_ids: [], - partner_id: 8, - to_check: false, - new_aml_dicts: [ - {account_id: 282, - credit: 0, - debit: 1225, - analytic_tag_ids: [[6, null, []]], - name: 'SAJ/2014/002 and SAJ/2014/003', - } - ]}] - ], "should call process_bank_statement_line with new aml dict reconcile values"); - } - return this._super(route, args); - }, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - // The first reconciliation "line" is where it happens - var widget = clientAction.widgets[0]; - - // Add first invoice - // There should be the opportunity to reconcile partially - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.ok(widget.$('.cell_right .edit_amount').length, - "should display the pencil to edit amount"); - - // Add second invoice - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.ok(widget.$('.cell_right .edit_amount').length, - "should display the pencil to edit amount"); - - var writeOffCreate = widget.$('div.create'); - - assert.equal(writeOffCreate.length, 1, - 'A write-off creation should be present'); - - assert.equal(writeOffCreate.find('input[name=amount]').val(), -1225, - 'The right amount should be proposed for the write-off'); - - await testUtils.dom.click(writeOffCreate.find('.create_account_id input.ui-autocomplete-input')); - await testUtils.dom.click($('ul.ui-autocomplete li a:first')); - - var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); - - assert.equal($buttonReconcile.length, 1, - 'The reconcile button must be visible'); - - await testUtils.dom.click($buttonReconcile); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation: partial payment of 2 invoices with one payment [REQUIRE FOCUS]', async function (assert) { - assert.expect(4); - - /* - * One payment: $1175 - * Two Invoices as Inv1 = 1200; Inv2 = 1200: - * Payment < Inv1 AND Payment < Inv2 - * Assign 500 to inv1 and 675 to inv2 - */ - - // modify the invoice line to have their amount > payment - var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0]'], function (line) {return line.id === 109}); - this.params.mv_lines['[5,"",0]'][indexInv1] = - {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", - total_amount_str: "$ 1200.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0002", partner_name: "Agrolait", - total_amount_currency_str: "", id: 109, credit: 0.0, journal_id: [1, "Customer Invoices"], amount_str: "$ 1200.00", debit: 1200.0, - account_code: "101200", ref: "", already_paid: false}; - - var indexInv2 = _.findIndex(this.params.mv_lines['[5,"",0]'], function (line) {return line.id === 112}); - this.params.mv_lines['[5,"",0]'][indexInv2] = - {account_type: "receivable", amount_currency_str: "", currency_id: false, date_maturity: "2017-02-07", date: "2017-01-08", - total_amount_str: "$ 1200.00", partner_id: 8, account_name: "101200 Account Receivable", name: "INV/2017/0003", - partner_name: "Agrolait", total_amount_currency_str: "", id: 112, credit: 0.0, journal_id: [1, "Customer Invoices"], - amount_str: "$ 1200.00", debit: 1200.0, account_code: "101200", ref: "", already_paid: false}; - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: function (route, args) { - if (args.method === 'process_bank_statement_line') { - assert.deepEqual(args.args, - [ - [5], // Id of the bank statement line - - [{counterpart_aml_dicts: - [{name:"INV/2017/0002", - debit: 0, - credit: 500, - analytic_tag_ids: [[6, null, []]], - counterpart_aml_id: 109}, - - {name: "INV/2017/0003", - debit: 0, - credit: 675, - analytic_tag_ids: [[6, null, []]], - counterpart_aml_id: 112}], - - payment_aml_ids: [], - partner_id: 8, - to_check: false, - new_aml_dicts: [] - }] - ], "should call process_bank_statement_line with correct counterpart_aml_dicts"); - } - return this._super(route, args); - }, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - // The first reconciliation "line" is where it happens - var widget = clientAction.widgets[0]; - - // Add first invoice - // There should be the opportunity to reconcile partially - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.ok(widget.$('.cell_right .edit_amount').length, - "should display the pencil to edit amount"); - - // Add second invoice - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.ok(widget.$('.cell_right .edit_amount').length, - "should display the pencil to edit amount"); - - // Edit invoice first amount - await testUtils.dom.click(widget.$('.edit_amount:first()')); - await testUtils.fields.editAndTrigger(widget.$('.edit_amount_input:first()'),'500',['blur']); - // Edit invoice second amount - var $buttonReconcile = widget.$('button.o_reconcile:not(hidden)'); - await testUtils.dom.click(widget.$('.edit_amount:last()')); - await testUtils.fields.editAndTrigger(widget.$('.edit_amount_input:last()'),'675',['blur']); - - assert.equal($buttonReconcile.length, 1, - 'The reconcile button must be visible'); - - await testUtils.dom.click($buttonReconcile,{allowInvisible:true}); - - clientAction.destroy(); - }); - - QUnit.test('Manual Reconciliation: remove a prop to attain balance and reconcile', async function (assert) { - assert.expect(5); - - // tweak the data to fit our needs - this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'] = _.extend({}, this.params.data_for_manual_reconciliation_widget['[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]']); - this.params.data_for_manual_reconciliation_widget['[283, null, "", 0, 6]'].accounts[0].reconciliation_proposition = [ - {account_id: 283, account_type: "other", amount_currency_str: "", currency_id: false, date_maturity: "2017-03-18", date: "2017-02-16", - total_amount_str: "$ 500.00", partner_id: 8, account_name: "101000 Current Assets", name: "INV/2017/0987", partner_name: "Agrolait", - total_amount_currency_str: "", id: 999, credit: 0.0, journal_id: [1, "Customer Invoices"], amount_str: "$ 500.00", debit: 500.0, - account_code: "101000", ref: "", already_paid: false} - ]; - - var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: function (route, args) { - if (args.method === 'process_move_lines') { - assert.deepEqual(args.args, - [ - [{id: null, type: null, - mv_line_ids: [399, 402], - new_mv_line_dicts: []} - ] - ], "should call process_move_lines without the new mv line dict"); - } - - return this._super(route, args); - }, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - // The first reconciliation "line" is where it happens - var widget = clientAction.widgets[0]; - - // Add first prop - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - assert.equal( widget.$('.cell_right .edit_amount').length, 0, "should not display the pencil to edit amount"); - - // Add second prop - await testUtils.dom.click(widget.$('.match:first .cell_account_code:first')); - // Check that a create form is here - - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - var writeOffCreate = widget.$('div.create'); - - assert.equal(writeOffCreate.length, 1, - 'A write-off creation should be present'); - - assert.equal(writeOffCreate.find('input[name=amount]').val(), 500, - 'The right amount should be proposed for the write-off'); - - // remove the first line, the other two will balance one another - await testUtils.dom.click(widget.$('tr[data-line-id="999"] td:first')); - - var $buttonReconcile = widget.$('button.o_reconcile:visible'); - assert.equal($buttonReconcile.length, 1, - 'The reconcile button must be visible'); - - await testUtils.dom.click($buttonReconcile); - - clientAction.destroy(); - }); - - QUnit.test('Manual Reconciliation: No lines for account', async function (assert) { - assert.expect(2); - - var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - // The second reconciliation "line" is where it happens - var widget = clientAction.widgets[1]; - - var emptyLine = widget.$('tr.mv_line'); - - assert.notOk('data-line-id' in emptyLine.getAttributes(), - 'Empty line should be empty'); - - await testUtils.dom.click(emptyLine.find('td:first')); - - // Check that a create form is here - var writeOffCreate = widget.$('div.create .create_account_id'); - - assert.equal(writeOffCreate.length, 1, - 'A write-off creation should be present'); - - clientAction.destroy(); - }); - - QUnit.test('Tax on account receivable', async function(assert){ - assert.expect(21); - - this.params.data_for_manual_reconciliation_widget['[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]'].accounts = []; - var clientAction = new ReconciliationClientAction.ManualAction(null, this.params.options); - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: {}, - mockRPC: function(route, args) { - if (args.method === "name_search") { - switch (args.model) { - // mock the default mock to do the minimal processing required - // to get the available values for the droplists. - case 'account.account': - assert.step("Account"); - return Promise.resolve( - _.map(this.data[args.model].records, function (record) { - return [record.id, record.name]; - }) - ); - case 'account.tax': - assert.step("Tax"); - return Promise.resolve( - _.map(this.data[args.model].records, function (record) { - return [record.id, record.display_name]; - }) - ); - case 'account.journal': - assert.step("Journal"); - return Promise.resolve( - _.map(this.data[args.model].records, function (record) { - return [record.id, record.display_name]; - }) - ); - } - } - if (args.method === 'process_move_lines') { - var mv_line_ids = args.args[0][0].mv_line_ids.slice(0); - mv_line_ids.sort(function(a, b) {return a - b}); - assert.deepEqual(mv_line_ids, [6, 19, 21], - "Reconciliation rpc payload, mv_line_ids are correct"); - - // Index aiming at the correct object in the list - var idx = _.has(args.args[0][0].new_mv_line_dicts[0], 'journal_id') ? 0 : 1; - assert.deepEqual( - _.pick(args.args[0][0].new_mv_line_dicts[idx], - 'account_id', 'name', 'credit', 'debit', 'journal_id'), - {account_id: 287, name: "dummy text", credit: 0, debit: 180, journal_id: 8}, - "Reconciliation rpc payload, new_mv_line_dicts.gift is correct" - ); - assert.deepEqual( - _.pick(args.args[0][0].new_mv_line_dicts[1 - idx], - 'account_id', 'name', 'credit', 'debit', 'tax_line_id'), - {account_id: 287, name: "Tax 20.00%", credit: 0, debit: 36, tax_line_id: 6}, - "Reconciliation rpc payload, new_mv_line_dicts.tax is correct" - ); - } - return this._super.apply(this, arguments); - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - var widget = clientAction.widgets[0]; - - // Select invoice of 1k$, payment of 1k$ and payment of 180$ - var $tableToReconcile = widget.$('.match'); - var defs = _.map([6, 19, 21], function(id) { - return testUtils.dom.click($tableToReconcile.find('tr.mv_line[data-line-id='+id+']:first td:first-child')); - }); - await Promise.all(defs); - assert.verifySteps([], "No rpc done"); - - // Store the money in excess to the "account receivable" account with 20% taxes - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - var $reconcileForm = widget.$(".create"); - await testUtils.dom.click($reconcileForm.find('.create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(101200 Account Receivable)').trigger('mouseover').trigger('click'); - await testUtils.nextTick(); - assert.verifySteps(["Account"], "Account rpc done"); - - await testUtils.dom.click($reconcileForm.find('.create_tax_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(Tax 20.00%)').trigger('mouseover').trigger('click'); - await testUtils.nextTick(); - assert.verifySteps(["Tax"], "Tax rpc done"); - - await testUtils.dom.click($reconcileForm.find('.create_journal_id input'),{allowInvisible:true}); - $('.ui-autocomplete .ui-menu-item a:contains(company 1 journal)').trigger('mouseover').trigger('click'); - await testUtils.nextTick(); - await testUtils.fields.editAndTrigger($reconcileForm.find('.create_label input'),'dummy text','input'); - await testUtils.dom.click($reconcileForm.find('.create_label input')); - assert.verifySteps(["Journal"], "Journal rpc done"); - - // Verify the two (gift + tax) lines were added to the list - var $newLines = widget.$('tr.mv_line[data-line-id^=createLine]'); - var idx = ($($($newLines[0]).find("td")[3]).text().trim() === "dummy text") ? 0 : 1; - - var $newLineGiftTds = $($newLines[1 - idx]).find("td"); - assert.equal($($newLineGiftTds[0]).text().trim().replace(/[\u200B]/g, ''), "101200", - "Gift line account number is valid"); - assert.equal($($newLineGiftTds[1]).text().trim(), "New", - "Gift line is flagged as new"); - assert.equal($($newLineGiftTds[2]).text().trim(), "dummy text", - "Gift line has the correct label"); - assert.equal($($newLineGiftTds[3]).text().trim(), "180.00", - "Gift line has the correct left amount"); - assert.equal($($newLineGiftTds[4]).text().trim(), "", - "Gift line has the correct right amount"); - - var $newLineTaxeTds = $($newLines[idx]).find("td"); - assert.equal($($newLineTaxeTds[0]).text().trim().replace(/[\u200B]/g, ''), "101200", - "Tax line account number is valid"); - assert.equal($($newLineTaxeTds[1]).text().trim(), "New", - "Tax line is flagged as new"); - assert.equal($($newLineTaxeTds[2]).text().trim(), "Tax 20.00%", - "Tax line has the correct label"); - assert.equal($($newLineTaxeTds[3]).text().trim(), "36.00", - "Tax line has the correct left amount"); - assert.equal($($newLineTaxeTds[4]).text().trim(), "", - "Tax line has the correct right amount"); - - // Reconcile - await testUtils.dom.click(widget.$("button.o_reconcile.btn.btn-primary:first")); - assert.ok(true, "No error in reconciliation"); - - clientAction.destroy(); - }); - - QUnit.test('Reconcile temporarily and ask to check', async function(assert){ - assert.expect(4); - this.params.options.context['to_check'] = true; - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - - testUtils.mock.addMockEnvironment(clientAction, { - data: this.params.data, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - await clientAction.appendTo($('#qunit-fixture')); - var widget = clientAction.widgets[0]; - - // Add a line as proposition - // open the first line - await testUtils.nextTick(); - await testUtils.dom.click(widget.$('.accounting_view tfoot td.cell_label')); - await testUtils.dom.click(widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')); - - var $reconcileForm = widget.$(".create"); - $reconcileForm.find('.create_account_id input').val('499001 Suspense Account').keydown().keyup(); - await testUtils.dom.click($reconcileForm.find('.create_account_id input')); - $('.ui-autocomplete .ui-menu-item a:contains(499001 Suspense Account)') - .trigger('mouseover') - .trigger('click'); - await testUtils.nextTick(); - - assert.equal($("button.o_validate.btn.btn-secondary.text-warning:first").length, 0, "should not display reconcile button in orange"); - await testUtils.dom.click($reconcileForm.find('.create_to_check input')); - assert.equal($("button.o_validate.btn.btn-secondary.text-warning:first").length, 1, "should display reconcile button in orange"); - - testUtils.mock.intercept(clientAction, 'call_service', function (event) { - assert.deepEqual(event.data.args[1].args, - [[5],[{partner_id: 8, - counterpart_aml_dicts: [], - payment_aml_ids: [], - new_aml_dicts: [{account_id: 499, - credit: 1175, - debit: 0, - analytic_tag_ids: [[6, null, []]], - name: 'SAJ/2014/002 and SAJ/2014/003', - }], - to_check: true, - }]], - "Should call process_bank_statement_line with to_check set to true"); - var def = testUtils.makeTestPromise(); - def.abort = function () {}; - event.data.callback(def); - }); - - await testUtils.dom.click(widget.$("button.o_validate.btn.btn-secondary:first")); - assert.ok(true, "No error in reconciliation"); - - clientAction.destroy(); - }); - - QUnit.test('Reconciliation Models handle analytic tags', async function (assert) { - assert.expect(6); - - var clientAction = new ReconciliationClientAction.StatementAction(null, this.params.options); - testUtils.addMockEnvironment(clientAction, { - data: this.params.data, - mockRPC: function (route, args) { - if (args.method === 'process_bank_statement_line') { - var new_aml_dicts = args.args[1][0].new_aml_dicts; - assert.strictEqual(new_aml_dicts.length, 2); - // I personnally judge the following use case rotten, since - // the first and the second line wouldn't have the same tags - assert.deepEqual(new_aml_dicts[0].analytic_tag_ids, [[6, null, [1, 2]]]); - assert.deepEqual(new_aml_dicts[1].analytic_tag_ids, [[6, null, [2]]]); - } - return this._super(route, args); - }, - session: { - currencies: { - 3: { - digits: [69, 2], - position: "before", - symbol: "$" - } - } - }, - archs: { - 'account.bank.statement.line,false,search': '<search string="Statement Line"><field name="display_name"/></search>', - }, - }); - - await clientAction.appendTo($('#qunit-fixture')); - await testUtils.nextTick(); - - // The first reconciliation "line" is where it happens - var widget = clientAction.widgets[0]; - - await testUtilsDom.click(widget.$('.nav-create:visible')); - await testUtilsDom.click(widget.$('.quick_add button:contains("Double")')); - assert.containsN(widget, '.create_analytic_tag_ids .o_field_many2manytags .badge', 2, - 'Two tags are loaded'); - assert.containsOnce(widget, '.create_analytic_tag_ids .o_field_many2manytags .badge:contains("Come together")', - 'Tags should have a name'); - assert.containsOnce(widget, '.create_analytic_tag_ids .o_field_many2manytags .badge:contains("Right now")', - 'Tags should have a name'); - - await testUtilsDom.click(widget.$('.create_analytic_tag_ids .o_field_many2manytags .badge a.o_delete:first()')); - - await testUtilsDom.click(widget.$('.o_reconcile:visible')); - - clientAction.destroy(); - }); -}); -}); diff --git a/addons/account/static/tests/tours/reconciliation.js b/addons/account/static/tests/tours/reconciliation.js deleted file mode 100644 index f100e0d1a0e..00000000000 --- a/addons/account/static/tests/tours/reconciliation.js +++ /dev/null @@ -1,108 +0,0 @@ -odoo.define('account.tour_bank_statement_reconciliation', function(require) { -'use strict'; - -var core = require('web.core'); -var rpc = require('web.rpc'); -var Tour = require('web_tour.tour'); - -Tour.register('bank_statement_reconciliation', { - test: true, - // Go to the reconciliation page of the statement: "BNK/2014/001" - }, [ - // Reconciliation of 'INV/2018/0002' - // Click on reconcile (matching done automatically by the reconciliation rule). - - { - content: "reconcile the line", - trigger: '.o_reconciliation_line:nth-child(1) .o_reconcile:visible', - }, - - // Reconciliation of 'First 2000 $ of INV/2018/0001' - // Make a partial reconciliation - - { - content: "open the 4th line in match_rp mode to test the partial reconciliation", - extra_trigger: '.o_reconciliation_line:first[data-mode="match_rp"]', - trigger: '.o_reconciliation_line:nth-child(4) .cell_label:contains("First")' - }, - { - content: "click on partial reconcile", - trigger: '.o_reconciliation_line:nth-child(4) .accounting_view .edit_amount', - }, - { - content: "Edit amount", - trigger: '.o_reconciliation_line:nth-child(4) .accounting_view .edit_amount_input:not(.d-none)', - run: 'text 2000' - }, - { - content: "Press enter to validate amount", - trigger: '.o_reconciliation_line:nth-child(4) .accounting_view .edit_amount_input:not(.d-none)', - run: 'keydown 13' // requires focus - }, - { - content: "Check that amount has changed", - trigger: '.o_reconciliation_line:nth-child(4) .accounting_view .line_amount:contains("2,000.00")' - }, - { - content: "reconcile the line", - trigger: '.o_reconciliation_line:nth-child(4) .o_reconcile:visible', - }, - - // Reconciliation of 'Prepayment' - // Test changing the partner - - { - content: "change the partner of the second line", - trigger: '.o_reconciliation_line:nth-child(2) .o_field_many2one[name="partner_id"] input', - run: 'text Deco' - }, - { - content: "select Deco Addict ", - extra_trigger: '.ui-autocomplete:visible li:eq(1):contains(Create "Deco")', - trigger: '.ui-autocomplete:visible li:contains(Deco Addict)', - }, - { - content: "use filter", - trigger: '.o_reconciliation_line:nth-child(1) .match .match_controls .filter', - run: 'text 4610' - }, - { - content: "select a line linked to Deco Addict ", - extra_trigger: ".o_reconciliation_line:nth-child(1) .match .line_info_button:last[data-content*='4,610']", - trigger: ".o_reconciliation_line:nth-child(1) .match .line_info_button[data-content*='Deco Addict']" - }, - { - content: "deselect the line", - trigger: '.o_reconciliation_line:nth-child(1) .accounting_view tbody .cell_label:first', - run: function() { - $('.o_reconciliation_line:nth-child(1) .accounting_view tbody .cell_label:first').trigger('click'); - } - }, - { - content: "create a write-off", - extra_trigger: '.o_reconciliation_line:nth-child(2) .accounting_view tfoot .cell_left:visible:contains(32.58)', - trigger: '.o_reconciliation_line:nth-child(2) .o_notebook .nav-link[href*="notebook_page_create"]' - }, - { - content: "enter an account", - trigger: '.o_reconciliation_line:nth-child(2) .o_field_many2one[name="account_id"] input', - run: 'text 151000' - }, - { - content: "select the first account", - trigger: '.ui-autocomplete:visible li:last:contains(151000)', - }, - { - content: "reconcile the line with the write-off", - trigger: '.o_reconciliation_line:nth-child(2) .o_reconcile:visible', - }, - - // Be done - { - content: "check the number off validate lines", - trigger: '.o_control_panel .progress-reconciliation:contains(3 / 6)' - }, - ] -); - -}); |
