Allow refreshing invoice report cache

Closes #12091
This commit is contained in:
Maxime Richez
2023-09-19 14:12:42 +02:00
parent 2d72bc32b1
commit d603d56f71
8 changed files with 253 additions and 11 deletions
+1
View File
@@ -1,3 +1,4 @@
* Allow refreshing invoice report cache
* Set employee who validates and posts invoices
* Allow modification of invoice references when report is not cached
* Add cache on invoice's amounts
+2
View File
@@ -23,6 +23,7 @@ def register():
invoice.PayInvoiceStart,
invoice.PayInvoiceAsk,
invoice.CreditInvoiceStart,
invoice.InvoiceReportRevision,
party.Address,
party.ContactMechanism,
party.Party,
@@ -46,6 +47,7 @@ def register():
invoice.CreditInvoice,
invoice.RescheduleLinesToPay,
invoice.DelegateLinesToPay,
invoice.RefreshInvoiceReport,
party.Replace,
party.Erase,
account.RenewFiscalYear,
+106 -11
View File
@@ -5,6 +5,7 @@ from collections import defaultdict, namedtuple
from decimal import Decimal
from itertools import chain, combinations, groupby
from genshi.template.text import TextTemplate
from sql import Literal, Null
from sql.aggregate import Sum
from sql.conditionals import Coalesce
@@ -27,7 +28,7 @@ from trytond.pool import Pool
from trytond.pyson import Bool, Eval, If
from trytond.report import Report
from trytond.rpc import RPC
from trytond.tools import firstline, grouped_slice, reduce_ids
from trytond.tools import firstline, grouped_slice, reduce_ids, slugify
from trytond.transaction import Transaction
from trytond.wizard import (
Button, StateAction, StateTransition, StateView, Wizard)
@@ -45,7 +46,17 @@ else:
store_prefix = None
class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
class InvoiceReportMixin:
__slots__ = ()
invoice_report_cache = fields.Binary(
"Invoice Report", readonly=True,
file_id=file_id, store_prefix=store_prefix)
invoice_report_cache_id = fields.Char("Invoice Report ID", readonly=True)
invoice_report_format = fields.Char("Invoice Report Format", readonly=True)
class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin, InvoiceReportMixin):
'Invoice'
__name__ = 'account.invoice'
_rec_name = 'number'
@@ -291,10 +302,12 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
amount_to_pay = fields.Function(Monetary(
"Amount to Pay", currency='currency', digits='currency'),
'get_amount_to_pay')
invoice_report_cache = fields.Binary('Invoice Report', readonly=True,
file_id=file_id, store_prefix=store_prefix)
invoice_report_cache_id = fields.Char('Invoice Report ID', readonly=True)
invoice_report_format = fields.Char('Invoice Report Format', readonly=True)
invoice_report_revisions = fields.One2Many(
'account.invoice.report.revision', 'invoice',
"Invoice Report Revisions", readonly=True,
states={
'invisible': ~Eval('invoice_report_revisions'),
})
allow_cancel = fields.Function(
fields.Boolean("Allow Cancel Invoice"), 'get_allow_cancel')
has_payment_method = fields.Function(
@@ -342,7 +355,8 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
'move', 'cancel_move', 'additional_moves',
'invoice_report_cache', 'invoice_report_format',
'total_amount_cache', 'tax_amount_cache', 'untaxed_amount_cache',
'lines', 'reference'}
'lines', 'reference', 'invoice_report_cache_id',
'invoice_report_revisions'}
cls._order = [
('number', 'DESC NULLS FIRST'),
('id', 'DESC'),
@@ -1249,6 +1263,26 @@ class Invoice(Workflow, ModelSQL, ModelView, TaxableMixin):
"Return the default computed tax identifier"
return self.company.party.tax_identifier
@property
def invoice_report_versioned(self):
return self.state in {'posted', 'paid'} and self.type == 'out'
def create_invoice_report_revision(self):
pool = Pool()
InvoiceReportRevision = pool.get('account.invoice.report.revision')
if not self.invoice_report_versioned:
return
invoice_report_revision = InvoiceReportRevision(
invoice=self,
invoice_report_cache=self.invoice_report_cache,
invoice_report_cache_id=self.invoice_report_cache_id,
invoice_report_format=self.invoice_report_format)
self.invoice_report_revisions += (invoice_report_revision,)
self.invoice_report_cache = None
self.invoice_report_cache_id = None
self.invoice_report_format = None
return invoice_report_revision
@property
def is_modifiable(self):
return not (self.state in {'posted', 'paid'}
@@ -3066,6 +3100,70 @@ class PaymentMethod(DeactivableMixin, ModelSQL, ModelView):
return Transaction().context.get('company')
class InvoiceReportRevision(ModelSQL, ModelView, InvoiceReportMixin):
"Invoice Report Revision"
__name__ = 'account.invoice.report.revision'
invoice = fields.Many2One(
'account.invoice', "Invoice", required=True, ondelete='CASCADE')
date = fields.DateTime("Date", required=True, readonly=True)
filename = fields.Function(fields.Char("File Name"), 'get_filename')
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('invoice')
cls._order.insert(0, ('date', 'DESC'))
cls.invoice_report_cache.filename = 'filename'
@classmethod
def default_date(cls):
return dt.datetime.now()
@classmethod
def get_filename(cls, revisions, name):
pool = Pool()
ActionReport = pool.get('ir.action.report')
action_report, = ActionReport.search([
('report_name', '=', 'account.invoice'),
], limit=1)
action_report_name = action_report.name[:100]
if action_report.record_name:
template = TextTemplate(action_report.record_name)
else:
template = None
filenames = {}
for revision in revisions:
invoice = revision.invoice
if template:
record_name = template.generate(record=invoice).render()
else:
record_name = invoice.rec_name
filename = '-'.join([action_report_name, record_name])
filenames[revision.id] = (
f'{slugify(filename)}.{revision.invoice_report_format}')
return filenames
class RefreshInvoiceReport(Wizard):
"Refresh Invoice Report"
__name__ = 'account.invoice.refresh_invoice_report'
start_state = 'archive'
archive = StateTransition()
print_ = StateAction('account_invoice.report_invoice')
def transition_archive(self):
for record in self.records:
record.create_invoice_report_revision()
self.model.save(self.records)
return 'print_'
def do_print_(self, action):
ids = [r.id for r in self.records]
return action, {'ids': ids}
class InvoiceReport(Report):
__name__ = 'account.invoice'
@@ -3086,10 +3184,7 @@ class InvoiceReport(Report):
invoice.invoice_report_cache)
else:
result = super()._execute(records, header, data, action)
# If the invoice is posted or paid and the report not saved in
# invoice_report_cache there was an error somewhere. So we save it
# now in invoice_report_cache
if invoice.state in {'posted', 'paid'} and invoice.type == 'out':
if invoice.invoice_report_versioned:
format_, data = result
if isinstance(data, str):
data = bytes(data, 'utf-8')
+33
View File
@@ -292,6 +292,39 @@ this repository contains the full copyright notices and license terms. -->
<field name="group" ref="account.group_account_admin"/>
</record>
<record model="ir.action.wizard" id="refresh_invoice_report_wizard">
<field name="name">Invoice (revised)</field>
<field name="wiz_name">account.invoice.refresh_invoice_report</field>
<field name="model">account.invoice</field>
</record>
<record model="ir.action.keyword" id="refresh_invoice_report_keyword">
<field name="keyword">form_print</field>
<field name="model">account.invoice,-1</field>
<field name="action" ref="refresh_invoice_report_wizard"/>
</record>
<record model="ir.action-res.group" id="refresh_invoice_report-group_account_admin">
<field name="action" ref="refresh_invoice_report_wizard"/>
<field name="group" ref="account.group_account_admin"/>
</record>
<record model="ir.ui.view" id="invoice_report_revision_view_list">
<field name="model">account.invoice.report.revision</field>
<field name="type">tree</field>
<field name="name">invoice_report_revision_list</field>
</record>
<record model="ir.ui.view" id="invoice_report_revision_view_form">
<field name="model">account.invoice.report.revision</field>
<field name="type">form</field>
<field name="name">invoice_report_revision_form</field>
</record>
<record model="ir.model.access" id="access_invoice_report_revision">
<field name="model" search="[('model', '=', 'account.invoice.report.revision')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.action.report" id="report_invoice">
<field name="name">Invoice</field>
<field name="model">account.invoice</field>
@@ -0,0 +1,90 @@
================================
Invoice Report Revision Scenario
================================
Imports::
>>> import datetime as dt
>>> from decimal import Decimal
>>> from proteus import Model, Wizard
>>> from trytond.tests.tools import activate_modules
>>> from trytond.modules.company.tests.tools import (
... create_company, get_company)
>>> from trytond.modules.account.tests.tools import (
... create_fiscalyear, create_chart, get_accounts, create_tax)
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> today = dt.date.today()
Activate modules::
>>> config = activate_modules('account_invoice')
>>> Invoice = Model.get('account.invoice')
>>> Journal = Model.get('account.journal')
>>> Move = Model.get('account.move')
>>> Party = Model.get('party.party')
>>> PaymentMethod = Model.get('account.invoice.payment.method')
>>> ProductCategory = Model.get('product.category')
>>> ProductTemplate = Model.get('product.template')
>>> ProductUom = Model.get('product.uom')
>>> Sequence = Model.get('ir.sequence')
Create company::
>>> _ = create_company()
>>> company = get_company()
Create fiscal year::
>>> fiscalyear = set_fiscalyear_invoice_sequences(
... create_fiscalyear(company))
>>> fiscalyear.click('create_period')
>>> period = fiscalyear.periods[0]
Create chart of accounts::
>>> _ = create_chart(company)
>>> accounts = get_accounts(company)
Create party::
>>> party = Party(name="Party")
>>> party.save()
Post an invoice::
>>> invoice = Invoice()
>>> invoice.type = 'out'
>>> invoice.party = party
>>> invoice.invoice_date = today
>>> line = invoice.lines.new()
>>> line.account = accounts['revenue']
>>> line.quantity = 5
>>> line.unit_price = Decimal('20')
>>> invoice.click('post')
>>> invoice.state
'posted'
Check invoice report::
>>> bool(invoice.invoice_report_cache)
True
>>> len(invoice.invoice_report_revisions)
0
>>> invoice.invoice_report_format
'odt'
Execute update invoice report wizard::
>>> refresh_invoice_report = Wizard(
... 'account.invoice.refresh_invoice_report', [invoice])
>>> revision, = invoice.invoice_report_revisions
>>> bool(revision.invoice_report_cache)
True
>>> revision.invoice_report_format
'odt'
>>> revision.filename
'Invoice-1.odt'
@@ -84,6 +84,9 @@ this repository contains the full copyright notices and license terms. -->
view_ids="account_invoice.move_line_view_list_payment"/>
<field name="reconciliation_lines" colspan="4" view_ids="account_invoice.move_line_view_list_payment"/>
</page>
<page name="invoice_report_revisions">
<field name="invoice_report_revisions" colspan="4"/>
</page>
</notebook>
<label id="empty" colspan="3"/>
<group col="-1" colspan="3" id="buttons">
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="date"/>
<field name="date"/>
<label name="invoice_report_cache"/>
<field name="invoice_report_cache"/>
</form>
@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="invoice" expand="2"/>
<field name="date" widget="date"/>
<field name="date" widget="time" string="Time"/>
<field name="invoice_report_cache" widget="binary" expand="1"/>
</tree>