+
+
+
diff --git a/account_reconciliation_widget/static/tests/account_reconciliation_tests.js b/account_reconciliation_widget/static/tests/account_reconciliation_tests.js
new file mode 100644
index 00000000..e97eb10d
--- /dev/null
+++ b/account_reconciliation_widget/static/tests/account_reconciliation_tests.js
@@ -0,0 +1,4922 @@
+odoo.define("account.reconciliation_tests.data", function () {
+ "use strict";
+
+ /*
+ * Debug tip:
+ * To be able to "see" the test in the browser:
+ * var $body = $('body');
+ * $body.addClass('debug');
+ * clientAction.appendTo($body);
+ */
+
+ var Datas = {};
+
+ var db = {
+ "res.company": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ display_name: {string: "Displayed name", type: "char"},
+ },
+ records: [{id: 1, display_name: "company 1"}],
+ },
+ "res.partner": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ display_name: {string: "Displayed name", type: "char"},
+ image: {string: "image", type: "integer"},
+ parent_id: {string: "Parent", type: "boolean"},
+ is_company: {string: "Is company", type: "boolean"},
+ property_account_receivable_id: {
+ string: "Account receivable",
+ type: "many2one",
+ relation: "account.account",
+ },
+ property_account_payable_id: {
+ string: "Account payable",
+ type: "many2one",
+ relation: "account.account",
+ },
+ },
+ records: [
+ {id: 1, display_name: "partner 1", image: "AAA"},
+ {id: 2, display_name: "partner 2", image: "BBB"},
+ {id: 3, display_name: "partner 3", image: "CCC"},
+ {id: 4, display_name: "partner 4", image: "DDD"},
+ {id: 8, display_name: "Agrolait", image: "EEE"},
+ {
+ id: 12,
+ display_name: "Camptocamp",
+ image: "FFF",
+ property_account_receivable_id: 287,
+ property_account_payable_id: 287,
+ },
+ // Add more to have 'Search More' option
+ {id: 98, display_name: "partner 98", image: "YYY"},
+ {id: 99, display_name: "partner 99", image: "ZZZ"},
+ ],
+ mark_as_reconciled: function () {
+ return Promise.resolve();
+ },
+ },
+ "account.account": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ code: {string: "code", type: "integer"},
+ name: {string: "Displayed name", type: "char"},
+ company_id: {
+ string: "Company",
+ type: "many2one",
+ relation: "res.company",
+ },
+ deprecated: {string: "Deprecated", type: "boolean"},
+ },
+ records: [
+ {
+ id: 282,
+ code: 100000,
+ name: "100000 Fixed Asset Account",
+ company_id: 1,
+ },
+ {id: 283, code: 101000, name: "101000 Current Assets", company_id: 1},
+ {
+ id: 284,
+ code: 101110,
+ name: "101110 Stock Valuation Account",
+ company_id: 1,
+ },
+ {
+ id: 285,
+ code: 101120,
+ name: "101120 Stock Interim Account (Received)",
+ company_id: 1,
+ },
+ {
+ id: 286,
+ code: 101130,
+ name: "101130 Stock Interim Account (Delivered)",
+ company_id: 1,
+ },
+ {
+ id: 287,
+ code: 101200,
+ name: "101200 Account Receivable",
+ company_id: 1,
+ },
+ {id: 288, code: 101300, name: "101300 Tax Paid", company_id: 1},
+ {id: 308, code: 101401, name: "101401 Bank", company_id: 1},
+ {id: 499, code: 499001, name: "499001 Suspense Account", company_id: 1},
+ {id: 500, code: 500, name: "500 Account", company_id: 1},
+ {id: 501, code: 501, name: "501 Account", company_id: 1},
+ {id: 502, code: 502, name: "502 Account", company_id: 1},
+ {id: 503, code: 503, name: "503 Account", company_id: 1},
+ {id: 504, code: 504, name: "504 Account", company_id: 1},
+ ],
+ mark_as_reconciled: function () {
+ return Promise.resolve();
+ },
+ },
+ "account.account.tag": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ },
+ records: [{id: 1}, {id: 2}, {id: 3}, {id: 4}],
+ },
+ "account.tax.repartition.line": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ repartition_type: {string: "Repartition Type", type: "selection"},
+ account_id: {
+ string: "Account",
+ type: "many2one",
+ relation: "account.account",
+ },
+ factor_percent: {string: "%", type: "integer"},
+ tag_ids: {
+ string: "Tax Grids",
+ type: "many2many",
+ relation: "account.account.tag",
+ },
+ },
+ records: [
+ {id: 1, factor_percent: 100, repartition_type: "base", tag_ids: [1]},
+ {id: 2, factor_percent: 100, repartition_type: "tax", tag_ids: [2]},
+ {id: 3, factor_percent: 100, repartition_type: "base", tag_ids: [3]},
+ {
+ id: 4,
+ factor_percent: 100,
+ repartition_type: "tax",
+ tag_ids: [4],
+ account_id: 288,
+ },
+ ],
+ },
+ "account.tax": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ display_name: {string: "Displayed name", type: "char"},
+ amount: {string: "amout", type: "float"},
+ price_include: {string: "Included in Price", type: "boolean"},
+ company_id: {
+ string: "Company",
+ type: "many2one",
+ relation: "res.company",
+ },
+ amount_type: {string: "type", type: "selection"},
+ invoice_repartition_line_ids: {
+ string: "Invoice Repartition",
+ type: "one2many",
+ relation: "account.tax.repartition.line",
+ },
+ // No need for refund repartition lines in our test; they're not used by reconciliation widget anyway
+ },
+ records: [
+ {
+ id: 6,
+ display_name: "Tax 20.00%",
+ amount: 20,
+ amount_type: "percent",
+ price_include: false,
+ company_id: 1,
+ invoice_repartition_line_ids: [1, 2],
+ },
+ {
+ id: 7,
+ display_name: "Tax 10.00% include",
+ amount: 10,
+ amount_type: "percent",
+ price_include: true,
+ company_id: 1,
+ invoice_repartition_line_ids: [3, 4],
+ },
+ ],
+ json_friendly_compute_all: function (args) {
+ var tax = _.find(db["account.tax"].records, {id: args[0][0]});
+ var amount = args[1];
+
+ var tax_base = null;
+ var base_tags = null;
+ var taxes = [];
+
+ for (let i = 0; i < tax.invoice_repartition_line_ids.length; i++) {
+ var rep_ln = _.find(db["account.tax.repartition.line"].records, {
+ id: tax.invoice_repartition_line_ids[i],
+ });
+
+ if (rep_ln.repartition_type == "base") {
+ tax_base =
+ (tax.price_include
+ ? (amount * 100) / (100 + tax.amount)
+ : amount) *
+ (rep_ln.factor_percent / 100);
+ base_tags = rep_ln.tag_ids;
+ } else if (rep_ln.repartition_type == "tax") {
+ /*
+ IMPORTANT :
+ For simplicity of testing, we assume there is ALWAYS a
+ base repartition line before the tax one, so tax_base is non-null
+ */
+ taxes.push({
+ id: tax.id,
+ amount: (tax_base * tax.amount) / 100,
+ base: tax_base,
+ name: tax.display_name,
+ analytic: false,
+ account_id: rep_ln.account_id,
+ price_include: tax.price_include,
+ tax_repartition_line_id: rep_ln.id,
+ tag_ids: rep_ln.tag_ids,
+ tax_ids: [tax.id],
+ });
+ }
+ }
+
+ return Promise.resolve({
+ base: amount,
+ taxes: taxes,
+ base_tags: base_tags,
+ total_excluded: (amount / 100) * (100 - tax.amount),
+ total_included: amount,
+ });
+ },
+ },
+ "account.journal": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ display_name: {string: "Displayed name", type: "char"},
+ company_id: {
+ string: "Company",
+ type: "many2one",
+ relation: "res.company",
+ },
+ },
+ records: [{id: 8, display_name: "company 1 journal", company_id: 1}],
+ },
+ "account.analytic.account": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ display_name: {string: "Displayed name", type: "char"},
+ },
+ records: [
+ {id: 16, display_name: "Administrative"},
+ {id: 7, display_name: "Agrolait - Agrolait"},
+ {id: 8, display_name: "Asustek - ASUSTeK"},
+ {id: 15, display_name: "Camp to Camp - Camptocamp"},
+ {id: 6, display_name: "CampToCamp - Camptocamp"},
+ {id: 17, display_name: "Commercial & Marketing"},
+ {id: 23, display_name: "Data Import/Export Plugin - Delta PC"},
+ {id: 9, display_name: "Delta PC - Delta PC"},
+ ],
+ },
+ "account.analytic.tag": {
+ fields: {
+ id: {string: "id", type: "integer"},
+ display_name: {string: "display_name", type: "char"},
+ },
+ records: [
+ {id: 1, display_name: "Come together"},
+ {id: 2, display_name: "Right now"},
+ ],
+ },
+ "account.bank.statement": {
+ fields: {},
+ },
+ "account.bank.statement.line": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ display_name: {string: "Displayed name", type: "char"},
+ partner_id: {
+ string: "partner",
+ type: "many2one",
+ relation: "res.partner",
+ },
+ company_id: {
+ string: "Company",
+ type: "many2one",
+ relation: "res.company",
+ },
+ },
+ records: [
+ {id: 5, display_name: "SAJ/2014/002 and SAJ/2014/003", company_id: 1},
+ {id: 6, display_name: "Bank fees", company_id: 1},
+ {id: 7, display_name: "Prepayment", company_id: 1},
+ {
+ id: 8,
+ display_name: "First 2000 \u20ac of SAJ/2014/001",
+ company_id: 1,
+ },
+ ],
+ },
+ "account.move.line": {
+ fields: {},
+ },
+ "account.reconcile.model": {
+ fields: {
+ id: {string: "ID", type: "integer"},
+ name: {string: "Button Label", type: "char"},
+ rule_type: {
+ string: "Type",
+ type: "selection",
+ selection: [
+ ["writeoff_button", "Create a Button"],
+ ["writeoff_suggestion", "Write off Suggestion"],
+ ["invoice_matching", "Invoice matching"],
+ ],
+ default: "writeoff_button",
+ },
+ has_second_line: {string: "Add a second line", type: "boolean"},
+ account_id: {
+ string: "Account",
+ type: "many2one",
+ relation: "account.account",
+ },
+ journal_id: {
+ string: "Journal",
+ type: "many2one",
+ relation: "account.journal",
+ },
+ label: {string: "Journal Item Label", type: "char"},
+ amount_type: {
+ string: "amount_type",
+ type: "selection",
+ selection: [
+ ["fixed", "Fixed"],
+ ["percentage", "Percentage of balance"],
+ ],
+ default: "percentage",
+ },
+ amount: {
+ string: "Amount",
+ type: "float",
+ digits: 0,
+ help:
+ "Fixed amount will count as a debit if it is negative, as a credit if it is positive.",
+ default: 100.0,
+ },
+ tax_ids: {string: "Tax", type: "many2many", relation: "account.tax"},
+ analytic_account_id: {
+ string: "Analytic Account",
+ type: "many2one",
+ relation: "account.analytic.account",
+ },
+ second_account_id: {
+ string: "Second Account",
+ type: "many2one",
+ relation: "account.account",
+ domain: [("deprecated", "=", false)],
+ },
+ second_journal_id: {
+ string: "Second Journal",
+ type: "many2one",
+ relation: "account.journal",
+ help: "This field is ignored in a bank statement reconciliation.",
+ },
+ second_label: {string: "Second Journal Item Label", type: "char"},
+ second_amount_type: {
+ string: "Second amount_type",
+ type: "selection",
+ selection: [
+ ["fixed", "Fixed"],
+ ["percentage", "Percentage of balance"],
+ ],
+ default: "percentage",
+ },
+ second_amount: {
+ string: "Second Amount",
+ type: "float",
+ digits: 0,
+ help:
+ "Fixed amount will count as a debit if it is negative, as a credit if it is positive.",
+ default: 100.0,
+ },
+ second_tax_ids: {
+ string: "Second Tax",
+ type: "many2many",
+ relation: "account.tax",
+ },
+ second_analytic_account_id: {
+ string: "Second Analytic Account",
+ type: "many2one",
+ relation: "account.analytic.account",
+ },
+ match_journal_ids: {
+ string: "Journal Ids",
+ type: "many2many",
+ relation: "account.journal",
+ },
+ analytic_tag_ids: {
+ string: "Analytic tags",
+ type: "many2many",
+ relation: "account.analytic.tag",
+ },
+ },
+ records: [
+ {
+ second_analytic_account_id: false,
+ second_amount_type: "percentage",
+ second_journal_id: false,
+ id: 4,
+ analytic_account_id: false,
+ display_name: "Int\u00e9rrets",
+ rule_type: "writeoff_button",
+ second_tax_ids: [],
+ has_second_line: false,
+ journal_id: false,
+ label: false,
+ second_label: false,
+ second_account_id: false,
+ account_id: 282,
+ company_id: [1, "Demo SPRL"],
+ tax_ids: [],
+ amount_type: "fixed",
+ name: "Int\u00e9rrets",
+ amount: 0.0,
+ second_amount: 100.0,
+ match_journal_ids: [],
+ },
+ {
+ second_analytic_account_id: false,
+ second_amount_type: "percentage",
+ second_journal_id: false,
+ id: 2,
+ analytic_account_id: false,
+ display_name: "Perte et Profit",
+ rule_type: "writeoff_button",
+ second_tax_ids: [],
+ has_second_line: false,
+ journal_id: false,
+ label: false,
+ second_label: false,
+ second_account_id: false,
+ account_id: 283,
+ company_id: [1, "Demo SPRL"],
+ tax_ids: [],
+ amount_type: "percentage",
+ name: "Perte et Profit",
+ amount: 100.0,
+ second_amount: 100.0,
+ match_journal_ids: [],
+ },
+ {
+ second_analytic_account_id: false,
+ second_amount_type: "percentage",
+ second_journal_id: false,
+ id: 5,
+ analytic_account_id: false,
+ display_name: "Fs bank",
+ rule_type: "writeoff_button",
+ second_tax_ids: [],
+ has_second_line: false,
+ journal_id: false,
+ label: false,
+ second_label: false,
+ second_account_id: false,
+ account_id: 284,
+ company_id: [1, "Demo SPRL"],
+ tax_ids: [],
+ amount_type: "percentage",
+ name: "Fs bank",
+ amount: 100.0,
+ second_amount: 100.0,
+ },
+ {
+ second_analytic_account_id: false,
+ second_amount_type: "percentage",
+ second_journal_id: false,
+ id: 8,
+ analytic_account_id: false,
+ display_name: "Caisse Sand.",
+ rule_type: "writeoff_button",
+ second_tax_ids: [],
+ has_second_line: false,
+ journal_id: false,
+ label: "Caisse Sand.",
+ second_label: false,
+ second_account_id: false,
+ account_id: 308,
+ company_id: [1, "Demo SPRL"],
+ tax_ids: [],
+ amount_type: "percentage",
+ name: "Caisse Sand.",
+ amount: 100.0,
+ second_amount: 100.0,
+ match_journal_ids: [],
+ },
+ {
+ second_analytic_account_id: false,
+ second_amount_type: "percentage",
+ second_journal_id: false,
+ id: 3,
+ analytic_account_id: false,
+ display_name: "ATOS",
+ rule_type: "writeoff_button",
+ second_tax_ids: [7],
+ has_second_line: true,
+ journal_id: false,
+ label: "ATOS Banque",
+ second_label: "ATOS Frais",
+ second_account_id: 286,
+ account_id: 285,
+ company_id: [1, "Demo SPRL"],
+ tax_ids: [6],
+ amount_type: "percentage",
+ name: "ATOS",
+ amount: 97.5,
+ second_amount: -14.75,
+ },
+ {
+ second_analytic_account_id: false,
+ second_amount_type: "percentage",
+ second_journal_id: false,
+ id: 10,
+ analytic_account_id: false,
+ display_name: "Double",
+ rule_type: "writeoff_button",
+ second_tax_ids: [],
+ has_second_line: true,
+ journal_id: false,
+ label: "Double Banque",
+ second_label: "Double Frais",
+ second_account_id: 286,
+ account_id: 285,
+ company_id: [1, "Demo SPRL"],
+ tax_ids: [],
+ amount_type: "percentage",
+ name: "Double",
+ amount: 97.5,
+ second_amount: 100,
+ match_journal_ids: [],
+ analytic_tag_ids: [1, 2],
+ },
+ ],
+ },
+ "account.reconciliation.widget": {
+ fields: {},
+ auto_reconcile: function () {
+ return Promise.resolve(Datas.used.auto_reconciliation);
+ },
+ process_bank_statement_line: function (args) {
+ var datas = args[1];
+ var ids = _.flatten(
+ _.pluck(
+ _.pluck(datas, "counterpart_aml_dicts"),
+ "counterpart_aml_id"
+ )
+ );
+ ids = ids.concat(_.flatten(_.pluck(datas, "payment_aml_ids")));
+ ids = _.compact(ids);
+
+ for (var key in Datas.used.move_lines_for_manual_reconciliation) {
+ Datas.used.move_lines_for_manual_reconciliation[key] = _.filter(
+ Datas.used.move_lines_for_manual_reconciliation[key],
+ function (mv_line) {
+ return ids.indexOf(mv_line.id) === -1;
+ }
+ );
+ }
+ return Promise.resolve();
+ },
+ get_move_lines_for_bank_statement_line: function (args) {
+ var partner_id = args.splice(1, 1)[0];
+ var excluded_ids = args.splice(1, 1)[0];
+ var mode = args.splice(-1, 1)[0];
+ if (mode === "other") return Promise.resolve([]);
+ args.splice(-1, 1); // Ignore limit
+ var key = JSON.stringify(args);
+ if (!Datas.used.mv_lines[key]) {
+ throw new Error(
+ "Unknown parameters for get_move_lines_for_bank_statement_line: '" +
+ key +
+ "'"
+ );
+ }
+ var lines = Datas.used.mv_lines[key]
+ .filter(function (line) {
+ return (
+ excluded_ids.indexOf(line.id) === -1 &&
+ (!partner_id || partner_id === line.partner_id)
+ );
+ })
+ .map(function (line, i, src) {
+ line.recs_count = src.length;
+ return line;
+ })
+ .slice(0, options.params.limitMoveLines);
+ return Promise.resolve(lines);
+ },
+ get_bank_statement_line_data: function (args) {
+ var ids = args[0];
+ var results = {
+ value_min: 0,
+ value_max: ids.length,
+ lines: _.filter(Datas.used.data_widget, function (w) {
+ return _.contains(ids, w.st_line.id);
+ }),
+ };
+ return Promise.resolve(results);
+ },
+ get_bank_statement_data: function () {
+ var results = Datas.used.data_preprocess;
+ results.lines = _.filter(Datas.used.data_widget, function (w) {
+ return _.contains(results.st_lines_ids, w.st_line.id);
+ });
+ return Promise.resolve(results);
+ },
+ get_move_lines_for_manual_reconciliation: function (args) {
+ var excluded_ids = args.splice(2, 1)[0];
+ args.splice(-1, 1); // Ignore limit
+ var key = JSON.stringify(args);
+ if (!Datas.used.move_lines_for_manual_reconciliation[key]) {
+ throw new Error(
+ "Unknown parameters for get_move_lines_for_manual_reconciliation: '" +
+ key +
+ "'"
+ );
+ }
+ var lines = Datas.used.move_lines_for_manual_reconciliation[key]
+ .filter(function (line) {
+ return excluded_ids.indexOf(line.id) === -1;
+ })
+ .map(function (line, i, src) {
+ line.recs_count = src.length;
+ return line;
+ })
+ .slice(0, options.params.limitMoveLines);
+ return Promise.resolve(lines);
+ },
+ get_all_data_for_manual_reconciliation: function (args) {
+ var key = JSON.stringify(args);
+ if (!Datas.used.data_for_manual_reconciliation_widget[key]) {
+ throw new Error(
+ "Unknown parameters for get_all_data_for_manual_reconciliation: '" +
+ key +
+ "'"
+ );
+ }
+ return Promise.resolve(
+ Datas.used.data_for_manual_reconciliation_widget[key]
+ );
+ },
+ process_move_lines: function (args) {
+ var datas = args[0];
+ for (var i in datas) {
+ var data = datas[i];
+ for (var key in Datas.used.move_lines_for_manual_reconciliation) {
+ Datas.used.move_lines_for_manual_reconciliation[key] = _.filter(
+ Datas.used.move_lines_for_manual_reconciliation[key],
+ function (mv_line) {
+ return data.mv_line_ids.indexOf(mv_line.id) === -1;
+ }
+ );
+ }
+ }
+ return Promise.resolve();
+ },
+ },
+ };
+
+ var data_preprocess = {
+ value_min: 0,
+ value_max: 4,
+ notifications: [],
+ num_already_reconciled_lines: 0,
+ st_lines_ids: [5, 6, 7, 8],
+ statement_name: "BNK/2014/001",
+ };
+
+ var data_widget = [
+ {
+ st_line: {
+ currency_id: 3,
+ communication_partner_name: false,
+ open_balance_account_id: 287,
+ name: "SAJ/2014/002 and SAJ/2014/003",
+ partner_name: "Agrolait",
+ partner_id: 8,
+ has_no_partner: false,
+ journal_id: 84,
+ account_name: "Bank",
+ note: "",
+ amount: 1175.0,
+ amount_str: "$ 1,175.00",
+ amount_currency_str: "",
+ date: "2017-01-01",
+ account_code: "101401",
+ ref: "",
+ id: 5,
+ statement_id: 2,
+ company_id: 1,
+ },
+ reconciliation_proposition: [],
+ },
+ {
+ st_line: {
+ currency_id: 3,
+ communication_partner_name: false,
+ name: "Bank fees",
+ partner_name: false,
+ partner_id: false,
+ has_no_partner: true,
+ journal_id: 84,
+ account_name: "Bank",
+ note: "",
+ amount: -32.58,
+ amount_str: "$ 32.58",
+ amount_currency_str: "",
+ date: "2017-01-01",
+ account_code: "101401",
+ ref: "",
+ id: 6,
+ statement_id: 2,
+ company_id: 1,
+ },
+ reconciliation_proposition: [],
+ },
+ {
+ st_line: {
+ currency_id: 3,
+ communication_partner_name: false,
+ open_balance_account_id: 287,
+ name: "Prepayment",
+ partner_name: "Camptocamp",
+ partner_id: 12,
+ has_no_partner: false,
+ journal_id: 84,
+ account_name: "Bank",
+ note: "",
+ amount: 650.0,
+ amount_str: "$ 650.00",
+ amount_currency_str: "",
+ date: "2017-01-01",
+ account_code: "101401",
+ ref: "",
+ id: 7,
+ statement_id: 2,
+ company_id: 1,
+ },
+ reconciliation_proposition: [
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0012",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 133,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 650.00",
+ debit: 650.0,
+ account_id: [287, "101200 Account Receivable"],
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ },
+ {
+ st_line: {
+ currency_id: 3,
+ communication_partner_name: false,
+ open_balance_account_id: 285,
+ name: "First 2000 \u20ac of SAJ/2014/001",
+ partner_name: "Camptocamp",
+ partner_id: 12,
+ has_no_partner: false,
+ journal_id: 84,
+ account_name: "Bank",
+ note: "",
+ amount: 2000.0,
+ amount_str: "$ 2,000.00",
+ amount_currency_str: "",
+ date: "2017-01-01",
+ account_code: "101401",
+ ref: "",
+ id: 8,
+ statement_id: 2,
+ company_id: 1,
+ },
+ reconciliation_proposition: [],
+ },
+ ];
+
+ var mv_lines = {
+ "[]": [],
+ '[5,"",0]': [
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 109,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 650.00",
+ debit: 650.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 525.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 112,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 525.00",
+ debit: 525.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0012",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 134,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 650.00",
+ debit: 650.0,
+ account_id: [287, "101200 Account Receivable"],
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 4,610.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 106,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 4,610.00",
+ debit: 4610.0,
+ account_id: [287, "101200 Account Receivable"],
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "payable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 10,000.00",
+ partner_id: 12,
+ account_name: "Account Payable",
+ name: "BILL/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 114,
+ credit: 10000.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 10,000.00",
+ debit: 0.0,
+ account_id: [284, "101110 Stock Valuation Account"],
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ '[5,"b",0]': [
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 100.00",
+ partner_id: 8,
+ account_name: "Bank",
+ name: "BNK1/2017/0003: CUST.IN/2017/0001",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 394,
+ credit: 0.0,
+ journal_id: "Bank",
+ amount_str: "$ 100.00",
+ debit: 100.0,
+ account_code: "101401",
+ ref: "",
+ already_paid: true,
+ },
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 525.50",
+ partner_id: 8,
+ account_name: "Bank",
+ name: "BNK1/2017/0004: CUST.IN/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 396,
+ credit: 0.0,
+ journal_id: "Bank",
+ amount_str: "$ 525.50",
+ debit: 525.5,
+ account_code: "101401",
+ ref: "INV/2017/0003",
+ already_paid: true,
+ },
+ ],
+ '[6,"",0]': [
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 376.00",
+ partner_id: 7,
+ account_name: "Bank",
+ name: "BNK1/2017/0002: SUPP.OUT/2017/0002",
+ partner_name: "ASUSTeK",
+ total_amount_currency_str: "",
+ id: 392,
+ credit: 376.0,
+ journal_id: "Bank",
+ amount_str: "$ 376.00",
+ debit: 0.0,
+ account_code: "101401",
+ ref: "BILL/2017/0003",
+ already_paid: true,
+ },
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 100.00",
+ partner_id: 8,
+ account_name: "Bank",
+ name: "BNK1/2017/0003: CUST.IN/2017/0001",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 394,
+ credit: 0.0,
+ journal_id: "Bank",
+ amount_str: "$ 100.00",
+ debit: 100.0,
+ account_code: "101401",
+ ref: "",
+ already_paid: true,
+ },
+ {
+ account_type: "payable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 10,000.00",
+ partner_id: 12,
+ account_name: "Account Payable",
+ name: "BILL/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 114,
+ credit: 10000.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 10,000.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 525.50",
+ partner_id: 8,
+ account_name: "Bank",
+ name: "BNK1/2017/0004: CUST.IN/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 396,
+ credit: 0.0,
+ journal_id: "Bank",
+ amount_str: "$ 525.50",
+ debit: 525.5,
+ account_code: "101401",
+ ref: "INV/2017/0003",
+ already_paid: true,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 109,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 650.00",
+ debit: 650.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-22",
+ date: "2017-01-23",
+ total_amount_str: "$ 525.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0004",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 399,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 525.00",
+ debit: 525.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 4,610.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 106,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 4,610.00",
+ debit: 4610.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "payable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-15",
+ total_amount_str: "$ 5,749.99",
+ partner_id: 7,
+ account_name: "Account Payable",
+ name: "BILL/2017/0002",
+ partner_name: "ASUSTeK",
+ total_amount_currency_str: "",
+ id: 117,
+ credit: 5749.99,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 5,749.99",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ '[6,"",5]': [
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 4,610.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 106,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 4,610.00",
+ debit: 4610.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "payable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 10,000.00",
+ partner_id: 12,
+ account_name: "Account Payable",
+ name: "BILL/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 114,
+ credit: 10000.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 10,000.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "payable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-15",
+ total_amount_str: "$ 5,749.99",
+ partner_id: 7,
+ account_name: "Account Payable",
+ name: "BILL/2017/0002",
+ partner_name: "ASUSTeK",
+ total_amount_currency_str: "",
+ id: 117,
+ credit: 5749.99,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 5,749.99",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ '[7,"",0]': [
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0012",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 133,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 650.00",
+ debit: 650.0,
+ account_id: [287, "101200 Account Receivable"],
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 4,610.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 106,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 4,610.00",
+ debit: 4610.0,
+ account_id: [287, "101200 Account Receivable"],
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "payable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-28",
+ date: "2017-01-01",
+ total_amount_str: "$ 10,000.00",
+ partner_id: 12,
+ account_name: "Account Payable",
+ name: "BILL/2017/0001",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 114,
+ credit: 10000.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 10,000.00",
+ debit: 0.0,
+ account_id: [284, "101110 Stock Valuation Account"],
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 376.00",
+ partner_id: 7,
+ account_name: "Bank",
+ name: "BNK1/2017/0002: SUPP.OUT/2017/0002",
+ partner_name: "ASUSTeK",
+ total_amount_currency_str: "",
+ id: 392,
+ credit: 376.0,
+ journal_id: "Bank",
+ amount_str: "$ 376.00",
+ debit: 0.0,
+ account_code: "101401",
+ ref: "BILL/2017/0003",
+ already_paid: true,
+ },
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 100.00",
+ partner_id: 8,
+ account_name: "Bank",
+ name: "BNK1/2017/0003: CUST.IN/2017/0001",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 394,
+ credit: 0.0,
+ journal_id: "Bank",
+ amount_str: "$ 100.00",
+ debit: 100.0,
+ account_code: "101401",
+ ref: "",
+ already_paid: true,
+ },
+ {
+ account_type: "liquidity",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-01-23",
+ date: "2017-01-23",
+ total_amount_str: "$ 525.50",
+ partner_id: 8,
+ account_name: "Bank",
+ name: "BNK1/2017/0004: CUST.IN/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 396,
+ credit: 0.0,
+ journal_id: "Bank",
+ amount_str: "$ 525.50",
+ debit: 525.5,
+ account_code: "101401",
+ ref: "INV/2017/0003",
+ already_paid: true,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 109,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 650.00",
+ debit: 650.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-22",
+ date: "2017-01-23",
+ total_amount_str: "$ 525.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0004",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 399,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 525.00",
+ debit: 525.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ '[8,"",0]': [],
+ };
+
+ var auto_reconciliation = {
+ num_already_reconciled_lines: 1,
+ notifications: [
+ {
+ message: "1 transaction was automatically reconciled.",
+ type: "info",
+ details: {
+ model: "account.move",
+ name: "Automatically reconciled items",
+ ids: [143],
+ },
+ },
+ ],
+ st_lines_ids: [5, 6, 8],
+ statement_name: false,
+ };
+
+ var data_for_manual_reconciliation_widget = {
+ "[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]": {
+ customers: [
+ {
+ account_id: 287,
+ partner_name: "Agrolait",
+ reconciliation_proposition: [],
+ currency_id: 3,
+ max_date: "2017-02-14 12:30:31",
+ last_time_entries_checked: null,
+ account_code: "101200",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ mode: "customers",
+ },
+ {
+ account_id: 7,
+ partner_name: "Camptocamp",
+ reconciliation_proposition: [],
+ currency_id: 3,
+ max_date: "2017-02-13 14:24:55",
+ last_time_entries_checked: null,
+ account_code: "101200",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ mode: "customers",
+ },
+ ],
+ accounts: [
+ {
+ account_id: 283,
+ account_name: "101000 Current Assets",
+ currency_id: 3,
+ max_date: "2017-02-16 14:32:04",
+ last_time_entries_checked: "2017-02-16",
+ account_code: "101000",
+ mode: "accounts",
+ reconciliation_proposition: [
+ {
+ account_id: 283,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-16",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "BNK1/2017/0006: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 399,
+ credit: 1000.0,
+ journal_id: [3, "Bank"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "101000",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_id: 283,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-18",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "INV/2017/0006",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 402,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 1000.0,
+ account_code: "101000",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ },
+ ],
+ suppliers: [
+ {
+ account_id: 284,
+ partner_name: "Agrolait",
+ reconciliation_proposition: [
+ {
+ account_id: 284,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-16",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "BNK1/999: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 999,
+ credit: 1000.0,
+ journal_id: [3, "Bank"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_id: 284,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-18",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "INV/998",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 998,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 1000.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ currency_id: 3,
+ max_date: "2017-02-14 12:36:05",
+ last_time_entries_checked: null,
+ account_code: "111100",
+ partner_id: 8,
+ account_name: "Account Payable",
+ mode: "suppliers",
+ },
+ {
+ account_id: 284,
+ partner_name: "Camptocamp",
+ reconciliation_proposition: [
+ {
+ account_id: 284,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-16",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 12,
+ account_name: "101000 Current Assets",
+ name: "BNK1/1999: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 1999,
+ credit: 1000.0,
+ journal_id: [3, "Bank"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_id: 284,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-18",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 12,
+ account_name: "101000 Current Assets",
+ name: "INV/1998",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 1998,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 1000.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ currency_id: 3,
+ max_date: "2017-02-14 12:36:05",
+ last_time_entries_checked: null,
+ account_code: "111100",
+ partner_id: 12,
+ account_name: "Account Payable",
+ mode: "suppliers",
+ },
+ ],
+ },
+ '["partner",null,"receivable"]': [
+ {
+ account_id: 287,
+ partner_name: "Agrolait",
+ reconciliation_proposition: [],
+ currency_id: 3,
+ max_date: "2017-02-14 12:30:31",
+ last_time_entries_checked: null,
+ account_code: "101200",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ mode: "customers",
+ },
+ {
+ account_id: 287,
+ partner_name: "Camptocamp",
+ reconciliation_proposition: [],
+ currency_id: 3,
+ max_date: "2017-02-13 14:24:55",
+ last_time_entries_checked: null,
+ account_code: "101200",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ mode: "customers",
+ },
+ ],
+ };
+
+ var move_lines_for_manual_reconciliation = {
+ '[287,8,"",0]': [
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency_str: "10,222.00 €",
+ currency_id: 1,
+ date_maturity: "2017-02-08",
+ date: "2017-02-08",
+ total_amount_str: "$ 11,000.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0004: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 17,
+ credit: 11000.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 11,000.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [7, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0005: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 19,
+ credit: 1000.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 180.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "BILL/2017/0003: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 21,
+ credit: 180.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 180.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "fddfgfdgfdgsdfg",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 90.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0006: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 23,
+ credit: 90.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 90.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-10",
+ date: "2017-02-08",
+ total_amount_str: "$ 650.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0012",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 6,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1000.00",
+ debit: 1000.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-10",
+ date: "2017-02-08",
+ total_amount_str: "$ 525.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 9,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 525.00",
+ debit: 525.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ '[7,12,"",0]': [
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-08",
+ date: "2017-02-08",
+ total_amount_str: "$ 11,000.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0004: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 17,
+ credit: 11000.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 11,000.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [7, "101200 Account Receivable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0005: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 19,
+ credit: 1000.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency: 100,
+ amount_currency_str: "100.00 €",
+ currency_id: 1,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 170.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 21,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 170.00",
+ debit: 170.0,
+ account_code: "101200",
+ ref: "INV fddfgfdgfdgsdfg",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency: 100,
+ amount_currency_str: "100.00 €",
+ currency_id: 1,
+ date_maturity: "2017-02-10",
+ date: "2017-02-10",
+ total_amount_str: "$ 180.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "BILL/2017/0003: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 22,
+ credit: 180.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 180.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "fddfgfdgfdgsdfg",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency: 170,
+ amount_currency_str: "170.00 €",
+ currency_id: 1,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 100.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 23,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 100.00",
+ debit: 100.0,
+ account_code: "101200",
+ ref: "INV fddfgfdgfdgsdfg",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [287, "101200 Account Receivable"],
+ amount_currency: 180,
+ amount_currency_str: "180.00 €",
+ currency_id: 1,
+ date_maturity: "2017-02-10",
+ date: "2017-02-10",
+ total_amount_str: "$ 100.00",
+ partner_id: 12,
+ account_name: "101200 Account Receivable",
+ name: "BILL/2017/0003: Customer Payment",
+ partner_name: "Camptocamp",
+ total_amount_currency_str: "",
+ id: 24,
+ credit: 100.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 100.00",
+ debit: 0.0,
+ account_code: "101200",
+ ref: "fddfgfdgfdgsdfg",
+ already_paid: false,
+ },
+ ],
+ '[284,8,"",0]': [
+ {
+ account_type: "receivable",
+ account_id: [284, "111100 Account Payable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-08",
+ date: "2017-02-08",
+ total_amount_str: "$ 11,000.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0004: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 17,
+ credit: 11000.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 11,000.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [284, "111100 Account Payable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0005: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 19,
+ credit: 1000.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "receivable",
+ account_id: [284, "111100 Account Payable"],
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-09",
+ date: "2017-02-09",
+ total_amount_str: "$ 180.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "BILL/2017/0003: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 21,
+ credit: 180.0,
+ journal_id: [2, "Vendor Bills"],
+ amount_str: "$ 180.00",
+ debit: 0.0,
+ account_code: "111100",
+ ref: "fddfgfdgfdgsdfg",
+ already_paid: false,
+ },
+ ],
+ '[283,null,"",0]': [
+ {
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-16",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "BNK1/2017/0006: Customer Payment",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 399,
+ credit: 1000.0,
+ journal_id: [3, "Bank"],
+ amount_str: "$ 1,000.00",
+ debit: 0.0,
+ account_code: "101000",
+ ref: "",
+ already_paid: false,
+ },
+ {
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-18",
+ date: "2017-02-16",
+ total_amount_str: "$ 1,000.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "INV/2017/0006",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 402,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1,000.00",
+ debit: 1000.0,
+ account_code: "101000",
+ ref: "",
+ already_paid: false,
+ },
+ ],
+ '[284,12,"",0]': [],
+ };
+
+ var session = {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ };
+
+ var options = {
+ context: {
+ statement_line_ids: [4],
+ },
+ params: {
+ limitMoveLines: 5,
+ },
+ };
+
+ Datas.params = {
+ data: db,
+ data_preprocess: data_preprocess,
+ data_widget: data_widget,
+ mv_lines: mv_lines,
+ auto_reconciliation: auto_reconciliation,
+ data_for_manual_reconciliation_widget: data_for_manual_reconciliation_widget,
+ move_lines_for_manual_reconciliation: move_lines_for_manual_reconciliation,
+ session: session,
+ options: options,
+ };
+ // This is the main function for this module. Its job is to export (and clone) all data for a test.
+ Datas.getParams = function () {
+ return (this.used = $.extend(true, {}, this.params));
+ };
+ return Datas;
+});
+
+odoo.define("account.reconciliation_tests", function (require) {
+ "use strict";
+
+ var ReconciliationClientAction = require("account.ReconciliationClientAction");
+ var ReconciliationRenderer = require("account.ReconciliationRenderer");
+ var demoData = require("account.reconciliation_tests.data");
+
+ var testUtils = require("web.test_utils");
+ var testUtilsDom = require("web.test_utils_dom");
+ var testUtilsMock = require("web.test_utils_mock");
+
+ QUnit.module(
+ "account",
+ {
+ beforeEach: function () {
+ this.params = demoData.getParams();
+ testUtils.patch(ReconciliationRenderer.LineRenderer, {
+ MV_LINE_DEBOUNCE: 0,
+ });
+ },
+ afterEach: function () {
+ testUtils.unpatch(ReconciliationRenderer.LineRenderer);
+ },
+ },
+ function () {
+ QUnit.module("Reconciliation");
+
+ QUnit.test("Reconciliation basic rendering", async function (assert) {
+ assert.expect(10);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+ var widget = clientAction.widgets[0];
+
+ assert.hasClass(
+ widget.$el,
+ "o_reconciliation_line",
+ "should instance of widget reconciliation"
+ );
+ assert.containsOnce(widget, ".accounting_view", "should have one view");
+ assert.containsN(
+ widget,
+ '[id*="notebook_page_match"]',
+ 2,
+ "should have 'match_rp' and 'match_other' panel"
+ );
+ assert.containsOnce(widget, ".create", "should have 'create' panel");
+
+ assert.strictEqual(
+ widget
+ .$("thead")
+ .text()
+ .replace(/[\n\r\s]+/g, " "),
+ " 101401 2017-01-01 SAJ/2014/002 and SAJ/2014/003 $ 1,175.00 ",
+ "should display the line information"
+ );
+ assert.ok(
+ widget.$("caption .o_field_many2one").length,
+ "should display the many2one with to select a partner"
+ );
+
+ assert.containsN(
+ clientAction,
+ '[data-mode="inactive"]',
+ 3,
+ "should be as 'inactive' mode by default"
+ );
+ assert.strictEqual(
+ widget.$el.data("mode"),
+ "match_rp",
+ "the first one should automatically switch to match_rp mode"
+ );
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ assert.strictEqual(
+ widget.$el.data("mode"),
+ "create",
+ "should switch to 'create' mode"
+ );
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_match_rp"]')
+ );
+ assert.strictEqual(
+ widget.$el.data("mode"),
+ "match_rp",
+ "should switch to 'match_rp' mode"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation fields", async function (assert) {
+ assert.expect(6);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ user_has_group: function (group) {
+ if (
+ group === "analytic.group_analytic_tags" ||
+ group === "analytic.group_analytic_accounting"
+ ) {
+ return $.when(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "Agrolait",
+ "the partner many2one should display agrolait"
+ );
+ assert.strictEqual(
+ clientAction.widgets[2].$(".o_input_dropdown input").val(),
+ "Camptocamp",
+ "the partner many2one should display Camptocamp"
+ );
+ await testUtils.dom.click(widget.$(".accounting_view tfoot td:first"));
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ assert.containsN(
+ widget,
+ ".create input.o_input",
+ 8,
+ "create panel should contain 8 fields (account_id, tax_id, journal_id, analytic_account_id, analytic_tag_ids, label, amount, date)"
+ );
+ assert.containsN(
+ widget,
+ ".create .create_account_id .o_required_modifier, .create .create_label .o_required_modifier, .create .create_amount .o_required_modifier",
+ 3,
+ "account_id, label and amount should be required fields"
+ );
+ assert.strictEqual(
+ widget.$(".create .create_label input").val(),
+ "SAJ/2014/002 and SAJ/2014/003",
+ "should use the name of the reconciliation line for the default label value"
+ );
+ assert.strictEqual(
+ widget.$(".create .create_amount input").val(),
+ "1175.00",
+ "should have the balance amout as default value for the amout field"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation basic data", async function (assert) {
+ assert.expect(17);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+ var widget = clientAction.widgets[0];
+
+ assert.containsN(
+ widget,
+ ".match:first .mv_line",
+ 2,
+ "should display 2 account move lines"
+ );
+ assert.strictEqual(
+ widget
+ .$(".match:first .mv_line")
+ .text()
+ .replace(/[\n\r\s]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101200 2017-02-07 INV/2017/0002 $ 650.00 101200 2017-02-07 INV/2017/0003 $ 525.00 ",
+ "should display 4 account move lines who contains the account_code, due_date, label and the credit"
+ );
+ assert.strictEqual(
+ widget.$('.match:first .mv_line .cell_right:contains(".")').length,
+ 2,
+ "should display only the credit account move lines (hide the debit)"
+ );
+
+ await testUtils.dom.click(
+ clientAction.widgets[1].$(".accounting_view thead td:first")
+ );
+ assert.containsN(
+ clientAction.widgets[1],
+ ".mv_line",
+ 5,
+ "should display 5 account move lines"
+ );
+ assert.strictEqual(
+ clientAction.widgets[1].$('.mv_line .cell_right:contains(".")')
+ .length,
+ 3,
+ "should display only the credit account move lines (hide the debit)"
+ );
+ assert.containsN(
+ clientAction.widgets[1],
+ ".mv_line.already_reconciled",
+ 3,
+ "should display 3 already reconciled account move lines"
+ );
+ assert.strictEqual(
+ clientAction.widgets[1]
+ .$(".mv_line")
+ .text()
+ .replace(/[\n\r\s]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101401 2017-01-23 ASUSTeK: BNK1/2017/0002: SUPP.OUT/2017/0002 : BILL/2017/0003 $ 376.00 101401 2017-01-23 Agrolait: BNK1/2017/0003: CUST.IN/2017/0001 $ 100.00 111100 2017-02-28 Camptocamp: BILL/2017/0001 $ 10,000.00 101401 2017-01-23 Agrolait: BNK1/2017/0004: CUST.IN/2017/0002 : INV/2017/0003 $ 525.50 101200 2017-02-07 Agrolait: INV/2017/0002 $ 650.00 ",
+ "should display 4 account move lines who contains the account_code, due_date, label and the credit"
+ );
+ assert.strictEqual(
+ clientAction.widgets[1].$('.mv_line .cell_left:contains(".")')
+ .length,
+ 2,
+ "should display only 2 debit account move lines"
+ );
+
+ // Load more
+ assert.ok(
+ clientAction.widgets[1].$(".match:first div.load-more a:visible")
+ .length,
+ "should display the 'load more' button"
+ );
+ assert.equal(
+ clientAction.widgets[1].$(".match:first div.load-more span").text(),
+ 3,
+ "should display 3 items remaining"
+ );
+ await testUtils.dom.click(
+ clientAction.widgets[1].$(".match:first div.load-more a")
+ );
+ assert.containsN(
+ clientAction.widgets[1],
+ ".mv_line",
+ 8,
+ "should load 3 more records"
+ );
+ assert.notOk(
+ clientAction.widgets[1].$(".match:first div.load-more a:visible")
+ .length,
+ "should not display the 'load more' button anymore"
+ );
+
+ assert.ok(
+ clientAction.widgets[0].$("caption button.btn-secondary:visible")
+ .length,
+ "should display the secondary 'Validate' button"
+ );
+ assert.equal(
+ clientAction.widgets[1].$("caption button:disabled:visible").length,
+ 1,
+ "button should be disabled"
+ );
+ assert.ok(
+ clientAction.widgets[2].$("caption button.btn-primary:visible")
+ .length,
+ "should display the primary 'Validate' button"
+ );
+
+ await testUtils.dom.click(
+ clientAction.widgets[3].$(".accounting_view thead td:first")
+ );
+ assert.strictEqual(
+ clientAction.widgets[3].$el.data("mode"),
+ "create",
+ "should switch to 'create' mode instead of 'match_rp' mode when 'match_rp' mode is empty"
+ );
+
+ // Open the first line
+ await testUtils.dom.click(widget.$(".accounting_view thead td:first"));
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_match_rp"]')
+ );
+ // Select propositions
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+
+ // Await testUtils.dom.click(widget.$('caption')); //why is it inactive?
+
+ testUtils.mock.intercept(clientAction, "call_service", function (
+ event
+ ) {
+ if (event.data.args[1].method == "process_bank_statement_line") {
+ assert.deepEqual(
+ event.data.args[1].args,
+ [
+ [5],
+ [
+ {
+ partner_id: 8,
+ counterpart_aml_dicts: [
+ {
+ counterpart_aml_id: 109,
+ credit: 650,
+ debit: 0,
+ name: "INV/2017/0002",
+ analytic_tag_ids: [[6, null, []]],
+ },
+ {
+ counterpart_aml_id: 112,
+ credit: 525,
+ debit: 0,
+ name: "INV/2017/0003",
+ analytic_tag_ids: [[6, null, []]],
+ },
+ ],
+ payment_aml_ids: [],
+ new_aml_dicts: [],
+ to_check: false,
+ },
+ ],
+ ],
+ "Should call process_bank_statement_line with args"
+ );
+ var def = testUtils.makeTestPromise();
+ def.abort = function () {};
+ event.data.callback(def);
+ }
+ });
+
+ // Click on reconcile button
+ await testUtils.dom.click(widget.$(".o_reconcile:visible"));
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation validate without proposition", async function (
+ assert
+ ) {
+ assert.expect(1);
+ // Test added to prevent this issue happening again: https://github.com/odoo/odoo/commit/3549688b21eb65e16b9c3f2b6462eb8d8b52cd47
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+ // Ensure that when we validate a line without any selection, it is the same
+ // as when we manually create a line with the line.balance and that only one
+ // line is send back to server.
+ testUtils.mock.intercept(clientAction, "call_service", function (
+ event
+ ) {
+ assert.deepEqual(
+ event.data.args[1].args,
+ [
+ [5],
+ [
+ {
+ partner_id: 8,
+ to_check: false,
+ counterpart_aml_dicts: [],
+ payment_aml_ids: [],
+ to_check: false,
+ new_aml_dicts: [
+ {
+ account_id: 287,
+ credit: 1175,
+ debit: 0,
+ name: "SAJ/2014/002 and SAJ/2014/003",
+ analytic_tag_ids: [[6, null, []]],
+ },
+ ],
+ },
+ ],
+ ],
+ "Should call process_bank_statement_line with ids"
+ );
+ var def = testUtils.makeTestPromise();
+ def.abort = function () {};
+ event.data.callback(def);
+ });
+
+ // Click on validate button
+ await testUtils.dom.click(widget.$("button.o_validate:not(:hidden)"));
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation validate with proposition", async function (
+ assert
+ ) {
+ assert.expect(1);
+ // Test added to check this functionality: https://github.com/odoo/odoo/commit/2f3b469dee6f18cbccce1cdf2a81cfe57960c533
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+ var widget = clientAction.widgets[0];
+ // Add a line as proposition
+ // open the first line
+ await testUtils.dom.click(widget.$(".accounting_view thead td:first"), {
+ allowInvisible: true,
+ });
+ await testUtils.nextTick();
+ // Select propositions
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first"),
+ {allowInvisible: true}
+ );
+ await testUtils.nextTick();
+
+ // Ensure that when we validate a line with propositions and that there is a remaining balance
+ // We also create a line which is the open balance.
+ testUtils.mock.intercept(clientAction, "call_service", function (
+ event
+ ) {
+ assert.deepEqual(
+ event.data.args[1].args,
+ [
+ [5],
+ [
+ {
+ partner_id: 8,
+ to_check: false,
+ counterpart_aml_dicts: [
+ {
+ counterpart_aml_id: 109,
+ credit: 650,
+ debit: 0,
+ name: "INV/2017/0002",
+ analytic_tag_ids: [[6, null, []]],
+ },
+ ],
+ payment_aml_ids: [],
+ new_aml_dicts: [
+ {
+ account_id: 287,
+ credit: 525,
+ debit: 0,
+ name:
+ "SAJ/2014/002 and SAJ/2014/003 : Open balance",
+ },
+ ],
+ },
+ ],
+ ],
+ "Should call process_bank_statement_line with ids"
+ );
+ var def = testUtils.makeTestPromise();
+ def.abort = function () {};
+ event.data.callback(def);
+ });
+
+ // Click on validate button
+ await testUtils.dom.click(widget.$("button.o_validate:not(:hidden)"));
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation partial [REQUIRE FOCUS]", async function (
+ assert
+ ) {
+ assert.expect(8);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ console.log(args.method);
+ if (args.method === "process_bank_statement_line") {
+ var lines = args.args["1"];
+ console.log(args.arsg);
+ assert.deepEqual(
+ args.args,
+ [
+ [6],
+ [
+ {
+ partner_id:
+ lines.length == 1
+ ? lines[0].partner_id
+ : false,
+ counterpart_aml_dicts: [
+ {
+ analytic_tag_ids: [[6, null, []]],
+ counterpart_aml_id: 114,
+ credit: 0,
+ debit: 32.58,
+ name: "BILL/2017/0001",
+ },
+ ],
+ payment_aml_ids: [],
+ new_aml_dicts: [],
+ to_check: false,
+ },
+ ],
+ ],
+ "should call process_bank_statement_line with partial reconcile values"
+ );
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+ var widget = clientAction.widgets[0];
+
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.equal(
+ widget.$(".cell_right .edit_amount").length,
+ 1,
+ "should display the edition pencil"
+ );
+
+ widget = clientAction.widgets[1];
+
+ await testUtils.dom.click(widget.$(".accounting_view thead td:first"));
+ assert.strictEqual(
+ widget.$(
+ ".match:first .mv_line[data-line-id=114] .cell_account_code:first()"
+ ).length,
+ 1,
+ "Should have line"
+ );
+ await testUtils.dom.click(
+ widget.$(
+ ".match:first .mv_line[data-line-id=114] .cell_account_code"
+ )
+ );
+
+ assert.equal(
+ widget.$(".accounting_view tbody .cell_left .edit_amount").length,
+ 1,
+ "should display the edition pencil"
+ );
+
+ // The partner has been set automatically, remove it.
+ await testUtils.dom.click(widget.$(".o_input_dropdown input"));
+ await testUtils.fields.editAndTrigger(
+ widget.$(".o_input_dropdown input"),
+ "",
+ ["keyup", "blur"]
+ );
+
+ assert.equal(
+ clientAction.widgets[1].$("caption button:disabled:visible").length,
+ 1,
+ "button should be disabled"
+ );
+ await testUtils.dom.click(
+ widget.$(".accounting_view .cell_left .edit_amount")
+ );
+ assert.strictEqual(
+ widget.$(
+ ".accounting_view .cell_left .edit_amount_input:not(.d-none)"
+ ).length,
+ 1,
+ "should display the input field to edit amount"
+ );
+ // Edit amount
+ await testUtils.fields.editAndTrigger(
+ widget.$(
+ ".accounting_view .cell_left .edit_amount_input:not(.d-none)"
+ ),
+ "32.58",
+ ["change", "blur"]
+ );
+ assert.strictEqual(
+ widget
+ .$(".accounting_view .cell_left .line_amount")
+ .text()
+ .replace(/[\n\r\s]+/g, " "),
+ " $ 10000.00 $ 32.58 ",
+ "should display previous amount and new amount"
+ );
+
+ assert.strictEqual(
+ widget.$("button.btn-primary:visible").length,
+ 1,
+ "should display the reconcile button"
+ );
+ await testUtils.dom.click(widget.$("button.btn-primary:visible"));
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation currencies", async function (assert) {
+ assert.expect(2);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: this.params.session,
+ translateParameters: {
+ date_format: "%m/%d/%Y",
+ direction: "ltr",
+ name: "English",
+ thousands_sep: ",",
+ time_format: "%H:%M:%S",
+ decimal_point: ".",
+ id: 1,
+ grouping: [3, 0],
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+
+ assert.strictEqual(
+ clientAction
+ .$(
+ ".accounting_view tfoot .cell_right, .accounting_view tfoot .cell_left"
+ )
+ .text()
+ .replace(/[\n\r\s]+/g, " "),
+ "$ 1,175.00$ 32.58$ 2,000.00",
+ "should display the different amounts with the currency"
+ );
+ // Await testUtils.dom.click(widget.$('.accounting_view thead .mv_line td:first'));
+
+ assert.strictEqual(
+ clientAction
+ .$(".accounting_view tbody")
+ .text()
+ .replace(/[\n\r\s]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101200 2017-02-07 INV/2017/0012 $ 650.00 ",
+ "should display the created reconciliation line with the currency"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation change partner", async function (assert) {
+ assert.expect(17);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ archs: {
+ "res.partner,false,list":
+ '',
+ "res.partner,false,search":
+ '' +
+ '' +
+ "",
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+ var widget = clientAction.widgets[0];
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "Agrolait",
+ "the partner many2one should display agrolait"
+ );
+ assert.containsN(
+ widget,
+ ".match:first table tr",
+ 2,
+ "agrolait should have 2 propositions for reconciliation"
+ );
+
+ // Adding the two propositions
+ // This is in order to try that after changing partner the propositions are emptied
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.containsN(
+ widget,
+ ".accounting_view tbody tr",
+ 2,
+ "Both proposition should be selected"
+ );
+
+ // Similate changing partner to one that does not have propositions to see if create mode is open after
+ await testUtils.dom.click(widget.$(".o_input_dropdown input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(partner 1)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ clientAction._onAction({
+ target: widget,
+ name: "change_partner",
+ data: {data: {display_name: "partner 1", id: 1}},
+ stopped: false,
+ });
+ await testUtils.nextTick();
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "partner 1",
+ "the partner many2one should display partner 1"
+ );
+ assert.containsNone(
+ widget,
+ ".match:first table tr.mv_line",
+ "partner 1 should have 0 propositions for reconciliation"
+ );
+ assert.strictEqual(
+ widget.$el.data("mode"),
+ "create",
+ "widget should be in create mode"
+ );
+
+ // Simulate changing partner
+ await testUtils.dom.clickFirst(widget.$(".o_input_dropdown input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(Camptocamp)")
+ .trigger("mouseenter")
+ .trigger("click");
+ clientAction._onAction({
+ target: widget,
+ name: "change_partner",
+ data: {data: {display_name: "Camptocamp", id: 12}},
+ stopped: false,
+ });
+ await testUtils.nextTick();
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "Camptocamp",
+ "the partner many2one should display Camptocamp"
+ );
+ assert.containsN(
+ widget,
+ ".match:first table tr.mv_line",
+ 3,
+ "camptocamp should have 3 propositions for reconciliation"
+ );
+
+ // Simulate changing partner with SelectCreateDialog
+ widget = clientAction.widgets[1];
+ assert.strictEqual(
+ $(".modal").length,
+ 0,
+ "shouldn't have any opened modal"
+ );
+ await testUtils.dom.click(widget.$(".o_input_dropdown input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(Search More):eq(1)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ assert.strictEqual(
+ $(".modal").length,
+ 1,
+ "should open a SelectCreateDialog"
+ );
+ await testUtils.dom.click(
+ $(".modal table.o_list_table td:contains(Camptocamp)")
+ );
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "Camptocamp",
+ "the partner many2one should display Camptocamp"
+ );
+
+ widget = clientAction.widgets[2];
+ await testUtils.dom.click(widget.$(".accounting_view thead td:first"));
+ await testUtils.dom.click(
+ widget.$(".accounting_view .mv_line .cell_label")
+ );
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "Camptocamp",
+ "the partner many2one should display agrolait"
+ );
+ assert.containsN(
+ widget,
+ ".match:first table tr",
+ 3,
+ "Camptocamp should have 3 propositions for reconciliation"
+ );
+ assert.notOk(
+ widget.$(".match:first div.load-more a:visible").length,
+ "should not display the load more button"
+ );
+
+ // Simulate remove partner
+ await testUtils.dom.click(widget.$(".o_input_dropdown input"));
+ await testUtils.fields.editAndTrigger(
+ widget.$(".o_input_dropdown input"),
+ "",
+ ["keyup", "blur"]
+ );
+
+ assert.strictEqual(
+ widget.$(".o_input_dropdown input").val(),
+ "",
+ "the partner many2one should be empty"
+ );
+ assert.containsN(
+ widget,
+ ".match:first table tr.mv_line",
+ 5,
+ "should have 5 propositions for reconciliation if partner is false"
+ );
+ assert.ok(
+ widget.$(".match:first div.load-more a:visible").length,
+ "should display the load more button"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation create line", async function (assert) {
+ assert.expect(23);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ assert.strictEqual(
+ clientAction
+ .$(
+ ".accounting_view tfoot .cell_right, .accounting_view tfoot .cell_left"
+ )
+ .text()
+ .replace(/[$, ]+/g, ""),
+ " 1175.00 32.58 2000.00",
+ "should display the open balance values"
+ );
+
+ var widget = clientAction.widgets[0];
+
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "Open balance",
+ "should display 'Open Balance' line with the rest to reconcile"
+ );
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ await testUtils.dom.click(widget.$(".create .create_account_id input"));
+ $(
+ ".ui-autocomplete .ui-menu-item a:contains(101200 Account Receivable)"
+ )
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+
+ assert.notOk(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "should not display 'Open Balance' line because the rest to reconcile is null"
+ );
+ assert.containsOnce(
+ widget,
+ ".accounting_view tbody tr",
+ "should have only the created reconcile line"
+ );
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody tr")
+ .text()
+ .replace(/[\n\r\s$,]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101200 New SAJ/2014/002 and SAJ/2014/003 1175.00 ",
+ "the new line should have the selected account, name and amout"
+ );
+ assert.ok(
+ widget.$("caption button.btn-primary:visible").length,
+ "should display the 'Reconcile' button"
+ );
+
+ testUtils.mock.intercept(clientAction, "do_action", function (event) {
+ assert.strictEqual(
+ JSON.stringify(event.data.action),
+ '{"type":"ir.actions.act_window","res_model":"account.reconcile.model","views":[[false,"form"]],"target":"current"}',
+ "should open the reconcile model form view"
+ );
+ });
+ await testUtils.dom.click(widget.$(".create .reconcile_model_create"), {
+ allowInvisible: true,
+ });
+
+ testUtils.mock.intercept(clientAction, "do_action", function (event) {
+ assert.strictEqual(
+ JSON.stringify(event.data.action),
+ '{"type":"ir.actions.act_window","res_model":"account.reconcile.model","views":[[false,"list"],[false,"form"]],"view_mode":"list","target":"current"}',
+ "should open the reconcile model list view"
+ );
+ });
+ await testUtils.dom.click(widget.$(".create .reconcile_model_edit"), {
+ allowInvisible: true,
+ });
+
+ await testUtils.fields.editInput(
+ widget.$(".create .create_amount input"),
+ "1100.00"
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody .cell_right")
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 1100.00",
+ "should display the value 1100.00 in right column"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_right").text(),
+ "$ 75.00",
+ "should display 'Open Balance' line because the rest to reconcile is 75.00"
+ );
+ assert.containsOnce(
+ widget,
+ ".accounting_view tbody tr",
+ "should have ever only the created reconcile line"
+ );
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody tr")
+ .text()
+ .replace(/[\n\r\s$,]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101200 New SAJ/2014/002 and SAJ/2014/003 1100.00 ",
+ "the new line should be update the amout"
+ );
+ assert.ok(
+ widget.$("caption button.btn-secondary:visible").length,
+ "should display the 'validate' button"
+ );
+
+ await testUtils.dom.click(widget.$(".create .add_line"), {
+ allowInvisible: true,
+ });
+ await testUtils.fields.editInput(
+ widget.$(".create .create_amount input"),
+ "-100"
+ );
+ await testUtils.dom.click(widget.$(".create .create_account_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ await testUtils.fields.editInput(
+ widget.$(".create .create_label input"),
+ "test0"
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody .cell_left:last")
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 100.00",
+ "should display the value 100.00 in left column"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "Open balance",
+ "should display 'Open Balance'"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_right").text(),
+ "$ 175.00",
+ "should display 'Open Balance' line because the rest to reconcile is 175.00"
+ );
+ assert.containsN(
+ widget,
+ ".accounting_view tbody tr",
+ 2,
+ "should have 2 created reconcile lines"
+ );
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody tr:eq(1)")
+ .text()
+ .replace(/[\n\r\s$,]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101000 New test0 100.00 ",
+ "the new line should have the selected account, name and amout"
+ );
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+
+ assert.strictEqual(
+ widget.$(".create .create_amount input").val(),
+ "175.00",
+ "should have '175.00' as default amount value"
+ );
+
+ await testUtils.fields.editInput(
+ widget.$(".create .create_amount input"),
+ "200"
+ );
+ widget.$(".create .create_account_id input").trigger("click");
+ $(".ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ await testUtils.fields.editInput(
+ widget.$(".create .create_label input"),
+ "test1"
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody .cell_right:last")
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 200.00",
+ "should display the value 200.00 in left column"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "Open balance",
+ "should display 'Open balance'"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_left").text(),
+ "$ 25.00",
+ "should display 'Open balance' with 25.00 in left column"
+ );
+ assert.containsN(
+ widget,
+ ".accounting_view tbody tr",
+ 3,
+ "should have 3 created reconcile lines"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation create line (many2one test)", async function (
+ assert
+ ) {
+ assert.expect(5);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ var def = testUtils.makeTestPromise();
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.account,false,list":
+ '',
+ "account.account,false,search":
+ '',
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ mockRPC: function (route, args) {
+ if (args.method === "name_get") {
+ return def.then(this._super.bind(this, route, args));
+ }
+ return this._super(route, args);
+ },
+ });
+
+ await clientAction.prependTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+
+ // Open the first line in write-off mode
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+
+ // Select an account with the many2one (drop down)
+ await testUtils.dom.click(widget.$(".create .create_account_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(101200)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ assert.strictEqual(
+ widget.$(".create .create_account_id input").val(),
+ "101200 Account Receivable",
+ "Display the selected account"
+ );
+ assert.strictEqual(
+ widget
+ .$("tbody:first .cell_account_code")
+ .text()
+ .replace(/[\u200B]/g, ""),
+ "101200",
+ "Display the code of the selected account"
+ );
+
+ // Use the many2one select dialog to change the account
+ await testUtils.dom.click(widget.$(".create .create_account_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(Search)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ // Select the account who does not appear in the drop drown
+ await testUtils.dom.click($(".modal tr.o_data_row:contains(502)"));
+ assert.strictEqual(
+ widget.$(".create .create_account_id input").val(),
+ "101200 Account Receivable",
+ "Selected account does not change"
+ );
+ // Wait the name_get to render the changes
+ def.resolve();
+ await testUtils.nextTick();
+ assert.strictEqual(
+ widget.$(".create .create_account_id input").val(),
+ "502 Account",
+ "Display the selected account"
+ );
+ assert.strictEqual(
+ widget
+ .$("tbody:first .cell_account_code")
+ .text()
+ .replace(/[\u200B]/g, ""),
+ "502",
+ "Display the code of the selected account"
+ );
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation create line with taxes", async function (
+ assert
+ ) {
+ assert.expect(13);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ await testUtils.dom.click(widget.$(".create .create_account_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(101000 Current Assets)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+ await testUtils.fields.editInput(
+ widget.$(".create .create_label input"),
+ "test1"
+ );
+ await testUtils.fields.editInput(
+ widget.$(".create .create_amount input"),
+ "1100"
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody .cell_right:last")
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 1100.00",
+ "should display the value 1100.00 in left column"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "Open balance",
+ "should display 'Open Balance'"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_right").text(),
+ "$\u00a075.00",
+ "should display 'Open Balance' with 75.00 in right column"
+ );
+ assert.containsOnce(
+ widget,
+ ".accounting_view tbody tr",
+ "should have 1 created reconcile lines"
+ );
+
+ await testUtils.dom.click(widget.$(".create .create_tax_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(10.00%)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody .cell_right")
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 1000.00 $ 100.00",
+ "should have 2 created reconcile lines with right column values"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "Open balance",
+ "should display 'Open Balance'"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_right").text(),
+ "$\u00a075.00",
+ "should display 'Open Balance' with 75.00 in right column"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_left").text(),
+ "",
+ "should display 'Open Balance' without any value in left column"
+ );
+ assert.containsN(
+ widget,
+ ".accounting_view tbody tr",
+ 2,
+ "should have 2 created reconcile lines"
+ );
+ await testUtils.dom.click(widget.$('[name="tax_ids"] a.o_delete'));
+ widget
+ .$(".create .create_tax_id input")
+ .val("")
+ .trigger("keyup")
+ .trigger("blur");
+ await testUtils.dom.click(widget.$(".create .create_tax_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(20.00%)")
+ .trigger("mouseenter")
+ .trigger("click");
+ await testUtils.nextTick();
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody .cell_right")
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 1100.00 $ 220.00",
+ "should have 2 created reconcile lines with right column values"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_label").text(),
+ "Open balance",
+ "should display 'Open balance'"
+ );
+ assert.strictEqual(
+ widget.$(".accounting_view tfoot .cell_left").text(),
+ "$\u00a0145.00",
+ "should display 'Open balance' with 145.00 in right column"
+ );
+ assert.containsN(
+ widget,
+ ".accounting_view tbody tr",
+ 2,
+ "should have 2 created reconcile lines"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test(
+ "Reconciliation create line from reconciliation model",
+ async function (assert) {
+ assert.expect(6);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ await testUtils.dom.click(
+ widget.$(".create .quick_add button:contains(ATOS)")
+ );
+
+ assert.strictEqual(
+ widget
+ .$(
+ ".accounting_view tbody .cell_label, .accounting_view tbody .cell_right"
+ )
+ .text()
+ .replace(/[\n\r\s$,]+/g, " "),
+ " ATOS Banque 1145.63 ATOS Banque Tax 20.00% 229.13 ATOS Frais 26.78 ATOS Frais Tax 10.00% include 2.68 ",
+ "should display 4 lines"
+ );
+ assert.strictEqual(
+ widget
+ .$(
+ ".accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left"
+ )
+ .text()
+ .replace(/[\n\r\s$,]+/g, ""),
+ "Openbalance229.22",
+ "should display the 'Open balance' line with value in left column"
+ );
+
+ await testUtils.fields.editAndTrigger(
+ widget.$(".create .create_amount input"),
+ "100",
+ ["input"]
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody")
+ .text()
+ .replace(/[\n\r\s$,]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101120 New ATOS Banque 1145.63 101120 New ATOS Banque Tax 20.00% 229.13 101130 New ATOS Frais 90.91 101300 New ATOS Frais Tax 10.00% include 9.09 ",
+ "should update the value of the 2 lines (because the line + its tax must have 100% of the value)"
+ );
+ assert.strictEqual(
+ widget
+ .$(
+ ".accounting_view tfoot .cell_label, .accounting_view tfoot .cell_left"
+ )
+ .text()
+ .replace(/[\n\r\s$,]+/g, ""),
+ "Openbalance299.76",
+ "should change the 'Open balance' line because the 20.00% tax is not an include tax"
+ );
+
+ await testUtils.dom.click(
+ widget.$(".accounting_view tbody .cell_account_code:first")
+ );
+ await testUtils.dom.click(
+ widget.$(".accounting_view tbody .cell_label:first")
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody")
+ .text()
+ .replace(/[\n\r\s$,]+/g, " "),
+ "",
+ "should removed every line"
+ );
+
+ await testUtils.dom.click(
+ widget.$(".create .quick_add button:contains(Double)")
+ );
+
+ assert.strictEqual(
+ widget
+ .$(".accounting_view tbody")
+ .text()
+ .replace(/[\n\r\s$,]+/g, " ")
+ .replace(/[\u200B]/g, ""),
+ " 101120 New Double Banque 1145.63 101130 New Double Frais 29.37 ",
+ "should have a sum of reconciliation proposition amounts equal to the line amount"
+ );
+
+ clientAction.destroy();
+ }
+ );
+
+ QUnit.test(
+ "Reconciliation fetch correct reconciliation models",
+ async function (assert) {
+ assert.expect(1);
+
+ testUtilsMock.patch(this.params.options.context, {
+ active_model: "account.journal", // On account dashboard, click "Reconcile" on a journal
+ active_ids: [1, 2], // Active journals
+ company_ids: [3, 4], // Active companies
+ });
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: async function (route, args) {
+ if (
+ args.model === "account.reconcile.model" &&
+ args.method === "search_read"
+ ) {
+ assert.deepEqual(
+ args.kwargs.domain,
+ [
+ ["company_id", "in", [3, 4]],
+ "|",
+ ["match_journal_ids", "=", false],
+ ["match_journal_ids", "in", [1, 2]],
+ ],
+ "The domain to get reconcile models should contain the right fields and values"
+ );
+ }
+ return this._super.apply(this, arguments);
+ },
+ });
+ clientAction.appendTo($("#qunit-fixture"));
+ testUtilsMock.unpatch(this.params.options.context);
+
+ clientAction.destroy();
+ }
+ );
+
+ QUnit.test("Reconciliation manual", async function (assert) {
+ assert.expect(13);
+
+ var clientAction = new ReconciliationClientAction.ManualAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: this.params.session,
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ assert.strictEqual(
+ clientAction
+ .$(".accounting_view:first thead")
+ .text()
+ .replace(/[\n\r\s]+/g, " "),
+ " 101000 Current AssetsLast Reconciliation: 2017-02-16 101000 ",
+ "should display the account as title"
+ );
+ assert.strictEqual(
+ clientAction.$(".o_reconciliation_line:first").data("mode"),
+ "inactive",
+ "should be in 'inactive' mode because no line to displayed and the balance amount is null"
+ );
+ assert.containsN(
+ clientAction,
+ ".accounting_view:first tbody tr",
+ 2,
+ "should have 2 propositions"
+ );
+ assert.containsOnce(
+ clientAction,
+ ".accounting_view:first .o_reconcile:visible",
+ "should display the reconcile button"
+ );
+
+ await testUtils.dom.click(
+ clientAction.$(".accounting_view:first .o_reconcile:visible")
+ );
+
+ assert.strictEqual(
+ clientAction
+ .$(".accounting_view:first thead")
+ .text()
+ .replace(/[\n\r\s]+/g, " "),
+ " 101200 Account Receivable 101200 ",
+ "should display the account and the account code as title"
+ );
+
+ assert.strictEqual(
+ clientAction
+ .$(
+ ".o_reconciliation_line:first .match:first tr:first .cell_right"
+ )
+ .text()
+ .trim()
+ .replace(/[\n\r\s\u00a0]+/g, " "),
+ "$ 11,000.00",
+ "sould display the line in $"
+ );
+ assert.strictEqual(
+ clientAction
+ .$(
+ ".o_reconciliation_line:first .match:first tr:first .cell_right .o_multi_currency"
+ )
+ .data("content"),
+ "10,222.00 €",
+ "sould display the monetary information in €"
+ );
+
+ assert.containsOnce(
+ clientAction,
+ ".accounting_view:first .o_no_valid:visible",
+ "should display the skip button"
+ );
+
+ await testUtils.dom.click(
+ clientAction.$(".o_reconciliation_line:eq(1) .accounting_view")
+ );
+ await testUtils.dom.click(
+ clientAction.$(".accounting_view:eq(1) thead td:first")
+ );
+ // Debugger
+ await testUtils.dom.click(
+ clientAction.$(
+ '.o_reconciliation_line:eq(1) [data-line-id="21"] .cell_label'
+ )
+ );
+ await testUtils.dom.click(
+ clientAction.$(
+ '.o_reconciliation_line:eq(1) [data-line-id="22"] .cell_label'
+ )
+ );
+
+ assert.strictEqual(
+ clientAction.$(".o_reconciliation_line:eq(1) tfoot tr").length,
+ 0,
+ "should not display the 'Write-off' line because the balance is null in Euro"
+ );
+ assert.strictEqual(
+ clientAction.$(".o_reconciliation_line:eq(1) .o_reconcile:visible")
+ .length,
+ 1,
+ "should display 'Reconcile' button in green"
+ );
+
+ await testUtils.dom.click(
+ clientAction.$(".o_reconciliation_line:eq(1) .o_reconcile:visible")
+ );
+
+ assert.containsOnce(
+ clientAction,
+ '.o_reconciliation_line[data-mode!="inactive"]',
+ "should have only one line open"
+ );
+
+ await testUtils.dom.click(
+ clientAction.$(
+ '.o_reconciliation_line:eq(1) [data-line-id="23"] .cell_label'
+ )
+ );
+ await testUtils.dom.click(
+ clientAction.$(
+ '.o_reconciliation_line:eq(1) [data-line-id="24"] .cell_label'
+ )
+ );
+
+ assert.strictEqual(
+ clientAction.$(".o_reconciliation_line:eq(1) tfoot tr").length,
+ 1,
+ "should display the 'Write-off' line because the balance is not null in Euro"
+ );
+ assert.strictEqual(
+ clientAction.$(".o_reconciliation_line:eq(1) .o_validate:visible")
+ .length,
+ 1,
+ "should display 'Reconcile' button"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test(
+ "Reconciliation: Payment < inv1 + inv2(partial)",
+ async function (assert) {
+ assert.expect(3);
+
+ /*
+ * One payment: $1175
+ * Two Invoices
+ * The first invoice will be fully reconciled $650
+ * The second invoice will be partially paid with the rest of the payment $999
+ */
+
+ // modify the second line that is already in db to put it at $999
+ var indexModif = _.findIndex(
+ this.params.mv_lines['[5,"",0]'],
+ function (line) {
+ return line.id === 112;
+ }
+ );
+ this.params.mv_lines['[5,"",0]'][indexModif] = {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 999.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 112,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 999.00",
+ debit: 999.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ };
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === "process_bank_statement_line") {
+ assert.deepEqual(
+ args.args,
+ [
+ [5], // Id of the bank statement line
+
+ [
+ {
+ counterpart_aml_dicts: [
+ {
+ name: "INV/2017/0002",
+ debit: 0,
+ credit: 650,
+ analytic_tag_ids: [
+ [6, null, []],
+ ],
+ counterpart_aml_id: 109,
+ },
+
+ {
+ name: "INV/2017/0003",
+ debit: 0,
+ credit: 525,
+ analytic_tag_ids: [
+ [6, null, []],
+ ],
+ counterpart_aml_id: 112,
+ },
+ ],
+
+ payment_aml_ids: [],
+ partner_id: 8,
+ to_check: false,
+ new_aml_dicts: [],
+ },
+ ],
+ ],
+ "should call process_bank_statement_line with partial reconcile values"
+ );
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ // Add first invoice to reconcile fully
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.ok(
+ widget.$(".cell_right .edit_amount").length,
+ "should display the pencil to edit amount"
+ );
+
+ // Add second invoice to reconcile partially
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+
+ // Edit amount on last invoice
+ await testUtils.dom.click(widget.$(".edit_amount:last()"));
+ await testUtils.fields.editAndTrigger(
+ widget.$(".edit_amount_input:last()"),
+ "525",
+ ["blur"]
+ );
+
+ var $buttonReconcile = widget.$("button.o_reconcile:not(hidden)");
+
+ assert.equal(
+ $buttonReconcile.length,
+ 1,
+ "The reconcile button must be visible"
+ );
+
+ await testUtils.dom.click($buttonReconcile);
+
+ clientAction.destroy();
+ }
+ );
+
+ QUnit.test("Reconciliation: payment and 2 partials", async function (
+ assert
+ ) {
+ assert.expect(6);
+
+ /*
+ * One payment: $1175
+ * Two Invoices as Inv1 = 1200; Inv2 = 1200:
+ * Payment < Inv1 AND Payment < Inv2
+ * No partial reconcile is possible, as a write-off of 1225 is necessary
+ */
+
+ // modify the invoice line to have their amount > payment
+ var indexInv1 = _.findIndex(this.params.mv_lines['[5,"",0]'], function (
+ line
+ ) {
+ return line.id === 109;
+ });
+ this.params.mv_lines['[5,"",0]'][indexInv1] = {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 1200.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 109,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1200.00",
+ debit: 1200.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ };
+
+ var indexInv2 = _.findIndex(this.params.mv_lines['[5,"",0]'], function (
+ line
+ ) {
+ return line.id === 112;
+ });
+ this.params.mv_lines['[5,"",0]'][indexInv2] = {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 1200.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 112,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1200.00",
+ debit: 1200.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ };
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === "process_bank_statement_line") {
+ assert.deepEqual(
+ args.args,
+ [
+ [5], // Id of the bank statement line
+
+ [
+ {
+ counterpart_aml_dicts: [
+ {
+ name: "INV/2017/0002",
+ debit: 0,
+ credit: 1200,
+ analytic_tag_ids: [[6, null, []]],
+ counterpart_aml_id: 109,
+ },
+
+ {
+ name: "INV/2017/0003",
+ debit: 0,
+ credit: 1200,
+ analytic_tag_ids: [[6, null, []]],
+ counterpart_aml_id: 112,
+ },
+ ],
+
+ payment_aml_ids: [],
+ partner_id: 8,
+ to_check: false,
+ new_aml_dicts: [
+ {
+ account_id: 282,
+ credit: 0,
+ debit: 1225,
+ analytic_tag_ids: [[6, null, []]],
+ name:
+ "SAJ/2014/002 and SAJ/2014/003",
+ },
+ ],
+ },
+ ],
+ ],
+ "should call process_bank_statement_line with new aml dict reconcile values"
+ );
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ // Add first invoice
+ // There should be the opportunity to reconcile partially
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.ok(
+ widget.$(".cell_right .edit_amount").length,
+ "should display the pencil to edit amount"
+ );
+
+ // Add second invoice
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.ok(
+ widget.$(".cell_right .edit_amount").length,
+ "should display the pencil to edit amount"
+ );
+
+ var writeOffCreate = widget.$("div.create");
+
+ assert.equal(
+ writeOffCreate.length,
+ 1,
+ "A write-off creation should be present"
+ );
+
+ assert.equal(
+ writeOffCreate.find("input[name=amount]").val(),
+ -1225,
+ "The right amount should be proposed for the write-off"
+ );
+
+ await testUtils.dom.click(
+ writeOffCreate.find(
+ ".create_account_id input.ui-autocomplete-input"
+ )
+ );
+ await testUtils.dom.click($("ul.ui-autocomplete li a:first"));
+
+ var $buttonReconcile = widget.$("button.o_reconcile:not(hidden)");
+
+ assert.equal(
+ $buttonReconcile.length,
+ 1,
+ "The reconcile button must be visible"
+ );
+
+ await testUtils.dom.click($buttonReconcile);
+
+ clientAction.destroy();
+ });
+
+ QUnit.test(
+ "Reconciliation: partial payment of 2 invoices with one payment [REQUIRE FOCUS]",
+ async function (assert) {
+ assert.expect(4);
+
+ /*
+ * One payment: $1175
+ * Two Invoices as Inv1 = 1200; Inv2 = 1200:
+ * Payment < Inv1 AND Payment < Inv2
+ * Assign 500 to inv1 and 675 to inv2
+ */
+
+ // modify the invoice line to have their amount > payment
+ var indexInv1 = _.findIndex(
+ this.params.mv_lines['[5,"",0]'],
+ function (line) {
+ return line.id === 109;
+ }
+ );
+ this.params.mv_lines['[5,"",0]'][indexInv1] = {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 1200.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0002",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 109,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1200.00",
+ debit: 1200.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ };
+
+ var indexInv2 = _.findIndex(
+ this.params.mv_lines['[5,"",0]'],
+ function (line) {
+ return line.id === 112;
+ }
+ );
+ this.params.mv_lines['[5,"",0]'][indexInv2] = {
+ account_type: "receivable",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-02-07",
+ date: "2017-01-08",
+ total_amount_str: "$ 1200.00",
+ partner_id: 8,
+ account_name: "101200 Account Receivable",
+ name: "INV/2017/0003",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 112,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 1200.00",
+ debit: 1200.0,
+ account_code: "101200",
+ ref: "",
+ already_paid: false,
+ };
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === "process_bank_statement_line") {
+ assert.deepEqual(
+ args.args,
+ [
+ [5], // Id of the bank statement line
+
+ [
+ {
+ counterpart_aml_dicts: [
+ {
+ name: "INV/2017/0002",
+ debit: 0,
+ credit: 500,
+ analytic_tag_ids: [
+ [6, null, []],
+ ],
+ counterpart_aml_id: 109,
+ },
+
+ {
+ name: "INV/2017/0003",
+ debit: 0,
+ credit: 675,
+ analytic_tag_ids: [
+ [6, null, []],
+ ],
+ counterpart_aml_id: 112,
+ },
+ ],
+
+ payment_aml_ids: [],
+ partner_id: 8,
+ to_check: false,
+ new_aml_dicts: [],
+ },
+ ],
+ ],
+ "should call process_bank_statement_line with correct counterpart_aml_dicts"
+ );
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ // Add first invoice
+ // There should be the opportunity to reconcile partially
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.ok(
+ widget.$(".cell_right .edit_amount").length,
+ "should display the pencil to edit amount"
+ );
+
+ // Add second invoice
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.ok(
+ widget.$(".cell_right .edit_amount").length,
+ "should display the pencil to edit amount"
+ );
+
+ // Edit invoice first amount
+ await testUtils.dom.click(widget.$(".edit_amount:first()"));
+ await testUtils.fields.editAndTrigger(
+ widget.$(".edit_amount_input:first()"),
+ "500",
+ ["blur"]
+ );
+ // Edit invoice second amount
+ var $buttonReconcile = widget.$("button.o_reconcile:not(hidden)");
+ await testUtils.dom.click(widget.$(".edit_amount:last()"));
+ await testUtils.fields.editAndTrigger(
+ widget.$(".edit_amount_input:last()"),
+ "675",
+ ["blur"]
+ );
+
+ assert.equal(
+ $buttonReconcile.length,
+ 1,
+ "The reconcile button must be visible"
+ );
+
+ await testUtils.dom.click($buttonReconcile, {allowInvisible: true});
+
+ clientAction.destroy();
+ }
+ );
+
+ QUnit.test(
+ "Manual Reconciliation: remove a prop to attain balance and reconcile",
+ async function (assert) {
+ assert.expect(5);
+
+ // Tweak the data to fit our needs
+ this.params.data_for_manual_reconciliation_widget[
+ '[283, null, "", 0, 6]'
+ ] = _.extend(
+ {},
+ this.params.data_for_manual_reconciliation_widget[
+ "[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]"
+ ]
+ );
+ this.params.data_for_manual_reconciliation_widget[
+ '[283, null, "", 0, 6]'
+ ].accounts[0].reconciliation_proposition = [
+ {
+ account_id: 283,
+ account_type: "other",
+ amount_currency_str: "",
+ currency_id: false,
+ date_maturity: "2017-03-18",
+ date: "2017-02-16",
+ total_amount_str: "$ 500.00",
+ partner_id: 8,
+ account_name: "101000 Current Assets",
+ name: "INV/2017/0987",
+ partner_name: "Agrolait",
+ total_amount_currency_str: "",
+ id: 999,
+ credit: 0.0,
+ journal_id: [1, "Customer Invoices"],
+ amount_str: "$ 500.00",
+ debit: 500.0,
+ account_code: "101000",
+ ref: "",
+ already_paid: false,
+ },
+ ];
+
+ var clientAction = new ReconciliationClientAction.ManualAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === "process_move_lines") {
+ assert.deepEqual(
+ args.args,
+ [
+ [
+ {
+ id: null,
+ type: null,
+ mv_line_ids: [399, 402],
+ new_mv_line_dicts: [],
+ },
+ ],
+ ],
+ "should call process_move_lines without the new mv line dict"
+ );
+ }
+
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ // Add first prop
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ assert.equal(
+ widget.$(".cell_right .edit_amount").length,
+ 0,
+ "should not display the pencil to edit amount"
+ );
+
+ // Add second prop
+ await testUtils.dom.click(
+ widget.$(".match:first .cell_account_code:first")
+ );
+ // Check that a create form is here
+
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ var writeOffCreate = widget.$("div.create");
+
+ assert.equal(
+ writeOffCreate.length,
+ 1,
+ "A write-off creation should be present"
+ );
+
+ assert.equal(
+ writeOffCreate.find("input[name=amount]").val(),
+ 500,
+ "The right amount should be proposed for the write-off"
+ );
+
+ // Remove the first line, the other two will balance one another
+ await testUtils.dom.click(
+ widget.$('tr[data-line-id="999"] td:first')
+ );
+
+ var $buttonReconcile = widget.$("button.o_reconcile:visible");
+ assert.equal(
+ $buttonReconcile.length,
+ 1,
+ "The reconcile button must be visible"
+ );
+
+ await testUtils.dom.click($buttonReconcile);
+
+ clientAction.destroy();
+ }
+ );
+
+ QUnit.test("Manual Reconciliation: No lines for account", async function (
+ assert
+ ) {
+ assert.expect(2);
+
+ var clientAction = new ReconciliationClientAction.ManualAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ // The second reconciliation "line" is where it happens
+ var widget = clientAction.widgets[1];
+
+ var emptyLine = widget.$("tr.mv_line");
+
+ assert.notOk(
+ "data-line-id" in emptyLine.getAttributes(),
+ "Empty line should be empty"
+ );
+
+ await testUtils.dom.click(emptyLine.find("td:first"));
+
+ // Check that a create form is here
+ var writeOffCreate = widget.$("div.create .create_account_id");
+
+ assert.equal(
+ writeOffCreate.length,
+ 1,
+ "A write-off creation should be present"
+ );
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Tax on account receivable", async function (assert) {
+ assert.expect(21);
+
+ this.params.data_for_manual_reconciliation_widget[
+ "[null,[282,283,284,285,286,287,288,308,499,500,501,502,503,504]]"
+ ].accounts = [];
+ var clientAction = new ReconciliationClientAction.ManualAction(
+ null,
+ this.params.options
+ );
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {},
+ mockRPC: function (route, args) {
+ if (args.method === "name_search") {
+ switch (args.model) {
+ // Mock the default mock to do the minimal processing required
+ // to get the available values for the droplists.
+ case "account.account":
+ assert.step("Account");
+ return Promise.resolve(
+ _.map(this.data[args.model].records, function (
+ record
+ ) {
+ return [record.id, record.name];
+ })
+ );
+ case "account.tax":
+ assert.step("Tax");
+ return Promise.resolve(
+ _.map(this.data[args.model].records, function (
+ record
+ ) {
+ return [record.id, record.display_name];
+ })
+ );
+ case "account.journal":
+ assert.step("Journal");
+ return Promise.resolve(
+ _.map(this.data[args.model].records, function (
+ record
+ ) {
+ return [record.id, record.display_name];
+ })
+ );
+ }
+ }
+ if (args.method === "process_move_lines") {
+ var mv_line_ids = args.args[0][0].mv_line_ids.slice(0);
+ mv_line_ids.sort(function (a, b) {
+ return a - b;
+ });
+ assert.deepEqual(
+ mv_line_ids,
+ [6, 19, 21],
+ "Reconciliation rpc payload, mv_line_ids are correct"
+ );
+
+ // Index aiming at the correct object in the list
+ var idx = _.has(
+ args.args[0][0].new_mv_line_dicts[0],
+ "journal_id"
+ )
+ ? 0
+ : 1;
+ assert.deepEqual(
+ _.pick(
+ args.args[0][0].new_mv_line_dicts[idx],
+ "account_id",
+ "name",
+ "credit",
+ "debit",
+ "journal_id"
+ ),
+ {
+ account_id: 287,
+ name: "dummy text",
+ credit: 0,
+ debit: 180,
+ journal_id: 8,
+ },
+ "Reconciliation rpc payload, new_mv_line_dicts.gift is correct"
+ );
+ assert.deepEqual(
+ _.pick(
+ args.args[0][0].new_mv_line_dicts[1 - idx],
+ "account_id",
+ "name",
+ "credit",
+ "debit",
+ "tax_repartition_line_id"
+ ),
+ {
+ account_id: 287,
+ name: "Tax 20.00%",
+ credit: 0,
+ debit: 36,
+ tax_repartition_line_id: 2,
+ },
+ "Reconciliation rpc payload, new_mv_line_dicts.tax is correct"
+ );
+ }
+ return this._super.apply(this, arguments);
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ var widget = clientAction.widgets[0];
+
+ // Select invoice of 1k$, payment of 1k$ and payment of 180$
+ var $tableToReconcile = widget.$(".match");
+ var defs = _.map([6, 19, 21], function (id) {
+ return testUtils.dom.click(
+ $tableToReconcile.find(
+ "tr.mv_line[data-line-id=" + id + "]:first td:first-child"
+ )
+ );
+ });
+ await Promise.all(defs);
+ assert.verifySteps([], "No rpc done");
+
+ // Store the money in excess to the "account receivable" account with 20% taxes
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+ var $reconcileForm = widget.$(".create");
+ await testUtils.dom.click(
+ $reconcileForm.find(".create_account_id input")
+ );
+ $(
+ ".ui-autocomplete .ui-menu-item a:contains(101200 Account Receivable)"
+ )
+ .trigger("mouseover")
+ .trigger("click");
+ await testUtils.nextTick();
+ assert.verifySteps(["Account"], "Account rpc done");
+
+ await testUtils.dom.click($reconcileForm.find(".create_tax_id input"));
+ $(".ui-autocomplete .ui-menu-item a:contains(Tax 20.00%)")
+ .trigger("mouseover")
+ .trigger("click");
+ await testUtils.nextTick();
+ assert.verifySteps(["Tax"], "Tax rpc done");
+
+ await testUtils.dom.click(
+ $reconcileForm.find(".create_journal_id input"),
+ {allowInvisible: true}
+ );
+ $(".ui-autocomplete .ui-menu-item a:contains(company 1 journal)")
+ .trigger("mouseover")
+ .trigger("click");
+ await testUtils.nextTick();
+ await testUtils.fields.editAndTrigger(
+ $reconcileForm.find(".create_label input"),
+ "dummy text",
+ "input"
+ );
+ await testUtils.dom.click($reconcileForm.find(".create_label input"));
+ assert.verifySteps(["Journal"], "Journal rpc done");
+
+ // Verify the two (gift + tax) lines were added to the list
+ var $newLines = widget.$("tr.mv_line[data-line-id^=createLine]");
+ var idx =
+ $($($newLines[0]).find("td")[3]).text().trim() === "dummy text"
+ ? 0
+ : 1;
+
+ var $newLineGiftTds = $($newLines[1 - idx]).find("td");
+ assert.equal(
+ $($newLineGiftTds[0])
+ .text()
+ .trim()
+ .replace(/[\u200B]/g, ""),
+ "101200",
+ "Gift line account number is valid"
+ );
+ assert.equal(
+ $($newLineGiftTds[1]).text().trim(),
+ "New",
+ "Gift line is flagged as new"
+ );
+ assert.equal(
+ $($newLineGiftTds[2]).text().trim(),
+ "dummy text",
+ "Gift line has the correct label"
+ );
+ assert.equal(
+ $($newLineGiftTds[3]).text().trim(),
+ "180.00",
+ "Gift line has the correct left amount"
+ );
+ assert.equal(
+ $($newLineGiftTds[4]).text().trim(),
+ "",
+ "Gift line has the correct right amount"
+ );
+
+ var $newLineTaxeTds = $($newLines[idx]).find("td");
+ assert.equal(
+ $($newLineTaxeTds[0])
+ .text()
+ .trim()
+ .replace(/[\u200B]/g, ""),
+ "101200",
+ "Tax line account number is valid"
+ );
+ assert.equal(
+ $($newLineTaxeTds[1]).text().trim(),
+ "New",
+ "Tax line is flagged as new"
+ );
+ assert.equal(
+ $($newLineTaxeTds[2]).text().trim(),
+ "Tax 20.00%",
+ "Tax line has the correct label"
+ );
+ assert.equal(
+ $($newLineTaxeTds[3]).text().trim(),
+ "36.00",
+ "Tax line has the correct left amount"
+ );
+ assert.equal(
+ $($newLineTaxeTds[4]).text().trim(),
+ "",
+ "Tax line has the correct right amount"
+ );
+
+ // Reconcile
+ await testUtils.dom.click(
+ widget.$("button.o_reconcile.btn.btn-primary:first")
+ );
+ assert.ok(true, "No error in reconciliation");
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconcile temporarily and ask to check", async function (
+ assert
+ ) {
+ assert.expect(4);
+ this.params.options.context.to_check = true;
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+
+ testUtils.mock.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+ await clientAction.appendTo($("#qunit-fixture"));
+ var widget = clientAction.widgets[0];
+
+ // Add a line as proposition
+ // open the first line
+ await testUtils.nextTick();
+ await testUtils.dom.click(
+ widget.$(".accounting_view tfoot td.cell_label")
+ );
+ await testUtils.dom.click(
+ widget.$('.o_notebook .nav-link[href*="notebook_page_create"]')
+ );
+
+ var $reconcileForm = widget.$(".create");
+ $reconcileForm
+ .find(".create_account_id input")
+ .val("499001 Suspense Account")
+ .keydown()
+ .keyup();
+ await testUtils.dom.click(
+ $reconcileForm.find(".create_account_id input")
+ );
+ $(".ui-autocomplete .ui-menu-item a:contains(499001 Suspense Account)")
+ .trigger("mouseover")
+ .trigger("click");
+ await testUtils.nextTick();
+
+ assert.equal(
+ $("button.o_validate.btn.btn-secondary.text-warning:first").length,
+ 0,
+ "should not display reconcile button in orange"
+ );
+ await testUtils.dom.click(
+ $reconcileForm.find(".create_to_check input")
+ );
+ assert.equal(
+ $("button.o_validate.btn.btn-secondary.text-warning:first").length,
+ 1,
+ "should display reconcile button in orange"
+ );
+
+ testUtils.mock.intercept(clientAction, "call_service", function (
+ event
+ ) {
+ assert.deepEqual(
+ event.data.args[1].args,
+ [
+ [5],
+ [
+ {
+ partner_id: 8,
+ counterpart_aml_dicts: [],
+ payment_aml_ids: [],
+ new_aml_dicts: [
+ {
+ account_id: 499,
+ credit: 1175,
+ debit: 0,
+ analytic_tag_ids: [[6, null, []]],
+ name: "SAJ/2014/002 and SAJ/2014/003",
+ },
+ ],
+ to_check: true,
+ },
+ ],
+ ],
+ "Should call process_bank_statement_line with to_check set to true"
+ );
+ var def = testUtils.makeTestPromise();
+ def.abort = function () {};
+ event.data.callback(def);
+ });
+
+ await testUtils.dom.click(
+ widget.$("button.o_validate.btn.btn-secondary:first")
+ );
+ assert.ok(true, "No error in reconciliation");
+
+ clientAction.destroy();
+ });
+
+ QUnit.test("Reconciliation Models handle analytic tags", async function (
+ assert
+ ) {
+ assert.expect(6);
+
+ var clientAction = new ReconciliationClientAction.StatementAction(
+ null,
+ this.params.options
+ );
+ testUtils.addMockEnvironment(clientAction, {
+ data: this.params.data,
+ mockRPC: function (route, args) {
+ if (args.method === "process_bank_statement_line") {
+ var new_aml_dicts = args.args[1][0].new_aml_dicts;
+ assert.strictEqual(new_aml_dicts.length, 2);
+ // I personnally judge the following use case rotten, since
+ // the first and the second line wouldn't have the same tags
+ assert.deepEqual(new_aml_dicts[0].analytic_tag_ids, [
+ [6, null, [1, 2]],
+ ]);
+ assert.deepEqual(new_aml_dicts[1].analytic_tag_ids, [
+ [6, null, [2]],
+ ]);
+ }
+ return this._super(route, args);
+ },
+ session: {
+ currencies: {
+ 3: {
+ digits: [69, 2],
+ position: "before",
+ symbol: "$",
+ },
+ },
+ user_has_group: function (group) {
+ if (
+ group === "analytic.group_analytic_tags" ||
+ group === "analytic.group_analytic_accounting"
+ ) {
+ return $.when(true);
+ }
+ return this._super.apply(this, arguments);
+ },
+ },
+ archs: {
+ "account.bank.statement.line,false,search":
+ '',
+ },
+ });
+
+ await clientAction.appendTo($("#qunit-fixture"));
+ await testUtils.nextTick();
+
+ // The first reconciliation "line" is where it happens
+ var widget = clientAction.widgets[0];
+
+ await testUtilsDom.click(widget.$(".nav-create:visible"));
+ await testUtilsDom.click(
+ widget.$('.quick_add button:contains("Double")')
+ );
+ assert.containsN(
+ widget,
+ ".create_analytic_tag_ids .o_field_many2manytags .badge",
+ 2,
+ "Two tags are loaded"
+ );
+ assert.containsOnce(
+ widget,
+ '.create_analytic_tag_ids .o_field_many2manytags .badge:contains("Come together")',
+ "Tags should have a name"
+ );
+ assert.containsOnce(
+ widget,
+ '.create_analytic_tag_ids .o_field_many2manytags .badge:contains("Right now")',
+ "Tags should have a name"
+ );
+
+ await testUtilsDom.click(
+ widget.$(
+ ".create_analytic_tag_ids .o_field_many2manytags .badge a.o_delete:first()"
+ )
+ );
+
+ await testUtilsDom.click(widget.$(".o_reconcile:visible"));
+
+ clientAction.destroy();
+ });
+ }
+ );
+});
From aa43f5881b75615239bcb28e222df0e38319e3b4 Mon Sep 17 00:00:00 2001
From: Francisco Ivan Anton Prieto
Date: Mon, 14 Dec 2020 03:09:33 +0100
Subject: [PATCH 2/3] [ADD] account_reconciliation_widget: first working alpha
---
account_reconciliation_widget/README.rst | 86 ++++
account_reconciliation_widget/__init__.py | 1 +
account_reconciliation_widget/__manifest__.py | 24 +
.../models/__init__.py | 5 +
.../models/account_bank_statement.py | 324 +++----------
.../models/account_journal.py | 15 +
.../models/account_move.py | 2 +-
.../models/reconciliation_widget.py | 143 +++---
.../readme/CONTRIBUTORS.rst | 1 +
.../readme/DESCRIPTION.rst | 2 +
.../readme/USAGE.rst | 9 +
.../security/ir.model.access.csv | 2 +
.../static/description/index.html | 429 ++++++++++++++++++
.../reconciliation/reconciliation_action.js | 41 +-
.../js/reconciliation/reconciliation_model.js | 41 +-
.../reconciliation/reconciliation_renderer.js | 26 +-
.../static/src/xml/account_reconciliation.xml | 4 +-
.../tests/account_reconciliation_tests.js | 51 ++-
.../tests/__init__.py | 1 +
.../tests/test_reconciliation_widget.py | 239 ++++++++++
.../views/account_bank_statement_view.xml | 25 +
.../views/account_journal_dashboard_view.xml | 47 ++
.../views/account_view.xml | 30 ++
.../views/assets.xml | 42 ++
.../odoo/addons/account_reconciliation_widget | 1 +
setup/account_reconciliation_widget/setup.py | 6 +
26 files changed, 1188 insertions(+), 409 deletions(-)
create mode 100644 account_reconciliation_widget/README.rst
create mode 100644 account_reconciliation_widget/__init__.py
create mode 100644 account_reconciliation_widget/__manifest__.py
create mode 100644 account_reconciliation_widget/models/__init__.py
create mode 100644 account_reconciliation_widget/readme/CONTRIBUTORS.rst
create mode 100644 account_reconciliation_widget/readme/DESCRIPTION.rst
create mode 100644 account_reconciliation_widget/readme/USAGE.rst
create mode 100644 account_reconciliation_widget/security/ir.model.access.csv
create mode 100644 account_reconciliation_widget/static/description/index.html
create mode 100644 account_reconciliation_widget/tests/__init__.py
create mode 100644 account_reconciliation_widget/tests/test_reconciliation_widget.py
create mode 100644 account_reconciliation_widget/views/account_bank_statement_view.xml
create mode 100644 account_reconciliation_widget/views/account_journal_dashboard_view.xml
create mode 100644 account_reconciliation_widget/views/account_view.xml
create mode 100644 account_reconciliation_widget/views/assets.xml
create mode 120000 setup/account_reconciliation_widget/odoo/addons/account_reconciliation_widget
create mode 100644 setup/account_reconciliation_widget/setup.py
diff --git a/account_reconciliation_widget/README.rst b/account_reconciliation_widget/README.rst
new file mode 100644
index 00000000..0aaf1cc5
--- /dev/null
+++ b/account_reconciliation_widget/README.rst
@@ -0,0 +1,86 @@
+=============================
+account_reconciliation_widget
+=============================
+
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ !! This file is generated by oca-gen-addon-readme !!
+ !! changes will be overwritten. !!
+ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
+ :target: https://odoo-community.org/page/development-status
+ :alt: Beta
+.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
+ :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
+ :alt: License: AGPL-3
+.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount_reconciliation_widget-lightgray.png?logo=github
+ :target: https://github.com/OCA/account_reconciliation_widget/tree/14.0/account_reconciliation_widget
+ :alt: OCA/account_reconciliation_widget
+.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
+ :target: https://translation.odoo-community.org/projects/account_reconciliation_widget-14-0/account_reconciliation_widget-14-0-account_reconciliation_widget
+ :alt: Translate me on Weblate
+
+|badge1| |badge2| |badge3| |badge4|
+
+This module restores account reconciliation widget moved from Odoo community to enterpise in V. 14.0
+Provides two widgets designed to reconcile move lines in a easy way: one focused on bank statements and another for generic use.
+
+**Table of contents**
+
+.. contents::
+ :local:
+
+Usage
+=====
+
+With an user with full accounting features enabled:
+
+Invoicing --> Accounting --> Actions --> Reconciliation.
+
+From journal items list view you can select check of them and click Action --> Reconcile.
+
+From accounting dashboard you can use reconcile button in Bank / Cash journals.
+
+Also, you can navigate to statements and use the reconcile button.
+
+
+Bug Tracker
+===========
+
+Bugs are tracked on `GitHub Issues `_.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+`feedback `_.
+
+Do not contact contributors directly about support or help with technical issues.
+
+Credits
+=======
+
+Authors
+~~~~~~~
+
+* Ozono Multimedia
+
+Contributors
+~~~~~~~~~~~~
+
+* Tecnativa - Pedro M. Baeza
+
+
+Maintainers
+~~~~~~~~~~~
+
+This module is maintained by the OCA.
+
+.. image:: https://odoo-community.org/logo.png
+ :alt: Odoo Community Association
+ :target: https://odoo-community.org
+
+OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.
+
+This module is part of the `OCA/account_reconciliation_widget `_ project on GitHub.
+
+You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
diff --git a/account_reconciliation_widget/__init__.py b/account_reconciliation_widget/__init__.py
new file mode 100644
index 00000000..0650744f
--- /dev/null
+++ b/account_reconciliation_widget/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/account_reconciliation_widget/__manifest__.py b/account_reconciliation_widget/__manifest__.py
new file mode 100644
index 00000000..26e6e107
--- /dev/null
+++ b/account_reconciliation_widget/__manifest__.py
@@ -0,0 +1,24 @@
+# Copyright 2020 Ozono Multimedia - Iván Antón
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+{
+ "name": "account_reconciliation_widget",
+ "version": "14.0.1.0.0",
+ "category": "Accounting",
+ "license": "AGPL-3",
+ "summary": "Account reconciliation widget",
+ "author": "Odoo, Ozono Multimedia, Odoo Community Association (OCA)",
+ "website": "https://github.com/OCA/account-reconcile",
+ "depends": ["account"],
+ "data": [
+ "security/ir.model.access.csv",
+ "views/assets.xml",
+ "views/account_view.xml",
+ "views/account_bank_statement_view.xml",
+ "views/account_journal_dashboard_view.xml",
+ ],
+ "qweb": [
+ "static/src/xml/account_reconciliation.xml",
+ ],
+ "installable": True,
+}
diff --git a/account_reconciliation_widget/models/__init__.py b/account_reconciliation_widget/models/__init__.py
new file mode 100644
index 00000000..7957603b
--- /dev/null
+++ b/account_reconciliation_widget/models/__init__.py
@@ -0,0 +1,5 @@
+from . import account_move
+from . import account_bank_statement
+from . import account_journal
+from . import reconciliation_widget
+from . import res_company
diff --git a/account_reconciliation_widget/models/account_bank_statement.py b/account_reconciliation_widget/models/account_bank_statement.py
index cee53c90..0916d919 100644
--- a/account_reconciliation_widget/models/account_bank_statement.py
+++ b/account_reconciliation_widget/models/account_bank_statement.py
@@ -33,6 +33,7 @@ class AccountBankStatementLine(models.Model):
_inherit = "account.bank.statement.line"
+ # FIXME: is this necessary now?
move_name = fields.Char(
string="Journal Entry Name",
readonly=True,
@@ -129,22 +130,23 @@ class AccountBankStatementLine(models.Model):
and user_type_id not in account_types
):
account_types |= user_type_id
- if suspense_moves_mode:
- if any(not line.journal_entry_ids for line in self):
- raise UserError(
- _(
- "Some selected statement line were not already "
- "reconciled with an account move."
- )
- )
- else:
- if any(line.journal_entry_ids for line in self):
- raise UserError(
- _(
- "A selected statement line was already reconciled with "
- "an account move."
- )
- )
+ # FIXME: review
+ # if suspense_moves_mode:
+ # if any(not line.journal_entry_ids for line in self):
+ # raise UserError(
+ # _(
+ # "Some selected statement line were not already "
+ # "reconciled with an account move."
+ # )
+ # )
+ # else:
+ # if any(line.journal_entry_ids for line in self):
+ # raise UserError(
+ # _(
+ # "A selected statement line was already reconciled with "
+ # "an account move."
+ # )
+ # )
# Fully reconciled moves are just linked to the bank statement
total = self.amount
@@ -171,7 +173,7 @@ class AccountBankStatementLine(models.Model):
# it.
aml_rec.move_id.date = self.date
aml_rec.payment_id.payment_date = self.date
- aml_rec.move_id.post()
+ aml_rec.move_id.action_post()
# We check the paid status of the invoices reconciled with this
# payment
for invoice in aml_rec.payment_id.reconciled_invoice_ids:
@@ -181,86 +183,10 @@ class AccountBankStatementLine(models.Model):
# (eg. invoice), in which case we reconcile the existing and the new
# move lines together, or being a write-off.
if counterpart_aml_dicts or new_aml_dicts:
-
- # Create the move
- self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
- move_vals = self._prepare_reconciliation_move(self.statement_id.name)
- if suspense_moves_mode:
- self.button_cancel_reconciliation()
- move = (
- self.env["account.move"]
- .with_context(default_journal_id=move_vals["journal_id"])
- .create(move_vals)
+ counterpart_moves = self._create_counterpart_and_new_aml(
+ counterpart_moves, counterpart_aml_dicts, new_aml_dicts
)
- counterpart_moves = counterpart_moves | move
- # Create The payment
- payment = self.env["account.payment"]
- partner_id = (
- self.partner_id
- or (aml_dict.get("move_line") and aml_dict["move_line"].partner_id)
- or self.env["res.partner"]
- )
- if abs(total) > 0.00001:
- payment_vals = self._prepare_payment_vals(total)
- if not payment_vals["partner_id"]:
- payment_vals["partner_id"] = partner_id.id
- if payment_vals["partner_id"] and len(account_types) == 1:
- payment_vals["partner_type"] = (
- "customer"
- if account_types == receivable_account_type
- else "supplier"
- )
- payment = payment.create(payment_vals)
-
- # Complete dicts to create both counterpart move lines and write-offs
- to_create = counterpart_aml_dicts + new_aml_dicts
- date = self.date or fields.Date.today()
- for aml_dict in to_create:
- aml_dict["move_id"] = move.id
- aml_dict["partner_id"] = self.partner_id.id
- aml_dict["statement_line_id"] = self.id
- self._prepare_move_line_for_currency(aml_dict, date)
-
- # Create write-offs
- for aml_dict in new_aml_dicts:
- aml_dict["payment_id"] = payment and payment.id or False
- aml_obj.with_context(check_move_validity=False).create(aml_dict)
-
- # Create counterpart move lines and reconcile them
- for aml_dict in counterpart_aml_dicts:
- if (
- aml_dict["move_line"].payment_id
- and not aml_dict["move_line"].statement_line_id
- ):
- aml_dict["move_line"].write({"statement_line_id": self.id})
- if aml_dict["move_line"].partner_id.id:
- aml_dict["partner_id"] = aml_dict["move_line"].partner_id.id
- aml_dict["account_id"] = aml_dict["move_line"].account_id.id
- aml_dict["payment_id"] = payment and payment.id or False
-
- counterpart_move_line = aml_dict.pop("move_line")
- new_aml = aml_obj.with_context(check_move_validity=False).create(
- aml_dict
- )
-
- (new_aml | counterpart_move_line).reconcile()
-
- self._check_invoice_state(counterpart_move_line.move_id)
-
- # Balance the move
- st_line_amount = -sum([x.balance for x in move.line_ids])
- aml_dict = self._prepare_reconciliation_move_line(move, st_line_amount)
- aml_dict["payment_id"] = payment and payment.id or False
- aml_obj.with_context(check_move_validity=False).create(aml_dict)
-
- # Needs to be called manually as lines were created 1 by 1
- move.update_lines_tax_exigibility()
- move.post()
- # record the move name on the statement line to be able to retrieve
- # it in case of unreconciliation
- self.write({"move_name": move.name})
- payment and payment.write({"payment_reference": move.name})
elif self.move_name:
raise UserError(
_(
@@ -277,177 +203,71 @@ class AccountBankStatementLine(models.Model):
if self.account_number and self.partner_id and not self.bank_account_id:
# Search bank account without partner to handle the case the
# res.partner.bank already exists but is set on a different partner.
- self.bank_account_id = self._find_or_create_bank_account()
+ self.partner_bank_id = self._find_or_create_bank_account()
counterpart_moves._check_balanced()
return counterpart_moves
- def _prepare_reconciliation_move(self, move_ref):
- """Prepare the dict of values to create the move from a statement line.
- This method may be overridden to adapt domain logic through model
- inheritance (make sure to call super() to establish a clean extension
- chain).
+ def _create_counterpart_and_new_aml(
+ self, counterpart_moves, counterpart_aml_dicts, new_aml_dicts
+ ):
- :param char move_ref: will be used as the reference of the generated
- account move
- :return: dict of value to create() the account.move
- """
- ref = move_ref or ""
- if self.ref:
- ref = move_ref + " - " + self.ref if move_ref else self.ref
- data = {
- "type": "entry",
- "journal_id": self.statement_id.journal_id.id,
- "currency_id": self.statement_id.currency_id.id,
- "date": self.statement_id.accounting_date or self.date,
- "partner_id": self.partner_id.id,
- "ref": ref,
- }
- if self.move_name:
- data.update(name=self.move_name)
- return data
+ aml_obj = self.env["account.move.line"]
- def _prepare_reconciliation_move_line(self, move, amount):
- """Prepare the dict of values to balance the move.
+ # Delete previous move_lines
+ self.move_id.line_ids.with_context(force_delete=True).unlink()
- :param recordset move: the account.move to link the move line
- :param dict move: a dict of vals of a account.move which will be created
- later
- :param float amount: the amount of transaction that wasn't already
- reconciled
- """
- company_currency = self.journal_id.company_id.currency_id
- statement_currency = self.journal_id.currency_id or company_currency
- st_line_currency = self.currency_id or statement_currency
- amount_currency = False
- st_line_currency_rate = (
- self.currency_id and (self.amount_currency / self.amount) or False
- )
- if isinstance(move, dict):
- amount_sum = sum(x[2].get("amount_currency", 0) for x in move["line_ids"])
- else:
- amount_sum = sum(x.amount_currency for x in move.line_ids)
- # We have several use case here to compare the currency and amount
- # currency of counterpart line to balance the move:
- if (
- st_line_currency != company_currency
- and st_line_currency == statement_currency
- ):
- # company in currency A, statement in currency B and transaction in
- # currency B
- # counterpart line must have currency B and correct amount is
- # inverse of already existing lines
- amount_currency = -amount_sum
- elif (
- st_line_currency != company_currency
- and statement_currency == company_currency
- ):
- # company in currency A, statement in currency A and transaction in
- # currency B
- # counterpart line must have currency B and correct amount is
- # inverse of already existing lines
- amount_currency = -amount_sum
- elif (
- st_line_currency != company_currency
- and st_line_currency != statement_currency
- ):
- # company in currency A, statement in currency B and transaction in
- # currency C
- # counterpart line must have currency B and use rate between B and
- # C to compute correct amount
- amount_currency = -amount_sum / st_line_currency_rate
- elif (
- st_line_currency == company_currency
- and statement_currency != company_currency
- ):
- # company in currency A, statement in currency B and transaction in
- # currency A
- # counterpart line must have currency B and amount is computed using
- # the rate between A and B
- amount_currency = amount / st_line_currency_rate
+ # Create liquidity line
+ liquidity_aml_dict = self._prepare_liquidity_move_line_vals()
+ aml_obj.with_context(check_move_validity=False).create(liquidity_aml_dict)
- # last case is company in currency A, statement in currency A and
- # transaction in currency A
- # and in this case counterpart line does not need any second currency
- # nor amount_currency
+ self.sequence = self.statement_id.line_ids.ids.index(self.id) + 1
+ counterpart_moves = counterpart_moves | self.move_id
- # Check if default_debit or default_credit account are properly configured
- account_id = (
- amount >= 0
- and self.statement_id.journal_id.default_credit_account_id.id
- or self.statement_id.journal_id.default_debit_account_id.id
- )
+ # Complete dicts to create both counterpart move lines and write-offs
+ to_create = counterpart_aml_dicts + new_aml_dicts
+ date = self.date or fields.Date.today()
+ for aml_dict in to_create:
+ aml_dict["move_id"] = self.move_id.id
+ aml_dict["partner_id"] = self.partner_id.id
+ aml_dict["statement_line_id"] = self.id
+ self._prepare_move_line_for_currency(aml_dict, date)
- if not account_id:
- raise UserError(
- _(
- "No default debit and credit account defined on journal %s "
- "(ids: %s)."
- % (
- self.statement_id.journal_id.name,
- self.statement_id.journal_id.ids,
- )
- )
- )
+ # Create write-offs
+ for aml_dict in new_aml_dicts:
+ aml_obj.with_context(check_move_validity=False).create(aml_dict)
- aml_dict = {
- "name": self.name,
- "partner_id": self.partner_id and self.partner_id.id or False,
- "account_id": account_id,
- "credit": amount < 0 and -amount or 0.0,
- "debit": amount > 0 and amount or 0.0,
- "statement_line_id": self.id,
- "currency_id": statement_currency != company_currency
- and statement_currency.id
- or (st_line_currency != company_currency and st_line_currency.id or False),
- "amount_currency": amount_currency,
- }
- if isinstance(move, self.env["account.move"].__class__):
- aml_dict["move_id"] = move.id
- return aml_dict
+ # Create counterpart move lines and reconcile them
+ aml_to_reconcile = []
+ for aml_dict in counterpart_aml_dicts:
+ if not aml_dict["move_line"].statement_line_id:
+ aml_dict["move_line"].write({"statement_line_id": self.id})
+ if aml_dict["move_line"].partner_id.id:
+ aml_dict["partner_id"] = aml_dict["move_line"].partner_id.id
+ aml_dict["account_id"] = aml_dict["move_line"].account_id.id
- def _get_communication(self, payment_method_id):
- return self.name or ""
+ counterpart_move_line = aml_dict.pop("move_line")
+ new_aml = aml_obj.with_context(check_move_validity=False).create(aml_dict)
- def _prepare_payment_vals(self, total):
- """Prepare the dict of values to create the payment from a statement
- line. This method may be overridden for update dict
- through model inheritance (make sure to call super() to establish a
- clean extension chain).
+ aml_to_reconcile.append((new_aml, counterpart_move_line))
- :param float total: will be used as the amount of the generated payment
- :return: dict of value to create() the account.payment
- """
- self.ensure_one()
- partner_type = False
- if self.partner_id:
- if total < 0:
- partner_type = "supplier"
- else:
- partner_type = "customer"
- if not partner_type and self.env.context.get("default_partner_type"):
- partner_type = self.env.context["default_partner_type"]
- currency = self.journal_id.currency_id or self.company_id.currency_id
- payment_methods = (
- (total > 0)
- and self.journal_id.inbound_payment_method_ids
- or self.journal_id.outbound_payment_method_ids
- )
- return {
- "payment_method_id": payment_methods and payment_methods[0].id or False,
- "payment_type": total > 0 and "inbound" or "outbound",
- "partner_id": self.partner_id.id,
- "partner_type": partner_type,
- "journal_id": self.statement_id.journal_id.id,
- "payment_date": self.date,
- "state": "reconciled",
- "currency_id": currency.id,
- "amount": abs(total),
- "communication": self._get_communication(
- payment_methods[0] if payment_methods else False
- ),
- "name": self.statement_id.name or _("Bank Statement %s") % self.date,
- }
+ # Post to allow reconcile
+ self.move_id.with_context(skip_account_move_synchronization=True).action_post()
+
+ # Reconcile new lines with counterpart
+ for new_aml, counterpart_move_line in aml_to_reconcile:
+ (new_aml | counterpart_move_line).reconcile()
+
+ self._check_invoice_state(counterpart_move_line.move_id)
+
+ # Needs to be called manually as lines were created 1 by 1
+ self.move_id.update_lines_tax_exigibility()
+ self.move_id.with_context(skip_account_move_synchronization=True).action_post()
+ # record the move name on the statement line to be able to retrieve
+ # it in case of unreconciliation
+ self.write({"move_name": self.move_id.name})
+
+ return counterpart_moves
def _prepare_move_line_for_currency(self, aml_dict, date):
self.ensure_one()
diff --git a/account_reconciliation_widget/models/account_journal.py b/account_reconciliation_widget/models/account_journal.py
index 261c8bbc..3650bc76 100644
--- a/account_reconciliation_widget/models/account_journal.py
+++ b/account_reconciliation_widget/models/account_journal.py
@@ -20,3 +20,18 @@ class AccountJournal(models.Model):
"company_ids": self.mapped("company_id").ids,
},
}
+
+ def action_open_reconcile_to_check(self):
+ self.ensure_one()
+ ids = self.to_check_ids().ids
+ action_context = {
+ "show_mode_selector": False,
+ "company_ids": self.mapped("company_id").ids,
+ }
+ action_context.update({"suspense_moves_mode": True})
+ action_context.update({"statement_line_ids": ids})
+ return {
+ "type": "ir.actions.client",
+ "tag": "bank_statement_reconciliation_view",
+ "context": action_context,
+ }
diff --git a/account_reconciliation_widget/models/account_move.py b/account_reconciliation_widget/models/account_move.py
index e1a68928..28d07239 100644
--- a/account_reconciliation_widget/models/account_move.py
+++ b/account_reconciliation_widget/models/account_move.py
@@ -121,7 +121,7 @@ class AccountMoveLine(models.Model):
# post all the writeoff moves at once
if writeoff_moves:
- writeoff_moves.post()
+ writeoff_moves.action_post()
# Return the writeoff move.line which is to be reconciled
return line_to_reconcile
diff --git a/account_reconciliation_widget/models/reconciliation_widget.py b/account_reconciliation_widget/models/reconciliation_widget.py
index ebe2c710..0d0a3d1b 100644
--- a/account_reconciliation_widget/models/reconciliation_widget.py
+++ b/account_reconciliation_widget/models/reconciliation_widget.py
@@ -1,5 +1,7 @@
import copy
+from psycopg2 import sql
+
from odoo import _, api, models
from odoo.exceptions import UserError
from odoo.osv import expression
@@ -86,8 +88,7 @@ class AccountReconciliation(models.AbstractModel):
# Blue lines = payment on bank account not assigned to a statement yet
aml_accounts = [
- st_line.journal_id.default_credit_account_id.id,
- st_line.journal_id.default_debit_account_id.id,
+ st_line.journal_id.default_account_id.id,
]
if partner_id is None:
@@ -106,7 +107,8 @@ class AccountReconciliation(models.AbstractModel):
from_clause, where_clause, where_clause_params = (
self.env["account.move.line"]._where_calc(domain).get_sql()
)
- query_str = """
+ query_str = sql.SQL(
+ """
SELECT "account_move_line".id FROM {from_clause}
{where_str}
ORDER BY ("account_move_line".debit -
@@ -115,10 +117,11 @@ class AccountReconciliation(models.AbstractModel):
"account_move_line".id ASC
{limit_str}
""".format(
- from_clause=from_clause,
- where_str=where_clause and (" WHERE %s" % where_clause) or "",
- amount=st_line.amount,
- limit_str=limit and " LIMIT %s" or "",
+ from_clause=from_clause,
+ where_str=where_clause and (" WHERE %s" % where_clause) or "",
+ amount=st_line.amount,
+ limit_str=limit and " LIMIT %s" or "",
+ )
)
params = where_clause_params + (limit and [limit] or [])
self.env["account.move"].flush()
@@ -290,7 +293,6 @@ class AccountReconciliation(models.AbstractModel):
"""
if not bank_statement_line_ids:
return {}
- suspense_moves_mode = self._context.get("suspense_moves_mode")
bank_statements = (
self.env["account.bank.statement.line"]
.browse(bank_statement_line_ids)
@@ -301,17 +303,13 @@ class AccountReconciliation(models.AbstractModel):
SELECT line.id
FROM account_bank_statement_line line
LEFT JOIN res_partner p on p.id = line.partner_id
- WHERE line.account_id IS NULL
+ INNER JOIN account_bank_statement st ON line.statement_id = st.id
+ AND st.state = 'posted'
+ WHERE line.is_reconciled = FALSE
AND line.amount != 0.0
AND line.id IN %(ids)s
- {cond}
GROUP BY line.id
- """.format(
- cond=not suspense_moves_mode
- and """AND NOT EXISTS (SELECT 1 from account_move_line aml
- WHERE aml.statement_line_id = line.id)"""
- or "",
- )
+ """
self.env.cr.execute(query, {"ids": tuple(bank_statement_line_ids)})
domain = [["id", "in", [line.get("id") for line in self.env.cr.dictfetchall()]]]
@@ -327,6 +325,9 @@ class AccountReconciliation(models.AbstractModel):
results.update(
{
+ "statement_id": len(bank_statements_left) == 1
+ and bank_statements_left.id
+ or False,
"statement_name": len(bank_statements_left) == 1
and bank_statements_left.name
or False,
@@ -403,7 +404,6 @@ class AccountReconciliation(models.AbstractModel):
)
if aml_ids:
aml = MoveLine.browse(aml_ids)
- aml._check_reconcile_validity()
account = aml[0].account_id
currency = account.currency_id or account.company_id.currency_id
return {
@@ -489,8 +489,7 @@ class AccountReconciliation(models.AbstractModel):
WHERE l.account_id = a.id
{inner_where}
AND l.amount_residual != 0
- AND (move.state = 'posted' OR (move.state = 'draft'
- AND journal.post_at = 'bank_rec'))
+ AND move.state = 'posted'
)
""".format(
inner_where=is_partner and "AND l.partner_id = p.id" or " "
@@ -504,8 +503,7 @@ class AccountReconciliation(models.AbstractModel):
WHERE l.account_id = a.id
{inner_where}
AND l.amount_residual > 0
- AND (move.state = 'posted'
- OR (move.state = 'draft' AND journal.post_at = 'bank_rec'))
+ AND move.state = 'posted'
)
AND EXISTS (
SELECT NULL
@@ -515,13 +513,13 @@ class AccountReconciliation(models.AbstractModel):
WHERE l.account_id = a.id
{inner_where}
AND l.amount_residual < 0
- AND (move.state = 'posted'
- OR (move.state = 'draft' AND journal.post_at = 'bank_rec'))
+ AND move.state = 'posted'
)
""".format(
inner_where=is_partner and "AND l.partner_id = p.id" or " "
)
- query = """
+ query = sql.SQL(
+ """
SELECT {select} account_id, account_name, account_code, max_date
FROM (
SELECT {inner_select}
@@ -549,35 +547,36 @@ class AccountReconciliation(models.AbstractModel):
) as s
{outer_where}
""".format(
- select=is_partner
- and "partner_id, partner_name, to_char(last_time_entries_checked, "
- "'YYYY-MM-DD') AS last_time_entries_checked,"
- or " ",
- inner_select=is_partner
- and "p.id AS partner_id, p.name AS partner_name, "
- "p.last_time_entries_checked AS last_time_entries_checked,"
- or " ",
- inner_from=is_partner
- and "RIGHT JOIN res_partner p ON (l.partner_id = p.id)"
- or " ",
- where1=is_partner
- and " "
- or "AND ((at.type <> 'payable' AND at.type <> 'receivable') "
- "OR l.partner_id IS NULL)",
- where2=account_type and "AND at.type = %(account_type)s" or "",
- where3=res_ids and "AND " + res_alias + ".id in %(res_ids)s" or "",
- company_id=self.env.company.id,
- where4=aml_ids and "AND l.id IN %(aml_ids)s" or " ",
- where5=all_entries and all_entries_query or only_dual_entries_query,
- group_by1=is_partner and "l.partner_id, p.id," or " ",
- group_by2=is_partner and ", p.last_time_entries_checked" or " ",
- order_by=is_partner
- and "ORDER BY p.last_time_entries_checked"
- or "ORDER BY a.code",
- outer_where=is_partner
- and "WHERE (last_time_entries_checked IS NULL "
- "OR max_date > last_time_entries_checked)"
- or " ",
+ select=is_partner
+ and "partner_id, partner_name, to_char(last_time_entries_checked, "
+ "'YYYY-MM-DD') AS last_time_entries_checked,"
+ or " ",
+ inner_select=is_partner
+ and "p.id AS partner_id, p.name AS partner_name, "
+ "p.last_time_entries_checked AS last_time_entries_checked,"
+ or " ",
+ inner_from=is_partner
+ and "RIGHT JOIN res_partner p ON (l.partner_id = p.id)"
+ or " ",
+ where1=is_partner
+ and " "
+ or "AND ((at.type <> 'payable' AND at.type <> 'receivable') "
+ "OR l.partner_id IS NULL)",
+ where2=account_type and "AND at.type = %(account_type)s" or "",
+ where3=res_ids and "AND " + res_alias + ".id in %(res_ids)s" or "",
+ company_id=self.env.company.id,
+ where4=aml_ids and "AND l.id IN %(aml_ids)s" or " ",
+ where5=all_entries and all_entries_query or only_dual_entries_query,
+ group_by1=is_partner and "l.partner_id, p.id," or " ",
+ group_by2=is_partner and ", p.last_time_entries_checked" or " ",
+ order_by=is_partner
+ and "ORDER BY p.last_time_entries_checked"
+ or "ORDER BY a.code",
+ outer_where=is_partner
+ and "WHERE (last_time_entries_checked IS NULL "
+ "OR max_date > last_time_entries_checked)"
+ or " ",
+ )
)
self.env["account.move.line"].flush()
self.env["account.account"].flush()
@@ -822,17 +821,6 @@ class AccountReconciliation(models.AbstractModel):
# line
domain = expression.AND([domain, [("company_id", "=", st_line.company_id.id)]])
- # take only moves in valid state. Draft is accepted only when "Post At"
- # is set to "Bank Reconciliation" in the associated journal
- domain_post_at = [
- "|",
- "&",
- ("move_id.state", "=", "draft"),
- ("journal_id.post_at", "=", "bank_rec"),
- ("move_id.state", "not in", ["draft", "cancel"]),
- ]
- domain = expression.AND([domain, domain_post_at])
-
if st_line.company_id.account_bank_reconciliation_start:
domain = expression.AND(
[
@@ -858,11 +846,7 @@ class AccountReconciliation(models.AbstractModel):
"&",
("reconciled", "=", False),
("account_id", "=", account_id),
- "|",
("move_id.state", "=", "posted"),
- "&",
- ("move_id.state", "=", "draft"),
- ("move_id.journal_id.post_at", "=", "bank_rec"),
]
domain = expression.AND([domain, [("balance", "!=", 0.0)]])
if partner_id:
@@ -1046,7 +1030,8 @@ class AccountReconciliation(models.AbstractModel):
data = {
"id": st_line.id,
"ref": st_line.ref,
- "note": st_line.note or "",
+ # FIXME: where to fill?
+ # 'note': st_line.note or "",
"name": st_line.name,
"date": format_date(self.env, st_line.date),
"amount": amount,
@@ -1056,11 +1041,11 @@ class AccountReconciliation(models.AbstractModel):
"journal_id": st_line.journal_id.id,
"statement_id": st_line.statement_id.id,
"account_id": [
- st_line.journal_id.default_debit_account_id.id,
- st_line.journal_id.default_debit_account_id.display_name,
+ st_line.journal_id.default_account_id.id,
+ st_line.journal_id.default_account_id.display_name,
],
- "account_code": st_line.journal_id.default_debit_account_id.code,
- "account_name": st_line.journal_id.default_debit_account_id.name,
+ "account_code": st_line.journal_id.default_account_id.code,
+ "account_name": st_line.journal_id.default_account_id.name,
"partner_name": st_line.partner_id.name,
"communication_partner_name": st_line.partner_name,
# Amount in the statement currency
@@ -1091,20 +1076,19 @@ class AccountReconciliation(models.AbstractModel):
where_str = where_clause and (" WHERE %s" % where_clause) or ""
# Get pairs
- query = """
+ query = sql.SQL(
+ """
SELECT a.id, b.id
FROM account_move_line a, account_move_line b,
account_move move_a, account_move move_b,
account_journal journal_a, account_journal journal_b
WHERE a.id != b.id
AND move_a.id = a.move_id
- AND (move_a.state = 'posted'
- OR (move_a.state = 'draft' AND journal_a.post_at = 'bank_rec'))
+ AND move_a.state = 'posted'
AND move_a.journal_id = journal_a.id
AND move_b.id = b.move_id
AND move_b.journal_id = journal_b.id
- AND (move_b.state = 'posted'
- OR (move_b.state = 'draft' AND journal_b.post_at = 'bank_rec'))
+ AND move_b.state = 'posted'
AND a.amount_residual = -b.amount_residual
AND a.balance != 0.0
AND b.balance != 0.0
@@ -1118,7 +1102,8 @@ class AccountReconciliation(models.AbstractModel):
ORDER BY a.date desc
LIMIT 1
""".format(
- from_clause + where_str
+ from_clause + where_str
+ )
)
move_line_id = self.env.context.get("move_line_id") or None
params = (
diff --git a/account_reconciliation_widget/readme/CONTRIBUTORS.rst b/account_reconciliation_widget/readme/CONTRIBUTORS.rst
new file mode 100644
index 00000000..207c602b
--- /dev/null
+++ b/account_reconciliation_widget/readme/CONTRIBUTORS.rst
@@ -0,0 +1 @@
+* Tecnativa - Pedro M. Baeza
diff --git a/account_reconciliation_widget/readme/DESCRIPTION.rst b/account_reconciliation_widget/readme/DESCRIPTION.rst
new file mode 100644
index 00000000..573558c8
--- /dev/null
+++ b/account_reconciliation_widget/readme/DESCRIPTION.rst
@@ -0,0 +1,2 @@
+This module restores account reconciliation widget moved from Odoo community to enterpise in V. 14.0
+Provides two widgets designed to reconcile move lines in a easy way: one focused on bank statements and another for generic use.
diff --git a/account_reconciliation_widget/readme/USAGE.rst b/account_reconciliation_widget/readme/USAGE.rst
new file mode 100644
index 00000000..4b4b9566
--- /dev/null
+++ b/account_reconciliation_widget/readme/USAGE.rst
@@ -0,0 +1,9 @@
+With an user with full accounting features enabled:
+
+Invoicing --> Accounting --> Actions --> Reconciliation.
+
+From journal items list view you can select check of them and click Action --> Reconcile.
+
+From accounting dashboard you can use reconcile button in Bank / Cash journals.
+
+Also, you can navigate to statements and use the reconcile button.
diff --git a/account_reconciliation_widget/security/ir.model.access.csv b/account_reconciliation_widget/security/ir.model.access.csv
new file mode 100644
index 00000000..d9184f74
--- /dev/null
+++ b/account_reconciliation_widget/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_account_reconciliation_widget_group_invoice,account_reconciliation_widget.group_invoice,model_account_reconciliation_widget,account.group_account_invoice,1,1,1,1
diff --git a/account_reconciliation_widget/static/description/index.html b/account_reconciliation_widget/static/description/index.html
new file mode 100644
index 00000000..560b763a
--- /dev/null
+++ b/account_reconciliation_widget/static/description/index.html
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+account_reconciliation_widget
+
+
+
+
+
account_reconciliation_widget
+
+
+
+
This module restores account reconciliation widget moved from Odoo community to enterpise in V. 14.0
+Provides two widgets designed to reconcile move lines in a easy way: one focused on bank statements and another for generic use.
Bugs are tracked on GitHub Issues.
+In case of trouble, please check there if your issue has already been reported.
+If you spotted it first, help us smashing it by providing a detailed and welcomed
+feedback.
+
Do not contact contributors directly about support or help with technical issues.
OCA, or the Odoo Community Association, is a nonprofit organization whose
+mission is to support the collaborative development of Odoo features and
+promote its widespread use.