Add an easy way to not post the entries in the future when calling post() on it, but rather set it to be auto-posted at accounting date. This is useful when we are creating a lot of entries in batch and some might be in the future, some in the past, and we don't want to separate that in two batch every time. (asset, accrual, transfer,... )
206 lines
10 KiB
Python
206 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
# 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.tools.misc import formatLang, format_date
|
|
|
|
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_manual_sequencing = fields.Boolean(related='journal_id.check_manual_sequencing')
|
|
check_number = fields.Char(string="Check Number", store=True, readonly=True,
|
|
compute='_compute_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')
|
|
|
|
@api.depends('payment_method_id', 'currency_id', 'amount')
|
|
def _compute_check_amount_in_words(self):
|
|
for pay in self:
|
|
if pay.currency_id and pay.payment_method_id.code == 'check_printing':
|
|
pay.check_amount_in_words = pay.currency_id.amount_to_text(pay.amount)
|
|
else:
|
|
pay.check_amount_in_words = False
|
|
|
|
@api.depends('journal_id')
|
|
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
|
|
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
|
|
for record in self:
|
|
number = record.check_number
|
|
try:
|
|
number = int(number)
|
|
except Exception as e:
|
|
number = 0
|
|
record.check_number_int = number
|
|
|
|
def action_post(self):
|
|
res = super(AccountPayment, self).action_post()
|
|
payment_method_check = self.env.ref('account_check_printing.account_payment_method_check')
|
|
for payment in self.filtered(lambda p: p.payment_method_id == payment_method_check and p.check_manual_sequencing):
|
|
sequence = payment.journal_id.check_sequence_id
|
|
payment.check_number = sequence.next_by_id()
|
|
return res
|
|
|
|
def print_checks(self):
|
|
""" Check that the recordset is valid, set the payments state to sent and call print_checks() """
|
|
# Since this method can be called via a client_action_multi, we need to make sure the received records are what we expect
|
|
self = self.filtered(lambda r: r.payment_method_id.code == 'check_printing' and r.state != 'reconciled')
|
|
|
|
if len(self) == 0:
|
|
raise UserError(_("Payments to print as a checks must have 'Check' selected as payment method and "
|
|
"not have already been reconciled"))
|
|
if any(payment.journal_id != self[0].journal_id for payment in self):
|
|
raise UserError(_("In order to print multiple checks at once, they must belong to the same bank journal."))
|
|
|
|
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
|
|
|
|
return {
|
|
'name': _('Print Pre-numbered Checks'),
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'print.prenumbered.checks',
|
|
'view_mode': 'form',
|
|
'target': 'new',
|
|
'context': {
|
|
'payment_ids': self.ids,
|
|
'default_next_check_number': next_check_number,
|
|
}
|
|
}
|
|
else:
|
|
self.filtered(lambda r: r.state == 'draft').action_post()
|
|
return self.do_print_checks()
|
|
|
|
def action_unmark_sent(self):
|
|
self.write({'is_move_sent': False})
|
|
|
|
def do_print_checks(self):
|
|
check_layout = self[0].company_id.account_check_printing_layout
|
|
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."))
|
|
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."))
|
|
self.write({'is_move_sent': True})
|
|
return report_action.report_action(self)
|
|
|
|
#######################
|
|
#CHECK PRINTING METHODS
|
|
#######################
|
|
def _check_fill_line(self, amount_str):
|
|
return amount_str and (amount_str + ' ').ljust(200, '*') or ''
|
|
|
|
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,
|
|
'date': format_date(self.env, self.date),
|
|
'partner_id': self.partner_id,
|
|
'partner_name': self.partner_id.name,
|
|
'currency': self.currency_id,
|
|
'state': self.state,
|
|
'amount': formatLang(self.env, self.amount, currency_obj=self.currency_id) if i == 0 else 'VOID',
|
|
'amount_in_word': self._check_fill_line(self.check_amount_in_words) if i == 0 else 'VOID',
|
|
'memo': self.ref,
|
|
'stub_cropped': not multi_stub and len(self.move_id._get_reconciled_invoices()) > INV_LINES_PER_STUB,
|
|
# If the payment does not reference an invoice, there is no stub line to display
|
|
'stub_lines': p,
|
|
}
|
|
|
|
def _check_get_pages(self):
|
|
""" Returns the data structure used by the template : a list of dicts containing what to print on pages.
|
|
"""
|
|
stub_pages = self._check_make_stub_pages() or [False]
|
|
pages = []
|
|
for i, p in enumerate(stub_pages):
|
|
pages.append(self._check_build_page_info(i, p))
|
|
return pages
|
|
|
|
def _check_make_stub_pages(self):
|
|
""" The stub is the summary of paid invoices. It may spill on several pages, in which case only the check on
|
|
first page is valid. This function returns a list of stub lines per page.
|
|
"""
|
|
if len(self.move_id._get_reconciled_invoices()) == 0:
|
|
return None
|
|
|
|
multi_stub = self.company_id.account_check_printing_multi_stub
|
|
|
|
invoices = self.move_id._get_reconciled_invoices().sorted(key=lambda r: r.invoice_date_due)
|
|
debits = invoices.filtered(lambda r: r.move_type == 'in_invoice')
|
|
credits = invoices.filtered(lambda r: r.move_type == 'in_refund')
|
|
|
|
# Prepare the stub lines
|
|
if not credits:
|
|
stub_lines = [self._check_make_stub_line(inv) for inv in invoices]
|
|
else:
|
|
stub_lines = [{'header': True, 'name': "Bills"}]
|
|
stub_lines += [self._check_make_stub_line(inv) for inv in debits]
|
|
stub_lines += [{'header': True, 'name': "Refunds"}]
|
|
stub_lines += [self._check_make_stub_line(inv) for inv in credits]
|
|
|
|
# Crop the stub lines or split them on multiple pages
|
|
if not multi_stub:
|
|
# If we need to crop the stub, leave place for an ellipsis line
|
|
num_stub_lines = len(stub_lines) > INV_LINES_PER_STUB and INV_LINES_PER_STUB - 1 or INV_LINES_PER_STUB
|
|
stub_pages = [stub_lines[:num_stub_lines]]
|
|
else:
|
|
stub_pages = []
|
|
i = 0
|
|
while i < len(stub_lines):
|
|
# Make sure we don't start the credit section at the end of a page
|
|
if len(stub_lines) >= i + INV_LINES_PER_STUB and stub_lines[i + INV_LINES_PER_STUB - 1].get('header'):
|
|
num_stub_lines = INV_LINES_PER_STUB - 1 or INV_LINES_PER_STUB
|
|
else:
|
|
num_stub_lines = INV_LINES_PER_STUB
|
|
stub_pages.append(stub_lines[i:i + num_stub_lines])
|
|
i += num_stub_lines
|
|
|
|
return stub_pages
|
|
|
|
def _check_make_stub_line(self, invoice):
|
|
""" Return the dict used to display an invoice/refund in the stub
|
|
"""
|
|
# Find the account.partial.reconcile which are common to the invoice and the payment
|
|
if invoice.move_type in ['in_invoice', 'out_refund']:
|
|
invoice_sign = 1
|
|
invoice_payment_reconcile = invoice.line_ids.mapped('matched_debit_ids').filtered(lambda r: r.debit_move_id in self.line_ids)
|
|
else:
|
|
invoice_sign = -1
|
|
invoice_payment_reconcile = invoice.line_ids.mapped('matched_credit_ids').filtered(lambda r: r.credit_move_id in self.line_ids)
|
|
|
|
if self.currency_id != self.journal_id.company_id.currency_id:
|
|
amount_paid = abs(sum(invoice_payment_reconcile.mapped('amount_currency')))
|
|
else:
|
|
amount_paid = abs(sum(invoice_payment_reconcile.mapped('amount')))
|
|
|
|
amount_residual = invoice_sign * invoice.amount_residual
|
|
|
|
return {
|
|
'due_date': format_date(self.env, invoice.invoice_date_due),
|
|
'number': invoice.ref and invoice.name + ' - ' + invoice.ref or invoice.name,
|
|
'amount_total': formatLang(self.env, invoice_sign * invoice.amount_total, currency_obj=invoice.currency_id),
|
|
'amount_residual': formatLang(self.env, amount_residual, currency_obj=invoice.currency_id) if amount_residual * 10**4 != 0 else '-',
|
|
'amount_paid': formatLang(self.env, invoice_sign * amount_paid, currency_obj=invoice.currency_id),
|
|
'currency': invoice.currency_id,
|
|
}
|