1406 lines
52 KiB
JavaScript
1406 lines
52 KiB
JavaScript
flectra.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 CrashManager = require('web.CrashManager');
|
|
var core = require('web.core');
|
|
var _t = core._t;
|
|
|
|
|
|
/**
|
|
* Model use to fetch, format and update 'account.bank.statement' and
|
|
* 'account.bank.statement.line' datas allowing reconciliation
|
|
*
|
|
* The statement internal structure::
|
|
*
|
|
* {
|
|
* valuenow: integer
|
|
* valuenow: valuemax
|
|
* [bank_statement_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', 'create')
|
|
* reconciliation_proposition: {
|
|
* id: number|string
|
|
* partial_reconcile: boolean
|
|
* 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
|
|
* [amount_currency_str]: string|false (amount in record currency)
|
|
* }
|
|
* mv_lines: object - idem than reconciliation_proposition
|
|
* offset: integer
|
|
* limitMoveLines: integer
|
|
* filter: string
|
|
* [createForm]: {
|
|
* account_id: {
|
|
* id: integer
|
|
* display_name: string
|
|
* }
|
|
* tax_id: {
|
|
* id: integer
|
|
* display_name: string
|
|
* }
|
|
* analytic_account_id: {
|
|
* id: integer
|
|
* display_name: string
|
|
* }
|
|
* 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_id'],
|
|
|
|
/**
|
|
* @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.defaultDisplayQty = 10;
|
|
this.limitMoveLines = options && options.limitMoveLines || 5;
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// 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 {Deferred}
|
|
*/
|
|
addProposition: function (handle, mv_line_id) {
|
|
var line = this.getLine(handle);
|
|
var prop = _.clone(_.find(line.mv_lines, {'id': mv_line_id}));
|
|
this._addProposition(line, prop);
|
|
return $.when(this._computeLine(line), this._performMoveLine(handle));
|
|
},
|
|
/**
|
|
* send information 'account.bank.statement.line' model to reconciliate
|
|
* lines, call rpc to 'reconciliation_widget_auto_reconcile'
|
|
* Update the number of validated line
|
|
*
|
|
* @returns {Deferred<Object>} resolved with an object who contains
|
|
* 'handles' key and 'notifications'
|
|
*/
|
|
autoReconciliation: function () {
|
|
var self = this;
|
|
var ids = _.pluck(_.filter(this.lines, {'reconciled': false}), 'id');
|
|
return this._rpc({
|
|
model: 'account.bank.statement.line',
|
|
method: 'reconciliation_widget_auto_reconcile',
|
|
args: [ids, self.valuenow],
|
|
})
|
|
.then(function (result) {
|
|
var reconciled_ids = _.difference(ids, result.st_lines_ids);
|
|
self.valuenow += reconciled_ids.length;
|
|
result.handles = [];
|
|
_.each(self.lines, function (line, handle) {
|
|
if (reconciled_ids.indexOf(line.id) !== -1) {
|
|
line.reconciled = true;
|
|
result.handles.push(handle);
|
|
}
|
|
});
|
|
return result;
|
|
});
|
|
},
|
|
/**
|
|
* change the filter for the target line and fetch the new matched lines
|
|
*
|
|
* @param {string} handle
|
|
* @param {string} filter
|
|
* @returns {Deferred}
|
|
*/
|
|
changeFilter: function (handle, filter) {
|
|
var line = this.getLine(handle);
|
|
line.filter = filter;
|
|
line.offset = 0;
|
|
return this._performMoveLine(handle);
|
|
},
|
|
/**
|
|
* change the mode line ('inactive', 'match', 'create'), and fetch the new
|
|
* matched lines or prepare to create a new line
|
|
*
|
|
* ``match``
|
|
* display the 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' | 'create'} mode
|
|
* @returns {Deferred}
|
|
*/
|
|
changeMode: function (handle, mode) {
|
|
var line = this.getLine(handle);
|
|
if (line.mode === 'create') {
|
|
this._blurProposition(handle);
|
|
line.createForm = null;
|
|
}
|
|
if (mode === 'create' && this.avoidCreate) {
|
|
mode = 'match';
|
|
}
|
|
line.mode = mode;
|
|
if (mode === 'match') {
|
|
return this._performMoveLine(handle);
|
|
}
|
|
if (line.mode === 'create') {
|
|
return this.createProposition(handle);
|
|
}
|
|
return $.when();
|
|
},
|
|
/**
|
|
* call 'write' method on the 'account.bank.statement'
|
|
*
|
|
* @param {string} name
|
|
* @returns {Deferred}
|
|
*/
|
|
changeName: function (name) {
|
|
return this._rpc({
|
|
model: 'account.bank.statement',
|
|
method: 'write',
|
|
args: [this.bank_statement_id.id, {name: name}],
|
|
});
|
|
},
|
|
/**
|
|
* change the offset for the matched lines, and fetch the new matched lines
|
|
*
|
|
* @param {string} handle
|
|
* @param {number} offset
|
|
* @returns {Deferred}
|
|
*/
|
|
changeOffset: function (handle, offset) {
|
|
this.getLine(handle).offset += (offset > 0 ? 1 : -1) * this.limitMoveLines;
|
|
return this._performMoveLine(handle);
|
|
},
|
|
/**
|
|
* change the partner on the line and fetch the new matched lines
|
|
*
|
|
* @param {string} handle
|
|
* @param {Object} partner
|
|
* @param {string} partner.display_name
|
|
* @param {number} partner.id
|
|
* @returns {Deferred}
|
|
*/
|
|
changePartner: function (handle, partner) {
|
|
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 $.when(partner && this._changePartner(handle, partner.id))
|
|
.then(function() {
|
|
line.reconciliation_proposition = [];
|
|
self._computeLine(line);
|
|
return self.changeMode(handle, 'match');
|
|
})
|
|
.then(function () {
|
|
if (line.mode === 'create') {
|
|
return self.createProposition(handle);
|
|
}
|
|
});
|
|
},
|
|
/**
|
|
* close the statement
|
|
* @returns {Deferred<number>} resolves to the res_id of the closed statements
|
|
*/
|
|
closeStatement: function () {
|
|
var self = this;
|
|
return this._rpc({
|
|
model: 'account.bank.statement',
|
|
method: 'button_confirm_bank',
|
|
args: [self.bank_statement_id.id],
|
|
})
|
|
.then(function () {
|
|
return self.bank_statement_id.id;
|
|
});
|
|
},
|
|
/**
|
|
*
|
|
* then open the first available line
|
|
*
|
|
* @param {string} handle
|
|
* @returns {Deferred}
|
|
*/
|
|
createProposition: function (handle) {
|
|
var line = this.getLine(handle);
|
|
var prop = _.filter(line.reconciliation_proposition, '__focus');
|
|
var last = prop[prop.length-1];
|
|
if (last && !this._isValid(last)) {
|
|
return $.Deferred().reject();
|
|
}
|
|
|
|
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
|
|
*
|
|
* @returns {boolean} true if load more button needs to be displayed
|
|
*/
|
|
hasMoreLines: function () {
|
|
var self = this;
|
|
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.bank.statement.line' fetch each line data
|
|
*
|
|
* @param {Object} context
|
|
* @param {number[]} context.statement_ids
|
|
* @returns {Deferred}
|
|
*/
|
|
load: function (context) {
|
|
var self = this;
|
|
var statement_ids = context.statement_ids;
|
|
if (!statement_ids) {
|
|
return $.when();
|
|
}
|
|
this.context = context;
|
|
|
|
var def_statement = this._rpc({
|
|
model: 'account.bank.statement',
|
|
method: 'reconciliation_widget_preprocess',
|
|
args: [statement_ids],
|
|
})
|
|
.then(function (statement) {
|
|
self.statement = statement;
|
|
self.bank_statement_id = statement_ids.length === 1 ? {id: statement_ids[0], display_name: statement.statement_name} : false;
|
|
self.valuenow = 0;
|
|
self.valuemax = statement.st_lines_ids.length;
|
|
self.context.journal_id = statement.journal_id;
|
|
_.each(statement.st_lines_ids, function (id) {
|
|
self.lines[_.uniqueId('rline')] = {
|
|
id: id,
|
|
reconciled: false,
|
|
mode: 'inactive',
|
|
mv_lines: [],
|
|
offset: 0,
|
|
filter: "",
|
|
reconciliation_proposition: [],
|
|
reconcileModels: [],
|
|
};
|
|
});
|
|
});
|
|
var def_reconcileModel = this._rpc({
|
|
model: 'account.reconcile.model',
|
|
method: 'search_read',
|
|
})
|
|
.then(function (reconcileModels) {
|
|
self.reconcileModels = reconcileModels;
|
|
});
|
|
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'));
|
|
});
|
|
return $.when(def_statement, def_reconcileModel, def_account).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.loadData(ids, []);
|
|
});
|
|
},
|
|
/**
|
|
* Load more bank statement line
|
|
*
|
|
* @param {integer} qty quantity to load
|
|
* @returns {Deferred}
|
|
*/
|
|
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
|
|
*
|
|
* @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 {Deferred}
|
|
*/
|
|
loadData: function(ids, excluded_ids) {
|
|
var self = this;
|
|
return self._rpc({
|
|
model: 'account.bank.statement.line',
|
|
method: 'get_data_for_reconciliation_widget',
|
|
args: [ids, excluded_ids],
|
|
})
|
|
.then(self._formatLine.bind(self));
|
|
},
|
|
/**
|
|
* 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 {Deferred}
|
|
*/
|
|
quickCreateProposition: function (handle, reconcileModelId) {
|
|
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', 'tax_id'];
|
|
this._blurProposition(handle);
|
|
|
|
var focus = this._formatQuickCreate(line, _.pick(reconcileModel, fields));
|
|
focus.reconcileModelId = reconcileModelId;
|
|
line.reconciliation_proposition.push(focus);
|
|
|
|
if (reconcileModel.has_second_line) {
|
|
var second = {};
|
|
_.each(fields, function (key) {
|
|
second[key] = ("second_"+key) in reconcileModel ? reconcileModel["second_"+key] : reconcileModel[key];
|
|
});
|
|
focus = this._formatQuickCreate(line, second);
|
|
focus.reconcileModelId = reconcileModelId;
|
|
line.reconciliation_proposition.push(focus);
|
|
this._computeReconcileModels(handle, reconcileModelId);
|
|
}
|
|
line.createForm = _.pick(focus, this.quickCreateFields);
|
|
return this._computeLine(line);
|
|
},
|
|
/**
|
|
* Remove a proposition and switch to an active mode ('create' or 'match')
|
|
*
|
|
* @param {string} handle
|
|
* @param {number} id (move line id)
|
|
* @returns {Deferred}
|
|
*/
|
|
removeProposition: function (handle, id) {
|
|
var self = this;
|
|
var line = this.getLine(handle);
|
|
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.mode = (id || line.mode !== "create") && isNaN(id) && !this.avoidCreate ? 'create' : 'match';
|
|
var def = this._computeLine(line);
|
|
if (line.mode === 'create') {
|
|
return def.then(function () {
|
|
return self.createProposition(handle);
|
|
});
|
|
} else if (line.mode === 'match') {
|
|
return $.when(def, self._performMoveLine(handle));
|
|
}
|
|
return def;
|
|
},
|
|
searchBalanceAmount: function (handle) {
|
|
var line = this.getLine(handle);
|
|
var amount = line.balance.amount;
|
|
var amount_str = _.str.sprintf('%.2f', Math.abs(amount));
|
|
amount_str = (amount > '0' ? '-' : '+') + amount_str;
|
|
if (line.balance.currency_id && line.balance.amount_currency) {
|
|
var amount_currency = line.balance.amount_currency;
|
|
var amount_currency_str = _.str.sprintf('%.2f', Math.abs(amount_currency));
|
|
amount_str += '|' + (amount_currency > '0' ? '-' : '+') + amount_currency_str;
|
|
}
|
|
if (amount_str === line.filter) {
|
|
line.filter = '';
|
|
line.offset = 0;
|
|
return this.changeMode(handle, 'create');
|
|
}
|
|
line.filter = amount_str;
|
|
line.offset = 0;
|
|
return this.changeMode(handle, 'match');
|
|
},
|
|
/**
|
|
* Force the partial reconciliation to display the reconciliate button.
|
|
* This method should only be called when there is onely one proposition.
|
|
*
|
|
* @param {string} handle
|
|
* @returns {Deferred}
|
|
*/
|
|
togglePartialReconcile: function (handle) {
|
|
var line = this.getLine(handle);
|
|
var props = _.filter(line.reconciliation_proposition, {'invalid': false});
|
|
var prop = props[0];
|
|
if (props.length !== 1 || Math.abs(line.st_line.amount) >= Math.abs(prop.amount)) {
|
|
return $.Deferred().reject();
|
|
}
|
|
prop.partial_reconcile = !prop.partial_reconcile;
|
|
if (!prop.partial_reconcile) {
|
|
return this._computeLine(line);
|
|
}
|
|
return this._computeLine(line).then(function () {
|
|
if (prop.partial_reconcile) {
|
|
line.balance.amount = 0;
|
|
line.balance.type = 1;
|
|
line.mode = 'inactive';
|
|
}
|
|
});
|
|
},
|
|
/**
|
|
* 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 {Deferred}
|
|
*/
|
|
updateProposition: function (handle, values) {
|
|
var line = this.getLine(handle);
|
|
var prop = _.last(_.filter(line.reconciliation_proposition, '__focus'));
|
|
if (!prop) {
|
|
prop = this._formatQuickCreate(line);
|
|
line.reconciliation_proposition.push(prop);
|
|
}
|
|
_.each(values, function (value, fieldName) {
|
|
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 ('account_id' in values || 'amount' in values || 'tax_id' in values) {
|
|
prop.__tax_to_recompute = true;
|
|
}
|
|
line.createForm = _.pick(prop, this.quickCreateFields);
|
|
return this._computeLine(line);
|
|
},
|
|
/**
|
|
* Format the value and send it to 'account.bank.statement.line' model
|
|
* Update the number of validated lines
|
|
*
|
|
* @param {(string|string[])} handle
|
|
* @returns {Deferred<Object>} resolved with an object who contains
|
|
* 'handles' key
|
|
*/
|
|
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 ids = [];
|
|
var values = [];
|
|
_.each(handles, function (handle) {
|
|
var line = self.getLine(handle);
|
|
var props = _.filter(line.reconciliation_proposition, function (prop) {return !prop.is_tax && !prop.invalid;});
|
|
if (props.length === 0) {
|
|
// Usability: if user has not choosen 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]);
|
|
self._computeLine(line);
|
|
}
|
|
ids.push(line.id);
|
|
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);
|
|
}), self._formatToProcessReconciliation.bind(self, line)),
|
|
};
|
|
// 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++;
|
|
});
|
|
|
|
return this._rpc({
|
|
model: 'account.bank.statement.line',
|
|
method: 'process_reconciliations',
|
|
args: [ids, values],
|
|
})
|
|
.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) {
|
|
function checkAccountType (r) {
|
|
return !isNaN(r.id) && r.account_type !== prop.account_type;
|
|
}
|
|
if (_.any(line.reconciliation_proposition, checkAccountType)) {
|
|
new CrashManager().show_warning({data: {
|
|
exception_type: _t("Incorrect Operation"),
|
|
message: _t("You cannot mix items from receivable and payable accounts.")
|
|
}});
|
|
return $.when();
|
|
}
|
|
|
|
line.reconciliation_proposition.push(prop);
|
|
_.each(line.reconciliation_proposition, function (prop) {
|
|
prop.partial_reconcile = false;
|
|
});
|
|
},
|
|
/**
|
|
* stop the editable proposition line and remove it if it's invalid then
|
|
* compute the line
|
|
*
|
|
* See :func:`_computeLine`
|
|
*
|
|
* @private
|
|
* @param {string} handle
|
|
* @returns {Deferred}
|
|
*/
|
|
_blurProposition: function (handle) {
|
|
var line = this.getLine(handle);
|
|
line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (l) {
|
|
l.__focus = false;
|
|
return !l.invalid;
|
|
});
|
|
return this._computeLine(line);
|
|
},
|
|
/**
|
|
* 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 {Deferred}
|
|
*/
|
|
_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.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_id
|
|
*
|
|
* @private
|
|
* @param {Object} line
|
|
* @returns {Deferred}
|
|
*/
|
|
_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,
|
|
};
|
|
_.each(line.reconciliation_proposition, function (prop) {
|
|
if (prop.is_tax) {
|
|
if (!_.find(line.reconciliation_proposition, {'id': prop.link}).__tax_to_recompute) {
|
|
reconciliation_proposition.push(prop);
|
|
}
|
|
return;
|
|
}
|
|
reconciliation_proposition.push(prop);
|
|
|
|
if (prop.tax_id && prop.__tax_to_recompute && prop.base_amount) {
|
|
line.reconciliation_proposition = _.filter(line.reconciliation_proposition, function (p) {
|
|
return !p.is_tax || p.link !== prop.id;
|
|
});
|
|
|
|
var args = [[prop.tax_id.id], prop.base_amount, formatOptions.currency_id];
|
|
tax_defs.push(self._rpc({
|
|
model: 'account.tax',
|
|
method: 'json_friendly_compute_all',
|
|
args: args,
|
|
})
|
|
.then(function (result) {
|
|
_.each(result.taxes, function(tax){
|
|
var tax_prop = self._formatQuickCreate(line, {
|
|
'link': prop.id,
|
|
'tax_id': tax.id,
|
|
'amount': tax.amount,
|
|
'label': tax.name,
|
|
'account_id': tax.account_id ? [tax.account_id, null] : prop.account_id,
|
|
'analytic': tax.analytic,
|
|
'is_tax': true,
|
|
'__focus': false
|
|
});
|
|
|
|
prop.computed_with_tax = tax.price_include
|
|
prop.tax_amount = tax.amount
|
|
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 $.when.apply($, 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.amount;
|
|
if (isOtherCurrencyId) {
|
|
amount_currency -= (prop.amount < 0 ? -1 : 1) * Math.abs(prop.amount_currency);
|
|
}
|
|
}
|
|
});
|
|
total = Math.round(total*1000)/1000 || 0;
|
|
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.type = line.balance.amount_currency ? (line.balance.amount_currency > 0 && 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;
|
|
},
|
|
/**
|
|
* Format each propositions (amount, label, account_id)
|
|
*
|
|
* @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;
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* Format each server lines and propositions and compute all lines
|
|
*
|
|
* @see '_computeLine'
|
|
*
|
|
* @private
|
|
* @param {Object[]} lines
|
|
* @returns {Deferred}
|
|
*/
|
|
_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;
|
|
}
|
|
defs.push(self._computeLine(line));
|
|
});
|
|
return $.when.apply($, defs);
|
|
},
|
|
/**
|
|
* Format the server value then compute the line
|
|
*
|
|
* @see '_computeLine'
|
|
*
|
|
* @private
|
|
* @param {string} handle
|
|
* @param {Object[]} mv_lines
|
|
* @returns {Deferred}
|
|
*/
|
|
_formatMoveLine: function (handle, mv_lines) {
|
|
var self = this;
|
|
var line = this.getLine(handle);
|
|
_.extend(line, {'mv_lines': mv_lines});
|
|
this._formatLineProposition(line, mv_lines);
|
|
if (line.mode !== 'create' && !mv_lines.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);
|
|
}
|
|
},
|
|
/**
|
|
* Apply default values for the proposition, format datas and format the
|
|
* base_amount with the decimal number from the currency
|
|
*
|
|
* @private
|
|
* @param {Object} line
|
|
* @param {Object} values
|
|
* @returns {Object}
|
|
*/
|
|
_formatQuickCreate: function (line, values) {
|
|
values = values || {};
|
|
var account = this._formatNameGet(values.account_id);
|
|
var formatOptions = {
|
|
currency_id: line.st_line.currency_id,
|
|
};
|
|
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),
|
|
'journal_id': this._formatNameGet(values.journal_id),
|
|
'tax_id': this._formatNameGet(values.tax_id),
|
|
'debit': 0,
|
|
'credit': 0,
|
|
'base_amount': values.amount_type !== "percentage" ?
|
|
(values.amount || line.balance.amount) :
|
|
line.balance.amount * values.amount / 100,
|
|
'percent': values.amount_type === "percentage" ? values.amount : null,
|
|
'link': values.link,
|
|
'display': true,
|
|
'invalid': true,
|
|
'__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
|
|
*
|
|
* @private
|
|
* @param {object} prop
|
|
* @returns {Boolean}
|
|
*/
|
|
_isDisplayedProposition: function (prop) {
|
|
return !isNaN(prop.id) || !!prop.account_id;
|
|
},
|
|
/**
|
|
* @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.bank.statement.line' propositions.
|
|
*
|
|
* @see '_formatMoveLine'
|
|
*
|
|
* @private
|
|
* @param {string} handle
|
|
* @returns {Deferred}
|
|
*/
|
|
_performMoveLine: function (handle) {
|
|
var line = this.getLine(handle);
|
|
var excluded_ids = _.compact(_.flatten(_.map(this.lines, function (line) {
|
|
return _.map(line.reconciliation_proposition, function (prop) {
|
|
return !prop.partial_reconcile && _.isNumber(prop.id) ? prop.id : null;
|
|
});
|
|
})));
|
|
var filter = line.filter || "";
|
|
var offset = line.offset;
|
|
var limit = this.limitMoveLines+1;
|
|
return this._rpc({
|
|
model: 'account.bank.statement.line',
|
|
method: 'get_move_lines_for_reconciliation_widget',
|
|
args: [line.id, line.st_line.partner_id, excluded_ids, filter, offset, limit],
|
|
})
|
|
.then(this._formatMoveLine.bind(this, handle));
|
|
},
|
|
/**
|
|
* format the proposition to send information server side
|
|
*
|
|
* @private
|
|
* @param {object} line
|
|
* @param {object} prop
|
|
* @returns {object}
|
|
*/
|
|
_formatToProcessReconciliation: function (line, prop) {
|
|
// Do not forward port in master. @CSN will change this
|
|
var amount = prop.computed_with_tax && -prop.base_amount || -prop.amount;
|
|
if (prop.partial_reconcile === true) {
|
|
amount = -line.st_line.amount;
|
|
}
|
|
var result = {
|
|
name : prop.label,
|
|
debit : amount > 0 ? amount : 0,
|
|
credit : amount < 0 ? -amount : 0,
|
|
// This one isn't usefull for the server,
|
|
// But since we need to change the amount (and thus its semantics) into base_amount
|
|
// It might be useful to have a trace in the RPC for debugging purposes
|
|
computed_with_tax: prop.computed_with_tax,
|
|
};
|
|
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_id) result.tax_ids = [[4, prop.tax_id.id, null]];
|
|
return result;
|
|
},
|
|
});
|
|
|
|
|
|
/**
|
|
* 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_id'],
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Public
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* load data from
|
|
* - 'account.move.line' 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 {Deferred}
|
|
*/
|
|
load: function (context) {
|
|
var self = this;
|
|
|
|
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'));
|
|
});
|
|
|
|
return def_account.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.move.line',
|
|
method: 'get_data_for_manual_reconciliation',
|
|
args: args,
|
|
context: context,
|
|
})
|
|
.then(function (result) {
|
|
var defs = _.map(result, self._formatLine.bind(self, context.mode));
|
|
self.valuenow = 0;
|
|
self.valuemax = Object.keys(self.lines).length;
|
|
return $.when.apply($, defs);
|
|
});
|
|
case 'accounts':
|
|
return self._rpc({
|
|
model: 'account.move.line',
|
|
method: 'get_data_for_manual_reconciliation',
|
|
args: ['account', context.account_ids || self.account_ids],
|
|
context: context,
|
|
})
|
|
.then(function (result) {
|
|
var defs = _.map(result, self._formatLine.bind(self, 'accounts'));
|
|
self.valuenow = 0;
|
|
self.valuemax = Object.keys(self.lines).length;
|
|
return $.when.apply($, defs);
|
|
});
|
|
default:
|
|
var partner_ids = context.partner_ids;
|
|
var account_ids = self.account_ids;
|
|
if (partner_ids && !account_ids) account_ids = [];
|
|
if (!partner_ids && account_ids) partner_ids = [];
|
|
account_ids = null; // TOFIX: REMOVE ME
|
|
partner_ids = null; // TOFIX: REMOVE ME
|
|
return self._rpc({
|
|
model: 'account.move.line',
|
|
method: 'get_data_for_manual_reconciliation_widget',
|
|
args: [partner_ids, account_ids],
|
|
context: context,
|
|
})
|
|
.then(function (result) {
|
|
var defs = _.map(result.accounts, self._formatLine.bind(self, 'accounts'));
|
|
defs = defs.concat(_.map(result.customers, self._formatLine.bind(self, 'customers')));
|
|
defs = defs.concat(_.map(result.suppliers, self._formatLine.bind(self, 'suppliers')));
|
|
self.valuenow = 0;
|
|
self.valuemax = Object.keys(self.lines).length;
|
|
return $.when.apply($, defs);
|
|
});
|
|
}
|
|
});
|
|
},
|
|
/**
|
|
* Mark the account or the partner as reconciled
|
|
*
|
|
* @param {(string|string[])} handle
|
|
* @returns {Deferred<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 = $.when();
|
|
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);}), 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.move.line',
|
|
method: 'process_reconciliations',
|
|
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).then(function () {
|
|
if(!line.mv_lines.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 $.when.apply($, defs).then(function() {
|
|
if (account_ids.length) {
|
|
self._rpc({
|
|
model: 'account.account',
|
|
method: 'mark_as_reconciled',
|
|
args: [account_ids],
|
|
});
|
|
}
|
|
if (partner_ids.length) {
|
|
self._rpc({
|
|
model: 'res.partner',
|
|
method: 'mark_as_reconciled',
|
|
args: [partner_ids],
|
|
});
|
|
}
|
|
return {reconciled: reconciled, updated: _.difference(handles, reconciled)};
|
|
});
|
|
});
|
|
},
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Private
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* override change the balance type to display or not the reconcile button
|
|
*
|
|
* @override
|
|
* @private
|
|
* @param {Object} line
|
|
* @returns {Deferred}
|
|
*/
|
|
_computeLine: function (line) {
|
|
return this._super(line).then(function () {
|
|
var props = _.reject(line.reconciliation_proposition, 'invalid');
|
|
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 {Deferred}
|
|
*/
|
|
_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: [],
|
|
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);
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* @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 {Deferred}
|
|
*/
|
|
_performMoveLine: function (handle) {
|
|
var line = this.getLine(handle);
|
|
var excluded_ids = _.compact(_.flatten(_.map(this.lines, function (line) {
|
|
return _.map(line.reconciliation_proposition, function (prop) {
|
|
return !prop.partial_reconcile && _.isNumber(prop.id) ? prop.id : null;
|
|
});
|
|
})));
|
|
var filter = line.filter || "";
|
|
var offset = line.offset;
|
|
var limit = this.limitMoveLines+1;
|
|
var args = [line.account_id.id, line.partner_id, excluded_ids, filter, offset, limit];
|
|
return this._rpc({
|
|
model: 'account.move.line',
|
|
method: 'get_move_lines_for_manual_reconciliation',
|
|
args: args,
|
|
})
|
|
.then(this._formatMoveLine.bind(this, handle));
|
|
},
|
|
});
|
|
|
|
return {
|
|
StatementModel: StatementModel,
|
|
ManualModel: ManualModel,
|
|
};
|
|
});
|