Merge pull request #2352 from odoo-dev/8.0-imp-bank-statement-reconciliation-widget-ama

fixes and improvements in the new bank statement reconciliation widget.
This commit is contained in:
qdp-odoo 2014-09-17 17:37:14 +02:00
commit 3f4ff3e725
6 changed files with 452 additions and 196 deletions

View File

@ -417,6 +417,11 @@ class account_bank_statement(osv.osv):
class account_bank_statement_line(osv.osv):
def create(self, cr, uid, vals, context=None):
if vals.get('amount_currency', 0) and not vals.get('amount', 0):
raise osv.except_osv(_('Error!'), _('If "Amount Currency" is specified, then "Amount" must be as well.'))
return super(account_bank_statement_line, self).create(cr, uid, vals, context=context)
def unlink(self, cr, uid, ids, context=None):
for item in self.browse(cr, uid, ids, context=context):
if item.journal_entry_id:
@ -496,6 +501,7 @@ class account_bank_statement_line(osv.osv):
'account_code': st_line.journal_id.default_debit_account_id.code,
'account_name': st_line.journal_id.default_debit_account_id.name,
'partner_name': st_line.partner_id.name,
'communication_partner_name': st_line.partner_name,
'amount_currency_str': amount_currency_str, # Amount in the statement currency
'has_no_partner': not st_line.partner_id.id,
}
@ -538,15 +544,20 @@ class account_bank_statement_line(osv.osv):
sign = 1
if statement_currency == company_currency:
amount_field = 'credit'
sign = -1
if st_line.amount > 0:
amount_field = 'debit'
else:
sign = -1
else:
amount_field = 'amount_currency'
if st_line.amount < 0:
sign = -1
if st_line.amount_currency:
amount = st_line.amount_currency
else:
amount = st_line.amount
match_id = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, offset=0, limit=1, additional_domain=[(amount_field, '=', (sign * st_line.amount))])
match_id = self.get_move_lines_for_reconciliation(cr, uid, st_line, excluded_ids=excluded_ids, offset=0, limit=1, additional_domain=[(amount_field, '=', (sign * amount))])
if match_id:
return [match_id[0]]
@ -576,36 +587,47 @@ class account_bank_statement_line(osv.osv):
mv_line_pool = self.pool.get('account.move.line')
# Make domain
domain = additional_domain + [('reconcile_id', '=', False),('state', '=', 'valid')]
domain = additional_domain + [('reconcile_id', '=', False), ('state', '=', 'valid'), ('account_id.reconcile', '=', True)]
if st_line.partner_id.id:
domain += [('partner_id', '=', st_line.partner_id.id),
'|', ('account_id.type', '=', 'receivable'),
('account_id.type', '=', 'payable')]
else:
domain += [('account_id.reconcile', '=', True), ('account_id.type', '=', 'other')]
if str:
domain += [('partner_id.name', 'ilike', str)]
domain += [('partner_id', '=', st_line.partner_id.id)]
if excluded_ids:
domain.append(('id', 'not in', excluded_ids))
if str:
domain += ['|', ('move_id.name', 'ilike', str), ('move_id.ref', 'ilike', str)]
if not st_line.partner_id.id:
domain.insert(-1, '|', )
domain.append(('partner_id.name', 'ilike', str))
if str != '/':
domain.insert(-1, '|', )
domain.append(('name', 'ilike', str))
# Get move lines
line_ids = mv_line_pool.search(cr, uid, domain, offset=offset, limit=limit, order="date_maturity asc, id asc", context=context)
lines = mv_line_pool.browse(cr, uid, line_ids, context=context)
# Either return number of lines
if count:
nb_lines = 0
reconcile_partial_ids = [] # for a partial reconciliation, take only one line
# Get move lines ; in case of a partial reconciliation, only consider one line
filtered_lines = []
reconcile_partial_ids = []
actual_offset = offset
while True:
line_ids = mv_line_pool.search(cr, uid, domain, offset=actual_offset, limit=limit, order="date_maturity asc, id asc", context=context)
lines = mv_line_pool.browse(cr, uid, line_ids, context=context)
make_one_more_loop = False
for line in lines:
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
#if we filtered a line because it is partially reconciled with an already selected line, we must do one more loop
#in order to get the right number of items in the pager
make_one_more_loop = True
continue
nb_lines += 1
filtered_lines.append(line)
if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id)
return nb_lines
if not limit or not make_one_more_loop or len(filtered_lines) >= limit:
break
actual_offset = actual_offset + limit
lines = limit and filtered_lines[:limit] or filtered_lines
# Either return number of lines
if count:
return len(lines)
# Or return list of dicts representing the formatted move lines
else:
target_currency = st_line.currency_id or st_line.journal_id.currency or st_line.journal_id.company_id.currency_id
@ -639,6 +661,10 @@ class account_bank_statement_line(osv.osv):
'account_id': account_id
}
def process_reconciliations(self, cr, uid, data, context=None):
for datum in data:
self.process_reconciliation(cr, uid, datum[0], datum[1], context=context)
def process_reconciliation(self, cr, uid, id, mv_line_dicts, context=None):
""" Creates a move line for each item of mv_line_dicts and for the statement line. Reconcile a new move line with its counterpart_move_line_id if specified. Finally, mark the statement line as reconciled by putting the newly created move id in the column journal_entry_id.
@ -699,6 +725,7 @@ class account_bank_statement_line(osv.osv):
mv_line_dict['statement_id'] = st_line.statement_id.id
if mv_line_dict.get('counterpart_move_line_id'):
mv_line = aml_obj.browse(cr, uid, mv_line_dict['counterpart_move_line_id'], context=context)
mv_line_dict['partner_id'] = mv_line.partner_id.id or st_line.partner_id.id
mv_line_dict['account_id'] = mv_line.account_id.id
if st_line_currency.id != company_currency.id:
ctx = context.copy()

View File

@ -766,18 +766,17 @@ class account_move_line(osv.osv):
currency_obj = self.pool.get('res.currency')
company_currency = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.currency_id
rml_parser = report_sxw.rml_parse(cr, uid, 'reconciliation_widget_aml', context=context)
reconcile_partial_ids = [] # for a partial reconciliation, take only one line
ret = []
for line in lines:
if line.reconcile_partial_id and line.reconcile_partial_id.id in reconcile_partial_ids:
continue
partial_reconciliation_siblings_ids = []
if line.reconcile_partial_id:
reconcile_partial_ids.append(line.reconcile_partial_id.id)
partial_reconciliation_siblings_ids = self.search(cr, uid, [('reconcile_partial_id', '=', line.reconcile_partial_id.id)], context=context)
partial_reconciliation_siblings_ids.remove(line.id)
ret_line = {
'id': line.id,
'name': line.move_id.name,
'name': line.name != '/' and line.move_id.name + ': ' + line.name or line.move_id.name,
'ref': line.move_id.ref,
'account_code': line.account_id.code,
'account_name': line.account_id.name,
@ -788,35 +787,59 @@ class account_move_line(osv.osv):
'journal_name': line.journal_id.name,
'partner_id': line.partner_id.id,
'partner_name': line.partner_id.name,
'is_partially_reconciled': bool(line.reconcile_partial_id),
'partial_reconciliation_siblings_ids': partial_reconciliation_siblings_ids,
}
# Amount residual can be negative
debit = line.debit
credit = line.credit
total_amount = abs(debit - credit)
total_amount_currency = line.amount_currency
amount_residual = line.amount_residual
amount_residual_currency = line.amount_residual_currency
if line.amount_residual < 0:
debit, credit = credit, debit
amount_residual = -amount_residual
amount_residual_currency = -amount_residual_currency
# Get right debit / credit:
line_currency = line.currency_id or company_currency
amount_currency_str = ""
total_amount_currency_str = ""
if line.currency_id and line.amount_currency:
amount_currency_str = rml_parser.formatLang(line.amount_currency, currency_obj=line.currency_id)
amount_currency_str = rml_parser.formatLang(amount_residual_currency, currency_obj=line.currency_id)
total_amount_currency_str = rml_parser.formatLang(total_amount_currency, currency_obj=line.currency_id)
if target_currency and line_currency == target_currency and target_currency != company_currency:
debit = line.debit > 0 and line.amount_residual_currency or 0.0
credit = line.credit > 0 and line.amount_residual_currency or 0.0
amount_currency_str = rml_parser.formatLang(line.amount_residual, currency_obj=company_currency)
debit = debit > 0 and amount_residual_currency or 0.0
credit = credit > 0 and amount_residual_currency or 0.0
amount_currency_str = rml_parser.formatLang(amount_residual, currency_obj=company_currency)
total_amount_currency_str = rml_parser.formatLang(total_amount, currency_obj=company_currency)
amount_str = rml_parser.formatLang(debit or credit, currency_obj=target_currency)
total_amount_str = rml_parser.formatLang(total_amount_currency, currency_obj=target_currency)
else:
debit = line.debit > 0 and line.amount_residual or 0.0
credit = line.credit > 0 and line.amount_residual or 0.0
debit = debit > 0 and amount_residual or 0.0
credit = credit > 0 and amount_residual or 0.0
amount_str = rml_parser.formatLang(debit or credit, currency_obj=company_currency)
total_amount_str = rml_parser.formatLang(total_amount, currency_obj=company_currency)
if target_currency and target_currency != company_currency:
amount_currency_str = rml_parser.formatLang(debit or credit, currency_obj=line_currency)
total_amount_currency_str = rml_parser.formatLang(total_amount, currency_obj=line_currency)
ctx = context.copy()
if target_date:
ctx.update({'date': target_date})
debit = currency_obj.compute(cr, uid, target_currency.id, company_currency.id, debit, context=ctx)
credit = currency_obj.compute(cr, uid, target_currency.id, company_currency.id, credit, context=ctx)
debit = currency_obj.compute(cr, uid, company_currency.id, target_currency.id, debit, context=ctx)
credit = currency_obj.compute(cr, uid, company_currency.id, target_currency.id, credit, context=ctx)
amount_str = rml_parser.formatLang(debit or credit, currency_obj=target_currency)
total_amount = currency_obj.compute(cr, uid, company_currency.id, target_currency.id, total_amount, context=ctx)
total_amount_str = rml_parser.formatLang(total_amount, currency_obj=target_currency)
ret_line['credit'] = credit
ret_line['debit'] = debit
ret_line['amount_str'] = amount_str
ret_line['amount_currency_str'] = amount_currency_str
ret_line['total_amount_str'] = total_amount_str # For partial reconciliations
ret_line['total_amount_currency_str'] = total_amount_currency_str
ret.append(ret_line)
return ret

View File

@ -18,17 +18,31 @@
border-top: 0;
border-bottom: 0;
height: 100%; }
.openerp .oe_bank_statement_reconciliation h1 input, .openerp .oe_bank_statement_reconciliation h2 input {
height: auto !important; }
.openerp .oe_bank_statement_reconciliation h1 {
width: 48%;
padding: 0 0 0 15px;
margin: 0 0 25px 0;
float: left;
font-size: 2em; }
font-size: 2em;
height: 1.5em; }
.openerp .oe_bank_statement_reconciliation .statement_name span {
line-height: 1.5em;
cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .change_statement_name_container {
display: none;
width: 95%; }
.openerp .oe_bank_statement_reconciliation .change_statement_name_container td:first-child, .openerp .oe_bank_statement_reconciliation .change_statement_name_container td:first-child > input {
width: 99%; }
.openerp .oe_bank_statement_reconciliation .change_statement_name_container td:last-child {
width: 1%;
padding-left: 3px; }
.openerp .oe_bank_statement_reconciliation h2 {
font-size: 1.8em; }
.openerp .oe_bank_statement_reconciliation .progress {
width: 49%;
margin: 4px 15px 0 0;
margin: 10px 15px 0 0;
float: right;
position: relative;
display: inline-block; }
@ -121,6 +135,12 @@
-ms-transform: rotate(90deg);
-o-transform: rotate(90deg);
transform: rotate(90deg); }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .partner_name .change_partner {
display: none;
cursor: pointer;
margin: 0 10px 0 5px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .partner_name:hover .change_partner {
display: inline; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .change_partner_container {
width: 200px;
display: none;
@ -181,43 +201,44 @@
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view tr .undo_partial_reconcile_button, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match table tr .undo_partial_reconcile_button {
color: #555;
padding-right: 5px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td {
border-top: 1px solid #bbbbbb;
padding-top: 4px;
padding-bottom: 5px;
background-color: #f0f0f0;
-webkit-transition-property: background-color;
-moz-transition-property: background-color;
-ms-transition-property: background-color;
transition-property: background-color;
-webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td.cell_action, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td.cell_info_popover {
border-top: none;
background: white !important;
padding-top: 6px;
padding-bottom: 3px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption {
text-align: left;
font-size: 1.1em;
font-weight: bold;
height: 26px;
margin: 0 15px 4px 15px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption button {
float: right; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption button:disabled {
opacity: 0.5; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > span, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > input {
position: relative;
top: 7px;
/* meh */
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view {
border-collapse: separate; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td {
border-top: 1px solid #bbb;
padding-top: 4px;
padding-bottom: 5px;
background-color: #f0f0f0;
-webkit-transition-property: background-color;
-moz-transition-property: background-color;
-ms-transition-property: background-color;
transition-property: background-color;
-webkit-transition-duration: 300ms;
-moz-transition-duration: 300ms;
-ms-transition-duration: 300ms;
-o-transition-duration: 300ms;
transition-duration: 300ms; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td.cell_action, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view .initial_line > td.cell_info_popover {
border-top: none;
background: white !important;
padding-top: 6px;
padding-bottom: 3px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption {
text-align: left;
font-size: 1.1em;
font-weight: bold;
cursor: pointer; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_credit {
border-left: 1px solid black; }
height: 26px;
margin: 0 15px 4px 15px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption button {
float: right; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption button:disabled {
opacity: 0.5; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > span, .openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view caption > input {
position: relative;
top: 7px;
/* meh */
font-weight: bold; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .accounting_view td.cell_credit {
border-left: 1px solid #000; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls {
padding: 0 0 5px 18px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .match .match_controls .filter {
@ -250,9 +271,11 @@
width: 49%;
height: 26px; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .create .oe_form > table:nth-child(2n+1) {
float: left; }
float: left;
clear: left; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .create .oe_form > table:nth-child(2n) {
float: right; }
float: right;
clear: right; }
.openerp .oe_bank_statement_reconciliation .oe_bank_statement_reconciliation_line .create .oe_form > table th {
font-weight: bold;
line-height: 26px;

View File

@ -31,12 +31,35 @@ $aestetic_animation_speed: 300ms;
}
}
h1 input, h2 input {
height: auto !important;
}
h1 {
width: 48%;
padding: 0 0 0 $actionColWidth;
margin: 0 0 25px 0;
float: left;
font-size: 2em;
height: 1.5em;
}
.statement_name span {
line-height: 1.5em;
cursor: pointer;
}
.change_statement_name_container {
display: none;
width: 95%;
td:first-child, td:first-child > input {
width: 99%;
}
td:last-child {
width: 1%;
padding-left: 3px;
}
}
h2 {
@ -45,7 +68,7 @@ $aestetic_animation_speed: 300ms;
.progress {
width: 49%;
margin: 4px $actionColWidth 0 0;
margin: 10px $actionColWidth 0 0;
float: right;
position: relative;
display: inline-block;
@ -173,6 +196,17 @@ $aestetic_animation_speed: 300ms;
transform: rotate(90deg);
}
.partner_name {
.change_partner {
display: none;
cursor: pointer;
margin: 0 10px 0 5px;
}
&:hover .change_partner {
display: inline;
}
}
.change_partner_container {
width: 200px;
display: none;
@ -308,6 +342,7 @@ $aestetic_animation_speed: 300ms;
/* Partie infos */
.accounting_view {
border-collapse: separate;
.initial_line > td {
border-top: $lightBorder;
@ -351,7 +386,6 @@ $aestetic_animation_speed: 300ms;
> span, > input {
position: relative; top: 7px; /* meh */
font-weight: bold;
cursor: pointer;
}
}
@ -416,8 +450,8 @@ $aestetic_animation_speed: 300ms;
width: 49%;
height: 26px;
&:nth-child(2n+1) { float: left; }
&:nth-child(2n) { float: right; }
&:nth-child(2n+1) { float: left; clear: left; }
&:nth-child(2n) { float: right; clear: right; }
th {
font-weight: bold;

View File

@ -9,12 +9,20 @@ openerp.account = function (instance) {
instance.web.client_actions.add('bank_statement_reconciliation_view', 'instance.web.account.bankStatementReconciliation');
instance.web.account.bankStatementReconciliation = instance.web.Widget.extend({
className: 'oe_bank_statement_reconciliation',
events: {
"click .statement_name span": "statementNameClickHandler",
"keyup .change_statement_name_field": "changeStatementNameFieldHandler",
"click .change_statement_name_button": "changeStatementButtonClickHandler",
},
init: function(parent, context) {
this._super(parent);
this.max_reconciliations_displayed = 10;
if (context.context.statement_id) this.statement_ids = [context.context.statement_id];
if (context.context.statement_ids) this.statement_ids = context.context.statement_ids;
this.single_statement = this.statement_ids !== undefined && this.statement_ids.length === 1;
this.multiple_statements = this.statement_ids !== undefined && this.statement_ids.length > 1;
this.title = context.context.title || _t("Reconciliation");
this.st_lines = [];
this.last_displayed_reconciliation_index = undefined; // Flow control
@ -127,8 +135,8 @@ openerp.account = function (instance) {
if (self.statement_ids && self.statement_ids.length > 0) {
lines_filter.push(['statement_id', 'in', self.statement_ids]);
// If only one statement, retreive its name
if (self.statement_ids.length === 1) {
// If only one statement, display its name as title and allow to modify it
if (self.single_statement) {
deferred_promises.push(self.model_bank_statement
.query(["name"])
.filter([['id', '=', self.statement_ids[0]]])
@ -211,7 +219,11 @@ openerp.account = function (instance) {
});
// Render and display
self.$el.prepend(QWeb.render("bank_statement_reconciliation", {title: self.title, total_lines: self.already_reconciled_lines+self.st_lines.length}));
self.$el.prepend(QWeb.render("bank_statement_reconciliation", {
title: self.title,
single_statement: self.single_statement,
total_lines: self.already_reconciled_lines+self.st_lines.length
}));
self.updateProgressbar();
var reconciliations_to_show = self.st_lines.slice(0, self.max_reconciliations_displayed);
self.last_displayed_reconciliation_index = reconciliations_to_show.length;
@ -222,33 +234,135 @@ openerp.account = function (instance) {
.call("get_data_for_reconciliations", [reconciliations_to_show])
.then(function (data) {
var child_promises = [];
_.each(reconciliations_to_show, function(st_line_id){
var datum = data.shift();
child_promises.push(self.displayReconciliation(st_line_id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition));
});
while ((datum = data.shift()) !== undefined)
child_promises.push(self.displayReconciliation(datum.st_line.id, 'inactive', false, true, datum.st_line, datum.reconciliation_proposition));
$.when.apply($, child_promises).then(function(){
self.getChildren()[0].set("mode", "match");
self.$(".reconciliation_lines_container").animate({opacity: 1}, self.aestetic_animation_speed);
self.getChildren()[0].set("mode", "match");
});
});
});
},
statementNameClickHandler: function() {
if (! this.single_statement) return;
this.$(".statement_name span").hide();
this.$(".change_statement_name_field").attr("value", this.title);
this.$(".change_statement_name_container").show();
this.$(".change_statement_name_field").focus();
},
changeStatementNameFieldHandler: function(e) {
var name = this.$(".change_statement_name_field").val();
if (name === "") this.$(".change_statement_name_button").attr("disabled", "disabled");
else this.$(".change_statement_name_button").removeAttr("disabled");
if (name !== "" && e.which === 13) // Enter
this.$(".change_statement_name_button").trigger("click");
if (e.which === 27) { // Escape
this.$(".statement_name span").show();
this.$(".change_statement_name_container").hide();
}
},
changeStatementButtonClickHandler: function() {
var self = this;
if (! self.single_statement) return;
var name = self.$(".change_statement_name_field").val();
if (name === "") return;
return self.model_bank_statement
.call("write", [[self.statement_ids[0]], {'name': name}])
.then(function () {
self.title = name;
self.$(".statement_name span").text(name).show();
self.$(".change_statement_name_container").hide();
});
},
keyboardShortcutsHandler: function(e) {
var self = this;
if ((e.which === 13 || e.which === 10) && (e.ctrlKey || e.metaKey)) {
$.each(self.getChildren(), function(i, o){
if (o.is_valid && o.persistAndDestroy()) {
self.lines_reconciled_with_ctrl_enter++;
}
});
self.persistReconciliations(_.filter(self.getChildren(), function(o) { return o.is_valid; }));
}
},
persistReconciliations: function(reconciliations) {
if (reconciliations.length === 0) return;
var self = this;
// Prepare data
var data = [];
for (var i=0; i<reconciliations.length; i++) {
var child = reconciliations[i];
data.push([child.st_line_id, child.makeMoveLineDicts()]);
}
var deferred_animation = self.$(".reconciliation_lines_container").fadeOut(self.aestetic_animation_speed);
deferred_rpc = self.model_bank_statement_line.call("process_reconciliations", [data]);
return $.when(deferred_animation, deferred_rpc)
.done(function() {
// Remove children
for (var i=0; i<reconciliations.length; i++) {
var child = reconciliations[i];
self.unexcludeMoveLines(child, child.partner_id, child.get("mv_lines_selected"));
$.each(child.$(".bootstrap_popover"), function(){ $(this).popover('destroy') });
child.destroy();
}
// Update interface
self.lines_reconciled_with_ctrl_enter += reconciliations.length;
self.reconciled_lines += reconciliations.length;
self.updateProgressbar();
self.doReloadMenuReconciliation();
// Display new line if there are left
if (self.last_displayed_reconciliation_index < self.st_lines.length) {
var begin = self.last_displayed_reconciliation_index;
var end = Math.min((begin+self.max_reconciliations_displayed), self.st_lines.length);
var reconciliations_to_show = self.st_lines.slice(begin, end);
return self.model_bank_statement_line
.call("get_data_for_reconciliations", [reconciliations_to_show])
.then(function (data) {
var child_promises = [];
var datum;
while ((datum = data.shift()) !== undefined) {
var context = {
st_line_id: datum.st_line.id,
mode: 'inactive',
animate_entrance: false,
initial_data_provided: true,
st_line: datum.st_line,
reconciliation_proposition: datum.reconciliation_proposition,
};
var widget = new instance.web.account.bankStatementReconciliationLine(self, context);
child_promises.push(widget.appendTo(self.$(".reconciliation_lines_container")));
}
self.last_displayed_reconciliation_index += reconciliations_to_show.length;
return $.when.apply($, child_promises).then(function() {
// Put the first line in match mode
if (self.reconciled_lines !== self.st_lines.length) {
var first_child = self.getChildren()[0];
if (first_child.get("mode") === "inactive") {
first_child.set("mode", "match");
}
}
self.$(".reconciliation_lines_container").fadeIn(self.aestetic_animation_speed);
});
});
}
// Congratulate the user if the work is done
if (self.reconciled_lines === self.st_lines.length) {
self.displayDoneMessage();
}
}).fail(function() {
self.$(".reconciliation_lines_container").fadeIn(self.aestetic_animation_speed);
});
},
// Adds move line ids to the list of move lines not to fetch for a given partner
// This is required because the same move line cannot be selected for multiple reconciliation
excludeMoveLines: function(source_child, partner_id, line_ids) {
// and because for a partial reconciliation only one line can be fetched)
excludeMoveLines: function(source_child, partner_id, lines) {
var self = this;
var line_ids = _.collect(lines, function(o) { return o.id });
var excluded_ids = this.excluded_move_lines_ids[partner_id];
var excluded_move_lines_changed = false;
@ -285,9 +399,10 @@ openerp.account = function (instance) {
});
},
unexcludeMoveLines: function(source_child, partner_id, line_ids) {
unexcludeMoveLines: function(source_child, partner_id, lines) {
var self = this;
var line_ids = _.collect(lines, function(o) { return o.id });
var initial_excluded_lines_num = this.excluded_move_lines_ids[partner_id].length;
this.excluded_move_lines_ids[partner_id] = _.difference(this.excluded_move_lines_ids[partner_id], line_ids);
if (this.excluded_move_lines_ids[partner_id].length === initial_excluded_lines_num)
@ -341,11 +456,36 @@ openerp.account = function (instance) {
}
}
},
goBackToStatementsTreeView: function() {
var self = this;
new instance.web.Model("ir.model.data")
.call("get_object_reference", ['account', 'action_bank_statement_tree'])
.then(function (result) {
var action_id = result[1];
// Warning : altough I don't see why this widget wouldn't be directly instanciated by the
// action manager, if it wasn't, this code wouldn't work. You'd have to do something like :
// var action_manager = self;
// while (! action_manager instanceof ActionManager)
// action_manager = action_manager.getParent();
var action_manager = self.getParent();
var breadcrumbs = action_manager.breadcrumbs;
var found = false;
for (var i=breadcrumbs.length-1; i>=0; i--) {
if (breadcrumbs[i].action && breadcrumbs[i].action.id === action_id) {
var title = breadcrumbs[i].get_title();
action_manager.select_breadcrumb(i, _.isArray(title) ? i : undefined);
found = true;
}
}
if (!found)
instance.web.Home(self);
});
},
displayDoneMessage: function() {
var self = this;
var is_single_statement = self.statement_ids !== undefined && self.statement_ids.length === 1;
var sec_taken = Math.round((Date.now()-self.time_widget_loaded)/1000);
var sec_per_item = Math.round(sec_taken/self.reconciled_lines);
var achievements = [];
@ -381,7 +521,8 @@ openerp.account = function (instance) {
transactions_done: self.reconciled_lines,
done_with_ctrl_enter: self.lines_reconciled_with_ctrl_enter,
achievements: achievements,
has_statement_id: is_single_statement,
single_statement: self.single_statement,
multiple_statements: self.multiple_statements,
}));
// Animate it
@ -392,32 +533,28 @@ openerp.account = function (instance) {
// Make it interactive
self.$(".achievement").popover({'placement': 'top', 'container': self.el, 'trigger': 'hover'});
self.$(".button_back_to_statement").click(function() {
self.do_action({
type: 'ir.actions.client',
tag: 'history_back',
});
});
if (is_single_statement && self.$(".button_close_statement").length !== 0) {
if (self.$(".button_back_to_statement").length !== 0) {
self.$(".button_back_to_statement").click(function() {
self.goBackToStatementsTreeView();
});
}
if (self.$(".button_close_statement").length !== 0) {
self.$(".button_close_statement").hide();
self.model_bank_statement
.query(["balance_end_real", "balance_end"])
.filter([['id', '=', self.statement_ids[0]]])
.first()
.filter([['id', 'in', self.statement_ids]])
.all()
.then(function(data){
if (data.balance_end_real === data.balance_end) {
if (_.all(data, function(o) { return o.balance_end_real === o.balance_end })) {
self.$(".button_close_statement").show();
self.$(".button_close_statement").click(function() {
self.$(".button_close_statement").attr("disabled", "disabled");
self.model_bank_statement
.call("button_confirm_bank", [[self.statement_ids[0]]])
.call("button_confirm_bank", [self.statement_ids])
.then(function () {
self.do_action({
type: 'ir.actions.client',
tag: 'history_back',
});
self.goBackToStatementsTreeView();
}, function() {
self.$(".button_close_statement").removeAttr("disabled");
});
@ -455,7 +592,7 @@ openerp.account = function (instance) {
className: 'oe_bank_statement_reconciliation_line',
events: {
"click .partner_name": "partnerNameClickHandler",
"click .change_partner": "changePartnerClickHandler",
"click .button_ok": "persistAndDestroy",
"click .mv_line": "moveLineClickHandler",
"click .initial_line": "initialLineClickHandler",
@ -485,10 +622,9 @@ openerp.account = function (instance) {
this.decorateStatementLine(this.st_line);
// Exclude selected move lines
var selected_line_ids = _(context.reconciliation_proposition).map(function(o){ return o.id });
if (this.getParent().excluded_move_lines_ids[this.partner_id] === undefined)
this.getParent().excluded_move_lines_ids[this.partner_id] = [];
this.getParent().excludeMoveLines(this, this.partner_id, selected_line_ids);
this.getParent().excludeMoveLines(this, this.partner_id, context.reconciliation_proposition);
} else {
this.set("mv_lines_selected", []);
this.st_line = undefined;
@ -634,12 +770,13 @@ openerp.account = function (instance) {
restart: function(mode) {
var self = this;
mode = (mode === undefined ? 'inactive' : mode);
self.context.animate_entrance = false;
self.$el.css("height", self.$el.outerHeight());
// Destroy everything
_.each(self.getChildren(), function(o){ o.destroy() });
self.is_consistent = false;
return $.when(self.$el.animate({opacity: 0}, self.animation_speed)).then(function() {
self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id }));
self.getParent().unexcludeMoveLines(self, self.partner_id, self.get("mv_lines_selected"));
$.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') });
self.$el.empty();
self.$el.removeClass("no_partner");
@ -786,7 +923,7 @@ openerp.account = function (instance) {
self.change_partner_field.on("change:value", self.change_partner_field, function() {
self.changePartner(this.get_value());
});
self.change_partner_field.$el.find("input").attr("placeholder", _t("Select Partner"));
self.change_partner_field.$el.find("input").attr("placeholder", self.st_line.communication_partner_name || _t("Select Partner"));
field_manager.do_show();
},
@ -875,18 +1012,7 @@ openerp.account = function (instance) {
return;
}
// If statement line has no partner, give it the partner of the selected move line
if (!this.st_line.partner_id && line.partner_id) {
self.changePartner(line.partner_id, function() {
self.selectMoveLine(mv_line);
});
} else {
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line));
// $(mv_line).attr('data-selected','true');
// self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line));
// this.set("mv_lines", _.reject(this.get("mv_lines"), function(o){return o.id == line_id}));
// this.getParent().excludeMoveLines([line_id]);
}
self.set("mv_lines_selected", self.get("mv_lines_selected").concat(line));
},
deselectMoveLine: function(mv_line) {
@ -906,11 +1032,6 @@ openerp.account = function (instance) {
self.$el.removeClass("no_match");
self.set("mode", "match");
self.set("mv_lines_selected", mv_lines_selected);
// $(mv_line).attr('data-selected','false');
// this.set("mv_lines", this.get("mv_lines").concat(line));
// this.getParent().unexcludeMoveLines([line_id]);
},
/** Matches pagination */
@ -1019,20 +1140,18 @@ openerp.account = function (instance) {
lineOpenBalanceClickHandler: function() {
var self = this;
if (self.get("mode") === "create") {
self.addLineBeingEdited();
self.set("mode", "match");
} else {
self.set("mode", "create");
}
},
partnerNameClickHandler: function() {
changePartnerClickHandler: function() {
var self = this;
// Delete statement line's partner
return self.changePartner('', function() {
self.$(".partner_name").hide();
self.$(".change_partner_container").show();
});
self.$(".change_partner_container").find("input").attr("placeholder", self.st_line.partner_name);
self.$(".change_partner_container").show();
self.$(".partner_name").hide();
self.change_partner_field.$drop_down.trigger("click");
},
@ -1158,27 +1277,13 @@ openerp.account = function (instance) {
}
},
modeChanged: function() {
modeChanged: function(o, val) {
var self = this;
self.$(".action_pane.active").removeClass("active");
// Special case hack : if no_partner, either inactive or create
if (self.st_line.has_no_partner) {
if (self.get("mode") === "inactive") {
self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideUp(self.animation_speed);
self.$(".toggle_match").removeClass("visible_toggle");
self.el.dataset.mode = "inactive";
} else {
self.initializeCreateForm();
self.$(".match").slideUp(self.animation_speed);
self.$(".create").slideDown(self.animation_speed);
self.$(".toggle_match").addClass("visible_toggle");
self.el.dataset.mode = "create";
}
return;
}
if (val.oldValue === "create")
self.addLineBeingEdited();
if (self.get("mode") === "inactive") {
self.$(".match").slideUp(self.animation_speed);
@ -1186,14 +1291,21 @@ openerp.account = function (instance) {
self.el.dataset.mode = "inactive";
} else if (self.get("mode") === "match") {
// TODO : remove this old_animation_speed / new_animation_speed hack
// when .on handler's returned deferred's no longer lost
var old_animation_speed = self.animation_speed;
return $.when(self.updateMatches()).then(function() {
var new_animation_speed = self.animation_speed;
self.animation_speed = old_animation_speed;
if (self.$el.hasClass("no_match")) {
self.set("mode", "inactive");
self.animation_speed = 0;
self.set("mode", "create");
return;
}
self.$(".match").slideDown(self.animation_speed);
self.$(".create").slideUp(self.animation_speed);
self.el.dataset.mode = "match";
self.animation_speed = new_animation_speed;
});
} else if (self.get("mode") === "create") {
@ -1224,7 +1336,14 @@ openerp.account = function (instance) {
} else {
self.$el.removeClass("no_match");
}
_.each(self.get("mv_lines"), function(line) {
if (line.partial_reconciliation_siblings_ids.length > 0) {
var correct_format = _.collect(line.partial_reconciliation_siblings_ids, function(o) { return {'id': o} });
self.getParent().excludeMoveLines(self, self.partner_id, correct_format);
}
});
self.updateMatchView();
self.updatePagerControls();
},
@ -1232,11 +1351,11 @@ openerp.account = function (instance) {
mvLinesSelectedChanged: function(elt, val) {
var self = this;
var added_lines_ids = _.map(_.difference(val.newValue, val.oldValue), function(o){ return o.id });
var removed_lines_ids = _.map(_.difference(val.oldValue, val.newValue), function(o){ return o.id });
var added_lines = _.difference(val.newValue, val.oldValue);
var removed_lines = _.difference(val.oldValue, val.newValue);
self.getParent().excludeMoveLines(self, self.partner_id, added_lines_ids);
self.getParent().unexcludeMoveLines(self, self.partner_id, removed_lines_ids);
self.getParent().excludeMoveLines(self, self.partner_id, added_lines);
self.getParent().unexcludeMoveLines(self, self.partner_id, removed_lines);
$.when(self.updateMatches()).then(function(){
self.updateAccountingViewMatchedLines();
@ -1374,10 +1493,9 @@ openerp.account = function (instance) {
var self = this;
var mv_lines_selected = self.get("mv_lines_selected");
var lines_selected_num = mv_lines_selected.length;
var lines_created_num = self.getCreatedLines().length;
// Undo partial reconciliation if necessary
if (lines_selected_num !== 1 || lines_created_num !== 0) {
if (lines_selected_num !== 1) {
_.each(mv_lines_selected, function(line) {
if (line.partial_reconcile === true) self.unpartialReconcileLine(line);
if (line.propose_partial_reconcile === true) line.propose_partial_reconcile = false;
@ -1394,20 +1512,26 @@ openerp.account = function (instance) {
_.each(self.getCreatedLines(), function(o) {
balance += o.amount;
});
// Should work as long as currency's rounding factor is > 0.001 (ie: don't use gold kilos as a currency)
// Dealing with floating-point
balance = Math.round(balance*1000)/1000;
self.set("balance", balance);
// Propose partial reconciliation if necessary
if (lines_selected_num === 1 && lines_created_num === 0 && self.st_line.amount * balance > 0) {
if (lines_selected_num === 1 &&
self.st_line.amount * balance > 0 &&
self.st_line.amount * (mv_lines_selected[0].debit - mv_lines_selected[0].credit) < 0 &&
! mv_lines_selected[0].partial_reconcile) {
mv_lines_selected[0].propose_partial_reconcile = true;
self.updateAccountingViewMatchedLines();
} else if (lines_selected_num === 1) {
mv_lines_selected[0].propose_partial_reconcile = false;
self.updateAccountingViewMatchedLines();
}
},
// Loads move lines according to the widget's state
updateMatches: function() {
if (this.st_line.has_no_partner) return;
var self = this;
var deselected_lines_num = self.mv_lines_deselected.length;
var move_lines_num = 0;
@ -1415,8 +1539,13 @@ openerp.account = function (instance) {
if (offset < 0) offset = 0;
var limit = (self.get("pager_index")+1) * self.max_move_lines_displayed - deselected_lines_num;
if (limit > self.max_move_lines_displayed) limit = self.max_move_lines_displayed;
var excluded_ids = _.collect(self.get("mv_lines_selected").concat(self.mv_lines_deselected), function(o){ return o.id });
excluded_ids = excluded_ids.concat(self.getParent().excluded_move_lines_ids[self.partner_id]);
var excluded_ids = self.getParent().excluded_move_lines_ids[self.partner_id];
var excluded_ids = _.collect(self.get("mv_lines_selected").concat(self.mv_lines_deselected), function(o) { return o.id; });
var globally_excluded_ids = self.getParent().excluded_move_lines_ids[self.partner_id];
if (globally_excluded_ids !== undefined)
for (var i=0; i<globally_excluded_ids.length; i++)
if (excluded_ids.indexOf(globally_excluded_ids[i]) === -1)
excluded_ids.push(globally_excluded_ids[i]);
var deferred_move_lines;
var move_lines = [];
@ -1454,6 +1583,7 @@ openerp.account = function (instance) {
.call("write", [[self.st_line_id], {'partner_id': partner_id}])
.then(function () {
self.do_load_reconciliation_proposition = false; // of the server might set the statement line's partner
self.animation_speed = 0;
return $.when(self.restart(self.get("mode"))).then(function(){
self.do_load_reconciliation_proposition = true;
self.is_consistent = true;
@ -1501,6 +1631,15 @@ openerp.account = function (instance) {
return dict;
},
makeMoveLineDicts: function() {
var self = this;
var mv_line_dicts = [];
_.each(self.get("mv_lines_selected"), function(o) { mv_line_dicts.push(self.prepareSelectedMoveLineForPersisting(o)) });
_.each(self.getCreatedLines(), function(o) { mv_line_dicts.push(self.prepareCreatedMoveLineForPersisting(o)) });
if (Math.abs(self.get("balance")).toFixed(3) !== "0.000") mv_line_dicts.push(self.prepareOpenBalanceForPersisting());
return mv_line_dicts;
},
// Persist data, notify parent view and terminate widget
persistAndDestroy: function(speed) {
@ -1508,8 +1647,6 @@ openerp.account = function (instance) {
speed = (isNaN(speed) ? self.animation_speed : speed);
if (! self.is_consistent) return;
self.getParent().unexcludeMoveLines(self, self.partner_id, _.map(self.get("mv_lines_selected"), function(o){ return o.id }));
// Sliding animation
var height = self.$el.outerHeight();
var container = $("<div />");
@ -1520,8 +1657,10 @@ openerp.account = function (instance) {
var deferred_animation = self.$el.parent().slideUp(speed*height/150);
// RPC
return $.when(self.makeRPCForPersisting())
return self.model_bank_statement_line
.call("process_reconciliation", [self.st_line_id, self.makeMoveLineDicts()])
.then(function () {
self.getParent().unexcludeMoveLines(self, self.partner_id, self.get("mv_lines_selected"));
$.each(self.$(".bootstrap_popover"), function(){ $(this).popover('destroy') });
return $.when(deferred_animation).then(function(){
self.$el.parent().remove();
@ -1536,16 +1675,6 @@ openerp.account = function (instance) {
});
});
},
makeRPCForPersisting: function() {
var self = this;
var mv_line_dicts = [];
_.each(self.get("mv_lines_selected"), function(o) { mv_line_dicts.push(self.prepareSelectedMoveLineForPersisting(o)) });
_.each(self.getCreatedLines(), function(o) { mv_line_dicts.push(self.prepareCreatedMoveLineForPersisting(o)) });
if (Math.abs(self.get("balance")).toFixed(3) !== "0.000") mv_line_dicts.push(self.prepareOpenBalanceForPersisting());
return self.model_bank_statement_line
.call("process_reconciliation", [self.st_line_id, mv_line_dicts]);
},
});
instance.web.views.add('tree_account_reconciliation', 'instance.web.account.ReconciliationListView');

View File

@ -13,7 +13,18 @@
<t t-name="bank_statement_reconciliation">
<div class="oe_form_sheetbg"><div class="oe_form_sheet oe_form_sheet_width">
<h1><t t-esc="title"/></h1>
<t t-if="single_statement">
<h1 class="statement_name">
<span><t t-esc="title"/></span>
<table class="change_statement_name_container oe_form"><tr>
<td><input class="change_statement_name_field" type="text"/></td>
<td><button class="change_statement_name_button oe_highlight">OK</button></td>
</tr></table>
</h1>
</t>
<t t-if="!single_statement">
<h1><t t-esc="title"/></h1>
</t>
<div class="progress progress-striped">
<div class="progress-text"><span class="valuenow">0</span> / <span class="valuemax"><t t-esc="total_lines"/></span></div>
<div class="progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" t-att-aria-valuemax="total_lines" style="width: 0%;">
@ -38,9 +49,9 @@
</t>
</p>
<p class="action_buttons">
<t t-if="has_statement_id">
<button class="button_back_to_statement">Back to statement</button>
<button class="button_close_statement">Close the statement</button>
<t t-if="single_statement or multiple_statements">
<button class="button_back_to_statement">Back to statements list</button>
<button class="button_close_statement">Close the statement<t t-if="multiple_statements">s</t></button>
</t>
</p>
</div>
@ -53,7 +64,10 @@
<table class="accounting_view">
<caption>
<button class="button_ok"></button>
<span t-if="! line.has_no_partner" class="partner_name"><t t-esc="line.partner_name"/></span>
<span t-if="! line.has_no_partner" class="partner_name">
<t t-esc="line.partner_name"/>
<span class="change_partner glyphicon glyphicon-pencil"></span>
</span>
<div class="change_partner_container oe_form"></div>
</caption>
<tbody class="tbody_initial_line">
@ -61,7 +75,9 @@
<td class="cell_action"><span class="toggle_match glyphicon glyphicon-cog"></span></td>
<td class="cell_account_code"><t t-esc="line.account_code"/></td>
<td class="cell_due_date"><t t-esc="line.date"/></td>
<td class="cell_label"><t t-if="line.name" t-esc="line.name"/>
<td class="cell_label">
<t t-if="line.name" t-esc="line.name"/>
<t t-if="line.ref"> : <t t-esc="line.ref" /></t>
<t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t></td>
<td class="cell_debit"><t t-if="line.amount &gt; 0">
<t t-esc="line.amount_str"/></t></td>
@ -134,22 +150,26 @@
</tr>
</t>
<t t-name="icon_do_partial_reconciliation"><i class="do_partial_reconcile_button fa fa-exclamation-triangle" data-content="This move's amount is higher than the transaction's amount. Click to do a partial reconciliation"></i></t>
<t t-name="icon_do_partial_reconciliation"><i class="do_partial_reconcile_button fa fa-exclamation-triangle" data-content="Click to register a partial payment and keep the invoice open. Otherwise it does a full reconciliation."></i></t>
<t t-name="icon_undo_partial_reconciliation"><i class="undo_partial_reconcile_button fa fa-exclamation-triangle" data-content="Undo the partial reconciliation."></i></t>
<t t-name="bank_statement_reconciliation_move_line_details">
<table class='details'>
<tr><td>id</td><td><t t-esc="line.id" /></td></tr>
<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>Journal</td><td><t t-esc="line.journal_name"/></td></tr>
<tr><td>Period</td><td><t t-esc="line.period_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.q_due_date"/></td></tr>
<tr><td>Amount</td>
<td><t t-if="line.debit !== 0" t-esc="line.debit_str"/><t t-if="line.credit !== 0" t-esc="line.credit_str"/>
<t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t>
<t t-if="line.is_partially_reconciled">
<tr><td>Total</td><td>
<t t-esc="line.total_amount_str"/><t t-if="line.total_amount_currency_str"> (<t t-esc="line.total_amount_currency_str"/>)</t>
</td></tr>
</t>
<tr><td><t t-if="line.is_partially_reconciled">Residual</t><t t-if="! line.is_partially_reconciled">Amount</t></td><td>
<t t-if="line.debit !== 0" t-esc="line.debit_str"/><t t-if="line.credit !== 0" t-esc="line.credit_str"/>
<t t-if="line.amount_currency_str"> (<t t-esc="line.amount_currency_str"/>)</t>
</td></tr>
</table>
</t>