[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>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
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('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
|
||||
|
||||
@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,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
account_check_printing_layout = fields.Selection(
|
||||
string="Check Layout",
|
||||
selection=[
|
||||
('disabled', 'None'),
|
||||
],
|
||||
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'.",
|
||||
selection=[
|
||||
('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.")
|
||||
)
|
||||
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.",
|
||||
)
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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.",
|
||||
)
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user