flectra.define('account.ReconciliationClientAction', function (require) { "use strict"; var ReconciliationModel = require('account.ReconciliationModel'); var ReconciliationRenderer = require('account.ReconciliationRenderer'); var ControlPanelMixin = require('web.ControlPanelMixin'); var Widget = require('web.Widget'); var core = require('web.core'); /** * Widget used as action for 'account.bank.statement' reconciliation */ var StatementAction = Widget.extend(ControlPanelMixin, { title: core._t('Bank reconciliation'), template: 'reconciliation', custom_events: { change_mode: '_onAction', change_filter: '_onAction', change_offset: '_onAction', change_partner: '_onAction', add_proposition: '_onAction', search_balance_amount: '_onAction', remove_proposition: '_onAction', update_proposition: '_onAction', create_proposition: '_onAction', quick_create_proposition: '_onAction', toggle_partial_reconcile: '_onAction', auto_reconciliation: '_onValidate', validate: '_onValidate', validate_all_balanced: '_onValidate', change_name: '_onChangeName', close_statement: '_onCloseStatement', load_more: '_onLoadMore', }, config: { // used to instanciate the model Model: ReconciliationModel.StatementModel, // used to instanciate the action interface ActionRenderer: ReconciliationRenderer.StatementRenderer, // used to instanciate each widget line LineRenderer: ReconciliationRenderer.LineRenderer, // used context params params: ['statement_ids'], // number of moves lines displayed in 'match' mode limitMoveLines: 5, }, /** * @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.model = new this.config.Model(this, { modelName: "account.bank.statement.line", limitMoveLines: params.params && params.params.limitMoveLines || this.config.limitMoveLines, }); this.widgets = []; if (!this.action_manager) { this.set_cp_bus(new Widget()); } // 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] = name.indexOf('_ids') !== -1 && _.isArray(params.context[name]) ? params.context[name].join() : 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 () { self.title = self.model.bank_statement_id ? self.model.bank_statement_id.display_name : self.title; self.renderer = new self.config.ActionRenderer(self, self.model, { 'bank_statement_id': self.model.bank_statement_id, 'valuenow': self.model.valuenow, 'valuemax': self.model.valuemax, 'defaultDisplayQty': self.model.defaultDisplayQty, 'title': self.title, }); }); }, /** * append the renderer and instantiate the line renderers * * @override */ start: function () { var self = this; this.set("title", this.title); var breadcrumbs = this.action_manager && this.action_manager.get_breadcrumbs() || [{ title: this.title, action: this }]; this.update_control_panel({breadcrumbs: breadcrumbs, search_view_hidden: true}, {clear: true}); this.renderer.prependTo(self.$('.o_form_sheet')); this._renderLines(); this._openFirstLine(); }, /** * update the control panel and breadcrumbs * * @override */ do_show: function () { this._super.apply(this, arguments); if (this.action_manager) { var breadcrumbs = this.action_manager && this.action_manager.get_breadcrumbs() || [{ title: this.title, action: this }]; while (breadcrumbs.length) { if (breadcrumbs[breadcrumbs.length-1].action.widget === this) { break; } breadcrumbs.pop(); } this.update_control_panel({breadcrumbs: breadcrumbs, search_view_hidden: true}, {clear: true}); this.action_manager.do_push_state({ action: this.params.tag, active_id: this.params.res_id, }); } }, //-------------------------------------------------------------------------- // 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 () { 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, 'match').always(function () { self._getWidget(handle).update(line); }); } return handle; }, /** * render line widget and append to view * * @private */ _renderLines: function () { var self = this; var linesToDisplay = this.model.getStatementLines(); _.each(linesToDisplay, function (line, handle) { var widget = new self.config.LineRenderer(self, self.model, line); widget.handle = handle; self.widgets.push(widget); widget.appendTo(self.$('.o_reconciliation_lines')); }); if (this.model.hasMoreLines() === false) { this.renderer.hideLoadMoreButton(); } }, //-------------------------------------------------------------------------- // 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', the other lines switch to 'inactive' mode * * @private * @param {FlectraEvent} event */ _onAction: function (event) { var self = this; var handle = event.target.handle; var line = this.model.getLine(handle); var mode = line.mode; this.model[_.str.camelize(event.name)](handle, event.data.data).always(function () { self._getWidget(handle).update(line); if (mode === 'inactive' && 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); } } }); } }); }, /** * call 'changeName' model method * * @private * @param {FlectraEvent} event */ _onChangeName: function (event) { var self = this; var title = event.data.data; this.model.changeName(title).then(function () { self.title = title; self.set("title", title); self.renderer.update({ 'valuenow': self.model.valuenow, 'valuemax': self.model.valuemax, 'title': title, }); }); }, /** * call 'closeStatement' model method * * @private * @param {FlectraEvent} 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', res_id: result, views: [[false, 'form']], type: 'ir.actions.act_window', view_type: 'form', view_mode: 'form', }); }); }, /** * Load more statement and render them * * @param {FlectraEvent} event */ _onLoadMore: function (event) { return this._loadMore(this.model.defaultDisplayQty); }, /** * call 'validate' or 'autoReconciliation' 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 {FlectraEvent} event */ _onValidate: function (event) { var self = this; var handle = event.target.handle; var method = event.name.indexOf('auto_reconciliation') === -1 ? 'validate' : 'autoReconciliation'; this.model[method](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(), }); _.each(result.handles, function (handle) { self._getWidget(handle).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'), config: { Model: ReconciliationModel.ManualModel, ActionRenderer: ReconciliationRenderer.ManualRenderer, LineRenderer: ReconciliationRenderer.ManualLineRenderer, params: ['company_ids', 'mode', 'partner_ids', 'account_ids'], limitMoveLines: 10, }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * call 'validate' or 'autoReconciliation' 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 {FlectraEvent} event */ _onValidate: function (event) { var self = this; var handle = event.target.handle; var method = event.name.indexOf('auto_reconciliation') === -1 ? 'validate' : 'autoReconciliation'; 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, }; });