From 8129aa40dbb236b693b21724ebf0e57bc59dbc8b Mon Sep 17 00:00:00 2001 From: wan Date: Mon, 17 Aug 2020 10:14:09 +0000 Subject: [PATCH] [IMP] account{,_check_printing}: usability Task 2005940 account: * Use stored compute methods instead of default for {out,in}bound_payment_method_ids * Track is_move_sent in the chatter * Split 'Invoices' and 'Bills' in the smart button of payment form * Because account.payment.method can be shown on the res.partner form, we need to relax the security level to readonly for all users account_check_printing: * Add the preferred payment method for partners, with a related on account move allowing to do a group by and doing payments in batch * Add a constraint to forbid twice the same check number in the same journal * The amount in words is now readonly to prevent typos and mismatches with the amount in digits * Remove the field `check_number_int`. The check number is kept as Char so that '000012345' is not displayed (and printed) as '12,345' but it is parsed so that comparison and incrementation are possible. closes odoo/odoo#56179 Related: odoo/upgrade#1669 Related: odoo/enterprise#12527 Signed-off-by: oco-odoo --- addons/account/models/account_journal.py | 60 ++++++++-- addons/account/models/account_move.py | 9 +- addons/account/models/account_payment.py | 63 ++++++++-- addons/account/security/ir.model.access.csv | 2 +- addons/account/views/account_payment_view.xml | 9 ++ addons/account_check_printing/__manifest__.py | 2 + .../account_check_printing/models/__init__.py | 3 +- .../models/account_journal.py | 78 ++++++++---- .../models/account_move.py | 23 ++++ .../models/account_payment.py | 112 ++++++++++++++---- .../models/chart_template.py | 20 ---- .../models/res_company.py | 52 ++++---- .../models/res_config_settings.py | 49 ++++++-- .../models/res_partner.py | 19 +++ .../tests/test_print_check.py | 4 +- .../views/account_move_views.xml | 13 ++ .../views/account_payment_views.xml | 8 +- .../views/res_partner_views.xml | 13 ++ .../wizard/print_prenumbered_checks.py | 3 +- 19 files changed, 409 insertions(+), 133 deletions(-) create mode 100644 addons/account_check_printing/models/account_move.py delete mode 100644 addons/account_check_printing/models/chart_template.py create mode 100644 addons/account_check_printing/models/res_partner.py create mode 100644 addons/account_check_printing/views/account_move_views.xml create mode 100644 addons/account_check_printing/views/res_partner_views.xml diff --git a/addons/account/models/account_journal.py b/addons/account/models/account_journal.py index 13c418cc099..0bab630145f 100644 --- a/addons/account/models/account_journal.py +++ b/addons/account/models/account_journal.py @@ -107,19 +107,41 @@ class AccountJournal(models.Model): sequence_override_regex = fields.Text(help="Technical field used to enforce complex sequence composition that the system would normally misunderstand.\n"\ "This is a regex that can include all the following capture groups: prefix1, year, prefix2, month, prefix3, seq, suffix.\n"\ "The prefix* groups are the separators between the year, month and the actual increasing sequence number (seq).\n"\ - + "e.g: ^(?P.*?)(?P\d{4})(?P\D*?)(?P\d{2})(?P\D+?)(?P\d+)(?P\D*?)$") - inbound_payment_method_ids = fields.Many2many('account.payment.method', 'account_journal_inbound_payment_method_rel', 'journal_id', 'inbound_payment_method', - domain=[('payment_type', '=', 'inbound')], string='Inbound Payment Methods', default=lambda self: self._default_inbound_payment_methods(), - help="Manual: Get paid by cash, check or any other method outside of Odoo.\n"\ - "Electronic: Get paid automatically through a payment acquirer by requesting a transaction on a card saved by the customer when buying or subscribing online (payment token).\n"\ - "Batch Deposit: Encase several customer checks at once by generating a batch deposit to submit to your bank. When encoding the bank statement in Odoo,you are suggested to reconcile the transaction with the batch deposit. Enable this option from the settings.") - outbound_payment_method_ids = fields.Many2many('account.payment.method', 'account_journal_outbound_payment_method_rel', 'journal_id', 'outbound_payment_method', - domain=[('payment_type', '=', 'outbound')], string='Outbound Payment Methods', default=lambda self: self._default_outbound_payment_methods(), - help="Manual:Pay bill by cash or any other method outside of Odoo.\n"\ - "Check:Pay bill by check and print it from Odoo.\n"\ - "SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your bank. Enable this option from the settings.") + inbound_payment_method_ids = fields.Many2many( + comodel_name='account.payment.method', + relation='account_journal_inbound_payment_method_rel', + column1='journal_id', + column2='inbound_payment_method', + domain=[('payment_type', '=', 'inbound')], + string='Inbound Payment Methods', + compute='_compute_inbound_payment_method_ids', + store=True, + readonly=False, + help="Manual: Get paid by cash, check or any other method outside of Odoo.\n" + "Electronic: Get paid automatically through a payment acquirer by requesting a transaction" + " on a card saved by the customer when buying or subscribing online (payment token).\n" + "Batch Deposit: Encase several customer checks at once by generating a batch deposit to" + " submit to your bank. When encoding the bank statement in Odoo,you are suggested to" + " reconcile the transaction with the batch deposit. Enable this option from the settings." + ) + outbound_payment_method_ids = fields.Many2many( + comodel_name='account.payment.method', + relation='account_journal_outbound_payment_method_rel', + column1='journal_id', + column2='outbound_payment_method', + domain=[('payment_type', '=', 'outbound')], + string='Outbound Payment Methods', + compute='_compute_outbound_payment_method_ids', + store=True, + readonly=False, + help="Manual:Pay bill by cash or any other method outside of Odoo.\n" + "Check:Pay bill by check and print it from Odoo.\n" + "SEPA Credit Transfer: Pay bill from a SEPA Credit Transfer file you submit to your" + " bank. Enable this option from the settings." + ) at_least_one_inbound = fields.Boolean(compute='_methods_compute', store=True) at_least_one_outbound = fields.Boolean(compute='_methods_compute', store=True) profit_account_id = fields.Many2one( @@ -190,6 +212,22 @@ class AccountJournal(models.Model): else: journal.default_account_type = False + @api.depends('type') + def _compute_outbound_payment_method_ids(self): + for journal in self: + if journal.type in ('bank', 'cash'): + journal.outbound_payment_method_ids = self._default_outbound_payment_methods() + else: + journal.outbound_payment_method_ids = False + + @api.depends('type') + def _compute_inbound_payment_method_ids(self): + for journal in self: + if journal.type in ('bank', 'cash'): + journal.inbound_payment_method_ids = self._default_inbound_payment_methods() + else: + journal.inbound_payment_method_ids = False + @api.depends('company_id', 'type') def _compute_suspense_account_id(self): for journal in self: diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index 221518dae56..7a7f2824efe 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -171,8 +171,13 @@ class AccountMove(models.Model): country_code = fields.Char(related='company_id.country_id.code', readonly=True) user_id = fields.Many2one(string='User', related='invoice_user_id', help='Technical field used to fit the generic behavior in mail templates.') - is_move_sent = fields.Boolean(readonly=True, default=False, copy=False, - help="It indicates that the invoice/payment has been sent.") + is_move_sent = fields.Boolean( + readonly=True, + default=False, + copy=False, + tracking=True, + help="It indicates that the invoice/payment has been sent.", + ) partner_bank_id = fields.Many2one('res.partner.bank', string='Recipient Bank', help='Bank Account Number to which the invoice will be paid. A Company bank account if this is a Customer Invoice or Vendor Credit Note, otherwise a Partner bank account number.', check_company=True) diff --git a/addons/account/models/account_payment.py b/addons/account/models/account_payment.py index 66c5e5df5c8..40c4c035e1a 100644 --- a/addons/account/models/account_payment.py +++ b/addons/account/models/account_payment.py @@ -109,6 +109,11 @@ class AccountPayment(models.Model): help="Invoices whose journal items have been reconciled with these payments.") reconciled_invoices_count = fields.Integer(string="# Reconciled Invoices", compute="_compute_stat_buttons_from_reconciliation") + reconciled_bill_ids = fields.Many2many('account.move', string="Reconciled Bills", + compute='_compute_stat_buttons_from_reconciliation', + help="Invoices whose journal items have been reconciled with these payments.") + reconciled_bills_count = fields.Integer(string="# Reconciled Bills", + compute="_compute_stat_buttons_from_reconciliation") reconciled_statement_ids = fields.Many2many('account.move', string="Reconciled Statements", compute='_compute_stat_buttons_from_reconciliation', help="Statements matched to this payment") @@ -422,6 +427,8 @@ class AccountPayment(models.Model): if not stored_payments: self.reconciled_invoice_ids = False self.reconciled_invoices_count = 0 + self.reconciled_bill_ids = False + self.reconciled_bills_count = 0 self.reconciled_statement_ids = False self.reconciled_statements_count = 0 return @@ -433,7 +440,8 @@ class AccountPayment(models.Model): self._cr.execute(''' SELECT payment.id, - ARRAY_AGG(DISTINCT invoice.id) AS invoice_ids + ARRAY_AGG(DISTINCT invoice.id) AS invoice_ids, + invoice.move_type FROM account_payment payment JOIN account_move move ON move.id = payment.move_id JOIN account_move_line line ON line.move_id = move.id @@ -448,16 +456,24 @@ class AccountPayment(models.Model): JOIN account_move invoice ON invoice.id = counterpart_line.move_id JOIN account_account account ON account.id = line.account_id WHERE account.internal_type IN ('receivable', 'payable') + AND payment.id IN %(payment_ids)s AND line.id != counterpart_line.id AND invoice.move_type in ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt') - GROUP BY payment.id - ''') - query_res = dict((payment_id, invoice_ids) for payment_id, invoice_ids in self._cr.fetchall()) - - for pay in self: - invoice_ids = query_res.get(pay.id, []) - pay.reconciled_invoice_ids = [(6, 0, invoice_ids)] - pay.reconciled_invoices_count = len(invoice_ids) + GROUP BY payment.id, invoice.move_type + ''', { + 'payment_ids': tuple(stored_payments.ids) + }) + query_res = self._cr.dictfetchall() + self.reconciled_invoice_ids = self.reconciled_invoices_count = False + self.reconciled_bill_ids = self.reconciled_bills_count = False + for res in query_res: + pay = self.browse(res['id']) + if res['move_type'] in self.env['account.move'].get_sale_types(True): + pay.reconciled_invoice_ids += self.env['account.move'].browse(res.get('invoice_ids', [])) + pay.reconciled_invoices_count = len(res.get('invoice_ids', [])) + else: + pay.reconciled_bill_ids += self.env['account.move'].browse(res.get('invoice_ids', [])) + pay.reconciled_bills_count = len(res.get('invoice_ids', [])) self._cr.execute(''' SELECT @@ -477,10 +493,13 @@ class AccountPayment(models.Model): OR part.credit_move_id = counterpart_line.id WHERE (account.id = journal.payment_debit_account_id OR account.id = journal.payment_credit_account_id) + AND payment.id IN %(payment_ids)s AND line.id != counterpart_line.id AND counterpart_line.statement_id IS NOT NULL GROUP BY payment.id - ''') + ''', { + 'payment_ids': tuple(stored_payments.ids) + }) query_res = dict((payment_id, statement_ids) for payment_id, statement_ids in self._cr.fetchall()) for pay in self: @@ -748,6 +767,30 @@ class AccountPayment(models.Model): }) return action + def button_open_bills(self): + ''' Redirect the user to the bill(s) paid by this payment. + :return: An action on account.move. + ''' + self.ensure_one() + + action = { + 'name': _("Paid Bills"), + 'type': 'ir.actions.act_window', + 'res_model': 'account.move', + 'context': {'create': False}, + } + if len(self.reconciled_bill_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': self.reconciled_bill_ids.id, + }) + else: + action.update({ + 'view_mode': 'list,form', + 'domain': [('id', 'in', self.reconciled_bill_ids.ids)], + }) + return action + def button_open_statements(self): ''' Redirect the user to the statement line(s) reconciled to this payment. :return: An action on account.move. diff --git a/addons/account/security/ir.model.access.csv b/addons/account/security/ir.model.access.csv index 2013d5dbd4b..cddbb5372f2 100644 --- a/addons/account/security/ir.model.access.csv +++ b/addons/account/security/ir.model.access.csv @@ -118,7 +118,7 @@ access_account_payment_term_partner_manager,account.payment.term partner manager access_account_payment_term_manager,account.payment.term,model_account_payment_term,account.group_account_manager,1,1,1,1 access_account_payment_term_line_partner_manager,account.payment.term.line partner manager,model_account_payment_term_line,base.group_user,1,0,0,0 access_account_payment_term_line_manager,account.payment.term.line,model_account_payment_term_line,account.group_account_manager,1,1,1,1 -access_account_payment_method_readonly,account.payment.method,model_account_payment_method,account.group_account_readonly,1,0,0,0 +access_account_payment_method_readonly,account.payment.method,model_account_payment_method,base.group_user,1,0,0,0 access_account_payment_method,account.payment.method,model_account_payment_method,account.group_account_invoice,1,1,1,1 access_account_payment_readonly,account.payment,model_account_payment,account.group_account_readonly,1,0,0,0 access_account_payment,account.payment,model_account_payment,account.group_account_invoice,1,1,1,1 diff --git a/addons/account/views/account_payment_view.xml b/addons/account/views/account_payment_view.xml index 18894102b42..2e4a05b74a8 100644 --- a/addons/account/views/account_payment_view.xml +++ b/addons/account/views/account_payment_view.xml @@ -171,6 +171,15 @@ Invoices + + +