diff options
| author | wan <wan@odoo.com> | 2020-08-17 10:14:09 +0000 |
|---|---|---|
| committer | wan <wan@odoo.com> | 2020-08-21 07:46:07 +0000 |
| commit | 8129aa40dbb236b693b21724ebf0e57bc59dbc8b (patch) | |
| tree | f7d5059bcac1bdc2af0ffd9ad570251f5c9f9eef /addons | |
| parent | eb6a177a50128f2d78607fcff36aaf65e8a3034e (diff) | |
[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 <oco-odoo@users.noreply.github.com>
Diffstat (limited to 'addons')
19 files changed, 409 insertions, 133 deletions
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<prefix1>.*?)(?P<year>\d{4})(?P<prefix2>\D*?)(?P<month>\d{2})(?P<prefix3>\D+?)(?P<seq>\d+)(?P<suffix>\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 @@ <span attrs="{'invisible': [('reconciled_invoices_count','<=', 1)]}">Invoices</span> </button> + <!-- Bill stat button --> + <button name="button_open_bills" type="object" + class="oe_stat_button" icon="fa-bars" + attrs="{'invisible': [('reconciled_bills_count','=', 0)]}"> + <field name="reconciled_bills_count"/> + <span attrs="{'invisible': [('reconciled_bills_count','>', 1)]}">Bill</span> + <span attrs="{'invisible': [('reconciled_bills_count','<=', 1)]}">Bills</span> + </button> + <!-- Statement stat button --> <button name="button_open_statements" type="object" class="oe_stat_button" icon="fa-bars" diff --git a/addons/account_check_printing/__manifest__.py b/addons/account_check_printing/__manifest__.py index 4882c5ee815..0e217e4361f 100644 --- a/addons/account_check_printing/__manifest__.py +++ b/addons/account_check_printing/__manifest__.py @@ -16,8 +16,10 @@ The check settings are located in the accounting journals configuration page. 'security/ir.model.access.csv', 'data/account_check_printing_data.xml', 'views/account_journal_views.xml', + 'views/account_move_views.xml', 'views/account_payment_views.xml', 'views/res_config_settings_views.xml', + 'views/res_partner_views.xml', 'wizard/print_prenumbered_checks_views.xml' ], 'installable': True, diff --git a/addons/account_check_printing/models/__init__.py b/addons/account_check_printing/models/__init__.py index 806954020e2..7341412a7f7 100644 --- a/addons/account_check_printing/models/__init__.py +++ b/addons/account_check_printing/models/__init__.py @@ -2,7 +2,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import account_journal +from . import account_move from . import account_payment -from . import chart_template from . import res_company from . import res_config_settings +from . import res_partner diff --git a/addons/account_check_printing/models/account_journal.py b/addons/account_check_printing/models/account_journal.py index 433e00c55e8..70eb02c34f2 100644 --- a/addons/account_check_printing/models/account_journal.py +++ b/addons/account_check_printing/models/account_journal.py @@ -8,37 +8,67 @@ from odoo.exceptions import ValidationError class AccountJournal(models.Model): _inherit = "account.journal" - @api.depends('outbound_payment_method_ids') - def _compute_check_printing_payment_method_selected(self): - for journal in self: - journal.check_printing_payment_method_selected = any(pm.code == 'check_printing' for pm in journal.outbound_payment_method_ids) + check_manual_sequencing = fields.Boolean( + string='Manual Numbering', + default=False, + help="Check this option if your pre-printed checks are not numbered.", + ) + check_sequence_id = fields.Many2one( + comodel_name='ir.sequence', + string='Check Sequence', + readonly=True, + copy=False, + help="Checks numbering sequence.", + ) + check_next_number = fields.Char( + string='Next Check Number', + compute='_compute_check_next_number', + inverse='_inverse_check_next_number', + help="Sequence number of the next printed check.", + ) + check_printing_payment_method_selected = fields.Boolean( + compute='_compute_check_printing_payment_method_selected', + help="Technical feature used to know whether check printing was enabled as payment method.", + ) @api.depends('check_manual_sequencing') - def _get_check_next_number(self): + def _compute_check_next_number(self): for journal in self: - if journal.check_sequence_id: - journal.check_next_number = journal.check_sequence_id.number_next_actual + sequence = journal.check_sequence_id + if sequence: + journal.check_next_number = sequence.get_next_char(sequence.number_next_actual) else: journal.check_next_number = 1 - def _set_check_next_number(self): + def _inverse_check_next_number(self): for journal in self: if journal.check_next_number and not re.match(r'^[0-9]+$', journal.check_next_number): raise ValidationError(_('Next Check Number should only contains numbers.')) if int(journal.check_next_number) < journal.check_sequence_id.number_next_actual: - raise ValidationError(_("The last check number was %s. In order to avoid a check being rejected " - "by the bank, you can only use a greater number.") % journal.check_sequence_id.number_next_actual) + raise ValidationError(_( + "The last check number was %s. In order to avoid a check being rejected " + "by the bank, you can only use a greater number.", + journal.check_sequence_id.number_next_actual + )) if journal.check_sequence_id: journal.check_sequence_id.sudo().number_next_actual = int(journal.check_next_number) + journal.check_sequence_id.sudo().padding = len(journal.check_next_number) + + @api.depends('type') + def _compute_outbound_payment_method_ids(self): + super()._compute_outbound_payment_method_ids() + for journal in self: + if journal.type == 'cash': + check_method = self.env.ref('account_check_printing.account_payment_method_check') + journal.outbound_payment_method_ids -= check_method - check_manual_sequencing = fields.Boolean('Manual Numbering', default=False, - help="Check this option if your pre-printed checks are not numbered.") - check_sequence_id = fields.Many2one('ir.sequence', 'Check Sequence', readonly=True, copy=False, - help="Checks numbering sequence.") - check_next_number = fields.Char('Next Check Number', compute='_get_check_next_number', inverse='_set_check_next_number', - help="Sequence number of the next printed check.") - check_printing_payment_method_selected = fields.Boolean(compute='_compute_check_printing_payment_method_selected', - help="Technical feature used to know whether check printing was enabled as payment method.") + @api.depends('outbound_payment_method_ids') + def _compute_check_printing_payment_method_selected(self): + for journal in self: + journal.check_printing_payment_method_selected = any( + pm.code == 'check_printing' + for pm in journal.outbound_payment_method_ids + ) @api.model def create(self, vals): @@ -73,13 +103,10 @@ class AccountJournal(models.Model): """ Enables check printing payment method and add a check sequence on bank journals. Called upon module installation via data file. """ - check_printing = self.env.ref('account_check_printing.account_payment_method_check') - bank_journals = self.search([('type', '=', 'bank')]) - for bank_journal in bank_journals: + check_method = self.env.ref('account_check_printing.account_payment_method_check') + for bank_journal in self.search([('type', '=', 'bank')]): bank_journal._create_check_sequence() - bank_journal.write({ - 'outbound_payment_method_ids': [(4, check_printing.id, None)], - }) + bank_journal.outbound_payment_method_ids += check_method def get_journal_dashboard_datas(self): domain_checks_to_print = [ @@ -93,6 +120,7 @@ class AccountJournal(models.Model): ) def action_checks_to_print(self): + check_method = self.env.ref('account_check_printing.account_payment_method_check') return { 'name': _('Checks to Print'), 'type': 'ir.actions.act_window', @@ -104,6 +132,6 @@ class AccountJournal(models.Model): journal_id=self.id, default_journal_id=self.id, default_payment_type='outbound', - default_payment_method_id=self.env.ref('account_check_printing.account_payment_method_check').id, + default_payment_method_id=check_method.id, ), } diff --git a/addons/account_check_printing/models/account_move.py b/addons/account_check_printing/models/account_move.py new file mode 100644 index 00000000000..c5c8dcb6d5d --- /dev/null +++ b/addons/account_check_printing/models/account_move.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from odoo import models, fields, api + + +class AccountMove(models.Model): + _inherit = 'account.move' + + preferred_payment_method_id = fields.Many2one( + string="Preferred Payment Method", + comodel_name='account.payment.method', + compute='_compute_preferred_payment_method_idd', + store=True, + ) + + @api.depends('partner_id') + def _compute_preferred_payment_method_idd(self): + for move in self: + partner = move.partner_id + # take the payment method corresponding to the move's company + move.preferred_payment_method_id = partner.with_company(move.company_id).property_payment_method_id diff --git a/addons/account_check_printing/models/account_payment.py b/addons/account_check_printing/models/account_payment.py index d27a2cebe8c..7325186f598 100644 --- a/addons/account_check_printing/models/account_payment.py +++ b/addons/account_check_printing/models/account_payment.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, fields, api, _ -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import UserError, ValidationError, RedirectWarning from odoo.tools.misc import formatLang, format_date INV_LINES_PER_STUB = 9 @@ -11,15 +11,55 @@ INV_LINES_PER_STUB = 9 class AccountPayment(models.Model): _inherit = "account.payment" - check_amount_in_words = fields.Char(string="Amount in Words", store=True, readonly=False, - compute='_compute_check_amount_in_words') + check_amount_in_words = fields.Char( + string="Amount in Words", + store=True, + compute='_compute_check_amount_in_words', + ) check_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing') - check_number = fields.Char(string="Check Number", store=True, readonly=True, + check_number = fields.Char( + string="Check Number", + store=True, + readonly=True, compute='_compute_check_number', + inverse='_inverse_check_number', help="The selected journal is configured to print check numbers. If your pre-printed check paper already has numbers " - "or if the current numbering is wrong, you can change it in the journal configuration page.") - check_number_int = fields.Integer(string="Check Number Integer", store=True, - compute='_compute_check_number_int') + "or if the current numbering is wrong, you can change it in the journal configuration page.", + ) + + @api.constrains('check_number', 'journal_id') + def _constrains_check_number(self): + if not self: + return + try: + self.mapped(lambda p: str(int(p.check_number))) + except ValueError: + raise ValidationError(_('Check numbers can only consist of digits')) + self.flush() + self.env.cr.execute(""" + SELECT payment.check_number, move.journal_id + FROM account_payment payment + JOIN account_move move ON move.id = payment.move_id + JOIN account_journal journal ON journal.id = move.journal_id, + account_payment other_payment + JOIN account_move other_move ON other_move.id = other_payment.move_id + WHERE payment.check_number::INTEGER = other_payment.check_number::INTEGER + AND move.journal_id = other_move.journal_id + AND payment.id != other_payment.id + AND payment.id IN %(ids)s + """, { + 'ids': tuple(self.ids), + }) + res = self.env.cr.dictfetchall() + if res: + raise ValidationError(_( + 'The following numbers are already used:\n%s', + '\n'.join(_( + '%(number)s in journal %(journal)s', + number=r['check_number'], + journal=self.env['account.journal'].browse(r['journal_id']).display_name, + ) for r in res) + )) @api.depends('payment_method_id', 'currency_id', 'amount') def _compute_check_amount_in_words(self): @@ -33,21 +73,24 @@ class AccountPayment(models.Model): def _compute_check_number(self): for pay in self: if pay.journal_id.check_manual_sequencing: - pay.check_number = pay.journal_id.check_sequence_id.number_next_actual + sequence = pay.journal_id.check_sequence_id + pay.check_number = sequence.get_next_char(sequence.number_next_actual) else: pay.check_number = False - @api.depends('check_number') - def _compute_check_number_int(self): - # store check number as int to avoid doing a lot of checks and transformations - # when calculating next_check_number + def _inverse_check_number(self): + for payment in self: + if payment.check_number: + sequence = payment.journal_id.check_sequence_id + sequence.padding = len(payment.check_number) + + @api.depends('payment_type', 'journal_id', 'partner_id') + def _compute_payment_method_id(self): + super()._compute_payment_method_id() for record in self: - number = record.check_number - try: - number = int(number) - except Exception as e: - number = 0 - record.check_number_int = number + preferred = record.partner_id.with_company(record.company_id).property_payment_method_id + if record.payment_type == 'outbound' and preferred in record.journal_id.outbound_payment_method_ids: + record.payment_method_id = preferred def action_post(self): res = super(AccountPayment, self).action_post() @@ -71,10 +114,19 @@ class AccountPayment(models.Model): if not self[0].journal_id.check_manual_sequencing: # The wizard asks for the number printed on the first pre-printed check # so payments are attributed the number of the check the'll be printed on. - last_printed_check = self.search([ - ('journal_id', '=', self[0].journal_id.id), - ('check_number_int', '!=', 0)], order="check_number_int desc", limit=1) - next_check_number = last_printed_check and last_printed_check.check_number_int + 1 or 1 + self.env.cr.execute(""" + SELECT payment.id + FROM account_payment payment + JOIN account_move move ON movE.id = payment.move_id + WHERE journal_id = %(journal_id)s + ORDER BY check_number::INTEGER DESC + LIMIT 1 + """, { + 'journal_id': self.journal_id.id, + }) + last_printed_check = self.browse(self.env.cr.fetchone()) + number_len = len(last_printed_check.check_number or "") + next_check_number = '%0{}d'.format(number_len) % (int(last_printed_check.check_number) + 1) return { 'name': _('Print Pre-numbered Checks'), @@ -94,13 +146,20 @@ class AccountPayment(models.Model): def action_unmark_sent(self): self.write({'is_move_sent': False}) + def action_void_check(self): + self.action_draft() + self.action_cancel() + def do_print_checks(self): - check_layout = self[0].company_id.account_check_printing_layout + check_layout = self.company_id.account_check_printing_layout + redirect_action = self.env.ref('account.action_account_config') if not check_layout or check_layout == 'disabled': - raise UserError(_("You have to choose a check layout. For this, go in Invoicing/Accounting Settings, search for 'Checks layout' and set one.")) + msg = _("You have to choose a check layout. For this, go in Invoicing/Accounting Settings, search for 'Checks layout' and set one.") + raise RedirectWarning(msg, redirect_action.id, _('Go to the configuration panel')) report_action = self.env.ref(check_layout, False) if not report_action: - raise UserError(_("Something went wrong with Check Layout, please select another layout in Invoicing/Accounting Settings and try again.")) + msg = _("Something went wrong with Check Layout, please select another layout in Invoicing/Accounting Settings and try again.") + raise RedirectWarning(msg, redirect_action.id, _('Go to the configuration panel')) self.write({'is_move_sent': True}) return report_action.report_action(self) @@ -113,7 +172,8 @@ class AccountPayment(models.Model): def _check_build_page_info(self, i, p): multi_stub = self.company_id.account_check_printing_multi_stub return { - 'sequence_number': self.check_number if (self.journal_id.check_manual_sequencing and self.check_number != 0) else False, + 'sequence_number': self.check_number, + 'manual_sequencing': self.journal_id.check_manual_sequencing, 'date': format_date(self.env, self.date), 'partner_id': self.partner_id, 'partner_name': self.partner_id.name, diff --git a/addons/account_check_printing/models/chart_template.py b/addons/account_check_printing/models/chart_template.py deleted file mode 100644 index 35c101873fb..00000000000 --- a/addons/account_check_printing/models/chart_template.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- - -from odoo import api, models - - -class AccountChartTemplate(models.Model): - _inherit = 'account.chart.template' - - def _create_bank_journals(self, company, acc_template_ref): - ''' - When system automatically creates journals of bank and cash type when CoA is being installed - do not enable the `Check` payment method on bank journals of type `Cash`. - - ''' - bank_journals = super(AccountChartTemplate, self)._create_bank_journals(company, acc_template_ref) - payment_method_check = self.env.ref('account_check_printing.account_payment_method_check') - bank_journals.filtered(lambda journal: journal.type == 'cash').write({ - 'outbound_payment_method_ids': [(3, payment_method_check.id)] - }) - return bank_journals diff --git a/addons/account_check_printing/models/res_company.py b/addons/account_check_printing/models/res_company.py index 45d22854d88..a96ad9a5599 100644 --- a/addons/account_check_printing/models/res_company.py +++ b/addons/account_check_printing/models/res_company.py @@ -8,25 +8,37 @@ class res_company(models.Model): # This field needs to be overridden with `selection_add` in the modules which intends to add report layouts. # The xmlID of all the report actions which are actually Check Layouts has to be kept as key of the selection. - account_check_printing_layout = fields.Selection(string="Check Layout", - help="Select the format corresponding to the check paper you will be printing your checks on.\n" - "In order to disable the printing feature, select 'None'.", + account_check_printing_layout = fields.Selection( + string="Check Layout", selection=[ - ('disabled', 'None') + ('disabled', 'None'), ], - default='disabled') - - account_check_printing_date_label = fields.Boolean('Print Date Label', default=True, - help="This option allows you to print the date label on the check as per CPA. Disable this if your pre-printed check includes the date label.") - - account_check_printing_multi_stub = fields.Boolean('Multi-Pages Check Stub', - help="This option allows you to print check details (stub) on multiple pages if they don't fit on a single page.") - - account_check_printing_margin_top = fields.Float('Check Top Margin', default=0.25, - help="Adjust the margins of generated checks to make it fit your printer's settings.") - - account_check_printing_margin_left = fields.Float('Check Left Margin', default=0.25, - help="Adjust the margins of generated checks to make it fit your printer's settings.") - - account_check_printing_margin_right = fields.Float('Right Margin', default=0.25, - help="Adjust the margins of generated checks to make it fit your printer's settings.") + default='disabled', + help="Select the format corresponding to the check paper you will be printing your checks on.\n" + "In order to disable the printing feature, select 'None'.", + ) + account_check_printing_date_label = fields.Boolean( + string='Print Date Label', + default=True, + help="This option allows you to print the date label on the check as per CPA.\n" + "Disable this if your pre-printed check includes the date label.", + ) + account_check_printing_multi_stub = fields.Boolean( + string='Multi-Pages Check Stub', + help="This option allows you to print check details (stub) on multiple pages if they don't fit on a single page.", + ) + account_check_printing_margin_top = fields.Float( + string='Check Top Margin', + default=0.25, + help="Adjust the margins of generated checks to make it fit your printer's settings.", + ) + account_check_printing_margin_left = fields.Float( + string='Check Left Margin', + default=0.25, + help="Adjust the margins of generated checks to make it fit your printer's settings.", + ) + account_check_printing_margin_right = fields.Float( + string='Right Margin', + default=0.25, + help="Adjust the margins of generated checks to make it fit your printer's settings.", + ) diff --git a/addons/account_check_printing/models/res_config_settings.py b/addons/account_check_printing/models/res_config_settings.py index 2e7b9e2ea7f..5716e12801f 100644 --- a/addons/account_check_printing/models/res_config_settings.py +++ b/addons/account_check_printing/models/res_config_settings.py @@ -7,16 +7,41 @@ from odoo import fields, models class ResConfigSettings(models.TransientModel): _inherit = 'res.config.settings' - account_check_printing_layout = fields.Selection(related='company_id.account_check_printing_layout', string="Check Layout", readonly=False, + account_check_printing_layout = fields.Selection( + related='company_id.account_check_printing_layout', + string="Check Layout", + readonly=False, help="Select the format corresponding to the check paper you will be printing your checks on.\n" - "In order to disable the printing feature, select 'None'.") - account_check_printing_date_label = fields.Boolean(related='company_id.account_check_printing_date_label', string="Print Date Label", readonly=False, - help="This option allows you to print the date label on the check as per CPA. Disable this if your pre-printed check includes the date label.") - account_check_printing_multi_stub = fields.Boolean(related='company_id.account_check_printing_multi_stub', string='Multi-Pages Check Stub', readonly=False, - help="This option allows you to print check details (stub) on multiple pages if they don't fit on a single page.") - account_check_printing_margin_top = fields.Float(related='company_id.account_check_printing_margin_top', string='Check Top Margin', readonly=False, - help="Adjust the margins of generated checks to make it fit your printer's settings.") - account_check_printing_margin_left = fields.Float(related='company_id.account_check_printing_margin_left', string='Check Left Margin', readonly=False, - help="Adjust the margins of generated checks to make it fit your printer's settings.") - account_check_printing_margin_right = fields.Float(related='company_id.account_check_printing_margin_right', string='Check Right Margin', readonly=False, - help="Adjust the margins of generated checks to make it fit your printer's settings.") + "In order to disable the printing feature, select 'None'." + ) + account_check_printing_date_label = fields.Boolean( + related='company_id.account_check_printing_date_label', + string="Print Date Label", + readonly=False, + help="This option allows you to print the date label on the check as per CPA.\n" + "Disable this if your pre-printed check includes the date label." + ) + account_check_printing_multi_stub = fields.Boolean( + related='company_id.account_check_printing_multi_stub', + string='Multi-Pages Check Stub', + readonly=False, + help="This option allows you to print check details (stub) on multiple pages if they don't fit on a single page." + ) + account_check_printing_margin_top = fields.Float( + related='company_id.account_check_printing_margin_top', + string='Check Top Margin', + readonly=False, + help="Adjust the margins of generated checks to make it fit your printer's settings." + ) + account_check_printing_margin_left = fields.Float( + related='company_id.account_check_printing_margin_left', + string='Check Left Margin', + readonly=False, + help="Adjust the margins of generated checks to make it fit your printer's settings." + ) + account_check_printing_margin_right = fields.Float( + related='company_id.account_check_printing_margin_right', + string='Check Right Margin', + readonly=False, + help="Adjust the margins of generated checks to make it fit your printer's settings." + ) diff --git a/addons/account_check_printing/models/res_partner.py b/addons/account_check_printing/models/res_partner.py new file mode 100644 index 00000000000..93aca4befdf --- /dev/null +++ b/addons/account_check_printing/models/res_partner.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +from odoo import models, fields + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + property_payment_method_id = fields.Many2one( + comodel_name='account.payment.method', + string='Payment Method', + company_dependent=True, + domain="[('payment_type', '=', 'outbound')]", + help="Preferred payment method when paying this vendor. This is used to filter vendor bills" + " by preferred payment method to register payments in mass. Use cases: create bank" + " files for batch wires, check runs.", + ) diff --git a/addons/account_check_printing/tests/test_print_check.py b/addons/account_check_printing/tests/test_print_check.py index 82222daea88..40996542a50 100644 --- a/addons/account_check_printing/tests/test_print_check.py +++ b/addons/account_check_printing/tests/test_print_check.py @@ -28,7 +28,7 @@ class TestPrintCheck(AccountTestInvoicingCommon): self.company_data['default_journal_bank'].write({ 'check_manual_sequencing': True, - 'check_next_number': 42, + 'check_next_number': '00042', }) # Create 10 customer invoices. @@ -69,7 +69,7 @@ class TestPrintCheck(AccountTestInvoicingCommon): self.company_data['default_journal_bank'].write({ 'check_manual_sequencing': True, - 'check_next_number': 42, + 'check_next_number': '00042', }) # Create 10 refunds. diff --git a/addons/account_check_printing/views/account_move_views.xml b/addons/account_check_printing/views/account_move_views.xml new file mode 100644 index 00000000000..905a9da9288 --- /dev/null +++ b/addons/account_check_printing/views/account_move_views.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="view_account_invoice_filter" model="ir.ui.view"> + <field name="name">account.invoice.select.account_check_printing</field> + <field name="model">account.move</field> + <field name="inherit_id" ref="account.view_account_invoice_filter"/> + <field name="arch" type="xml"> + <xpath expr="//search/group/filter[@name='status']" position="after"> + <filter name="preferred_payment_method" context="{'group_by': 'preferred_payment_method_id'}" groups="account.group_account_invoice,account.group_account_readonly"/> + </xpath> + </field> + </record> +</odoo> diff --git a/addons/account_check_printing/views/account_payment_views.xml b/addons/account_check_printing/views/account_payment_views.xml index 32093a46845..0ee3c729ea6 100644 --- a/addons/account_check_printing/views/account_payment_views.xml +++ b/addons/account_check_printing/views/account_payment_views.xml @@ -6,16 +6,20 @@ <field name="inherit_id" ref="account.view_account_payment_form" /> <field name="arch" type="xml"> <xpath expr="//button[@name='action_post']" position="before"> - <button name="print_checks" class="oe_highlight" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'posted')]}" string="Print Check" type="object"/> + <button name="print_checks" class="oe_highlight" attrs="{'invisible': ['|', '|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'posted'), ('is_move_sent', '=', True)]}" string="Print Check" type="object"/> <button name="action_unmark_sent" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('is_move_sent', '=', False)]}" string="Unmark Sent" type="object"/> + <button name="action_void_check" attrs="{'invisible': ['|', '|', ('payment_method_code', '!=', 'check_printing'), ('state', '!=', 'posted'), ('is_move_sent', '=', False)]}" string="Void Check" type="object"/> </xpath> <xpath expr="//div[@name='amount_div']" position="after"> - <field name="check_amount_in_words" attrs="{'invisible': [('payment_method_code', '!=', 'check_printing')], 'readonly': [('state', '!=', 'draft')]}" groups="base.group_no_one"/> + <field name="check_amount_in_words" attrs="{'invisible': [('payment_method_code', '!=', 'check_printing')]}" groups="base.group_no_one"/> </xpath> <xpath expr="//field[@name='ref']" position="after"> <field name="check_manual_sequencing" invisible="1"/> <field name="check_number" attrs="{'invisible': ['|', ('payment_method_code', '!=', 'check_printing'), ('check_number', '=', False)]}"/> </xpath> + <xpath expr="//field[@name='name']" position='before'> + <widget name="web_ribbon" title="Sent" attrs="{'invisible': [('is_move_sent', '!=', True)]}"/> + </xpath> </field> </record> diff --git a/addons/account_check_printing/views/res_partner_views.xml b/addons/account_check_printing/views/res_partner_views.xml new file mode 100644 index 00000000000..cdd0fa569f4 --- /dev/null +++ b/addons/account_check_printing/views/res_partner_views.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="view_partner_property_form" model="ir.ui.view"> + <field name="name">res.partner.property.form.inherit</field> + <field name="model">res.partner</field> + <field name="inherit_id" ref="account.view_partner_property_form"/> + <field name="arch" type="xml"> + <field name="property_supplier_payment_term_id" position="after"> + <field name="property_payment_method_id" widget="selection" groups="account.group_account_invoice,account.group_account_readonly"/> + </field> + </field> + </record> +</odoo> diff --git a/addons/account_check_printing/wizard/print_prenumbered_checks.py b/addons/account_check_printing/wizard/print_prenumbered_checks.py index 416ab4a1a11..84d4f10a9d7 100644 --- a/addons/account_check_printing/wizard/print_prenumbered_checks.py +++ b/addons/account_check_printing/wizard/print_prenumbered_checks.py @@ -20,10 +20,11 @@ class PrintPreNumberedChecks(models.TransientModel): def print_checks(self): check_number = int(self.next_check_number) + number_len = len(self.next_check_number or "") payments = self.env['account.payment'].browse(self.env.context['payment_ids']) payments.filtered(lambda r: r.state == 'draft').action_post() payments.filtered(lambda r: r.state == 'posted' and not r.is_move_sent).write({'is_move_sent': True}) for payment in payments: - payment.check_number = check_number + payment.check_number = '%0{}d'.format(number_len) % check_number check_number += 1 return payments.do_print_checks() |
