[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:
wan
2020-08-21 07:46:07 +00:00
parent eb6a177a50
commit 8129aa40db
19 changed files with 409 additions and 133 deletions
+49 -11
View File
@@ -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:
+7 -2
View File
@@ -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)
+53 -10
View File
@@ -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.
+1 -1
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
118 access_account_move_reversal access.account.move.reversal model_account_move_reversal account.group_account_invoice 1 1 1 0
119 access_account_common_report access.account.common.report model_account_common_report account.group_account_user 1 1 1 0
120 access_account_common_journal_report access.account.common.journal.report model_account_common_journal_report account.group_account_user 1 1 1 0
121 access_account_print_journal access.account.print.journal model_account_print_journal account.group_account_user 1 1 1 0
122 access_account_financial_year_op access.account.financial.year.op model_account_financial_year_op account.group_account_manager 1 1 1 0
123 access_account_setup_bank_manual_config access.account.setup.bank.manual.config model_account_setup_bank_manual_config account.group_account_manager 1 1 1 0
124 access_tax_adjustments_wizard access.tax.adjustments.wizard model_tax_adjustments_wizard account.group_account_user 1 1 1 0
@@ -171,6 +171,15 @@
<span attrs="{'invisible': [('reconciled_invoices_count','&lt;=', 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','&gt;', 1)]}">Bill</span>
<span attrs="{'invisible': [('reconciled_bills_count','&lt;=', 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()