Files
odoo_source/addons/account_check_printing/models/account_payment.py
T
william 82dc0cb7b9 [IMP] account: soft post entries in the future
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,... )
2020-08-05 11:57:10 +00:00

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,
}