Add workflow on chorus invoice

This commit is contained in:
Cédric Krier
2023-08-14 19:00:13 +02:00
parent d5b575e0b0
commit 6dde3d7eb5
6 changed files with 225 additions and 22 deletions
+116 -7
View File
@@ -12,7 +12,7 @@ from sql.functions import CharLength
from trytond.config import config
from trytond.i18n import gettext
from trytond.model import ModelSQL, ModelView, Unique, fields
from trytond.model import ModelSQL, ModelView, Unique, Workflow, fields
from trytond.model.exceptions import AccessError
from trytond.modules.company.model import CompanyValueMixin
from trytond.pool import Pool, PoolMeta
@@ -47,6 +47,11 @@ else:
logger = logging.getLogger(__name__)
SUCCEEDED = {'IN_INTEGRE', 'IN_RECU', 'IN_TRAITE_SE_CPP'}
FAILED = {
'IN_INCIDENTE', 'QP_IRRECEVABLE', 'QP_RECEVABLE_AVEC_ERREUR', 'IN_REJETE'}
class _SyntaxMixin(object):
__slots__ = ()
@@ -176,9 +181,11 @@ class Invoice(metaclass=PoolMeta):
InvoiceChorus.save(invoices_chorus)
class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
class InvoiceChorus(
Workflow, ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
"Invoice Chorus"
__name__ = 'account.invoice.chorus'
_history = True
invoice = fields.Many2One(
'account.invoice', "Invoice", required=True,
@@ -190,12 +197,26 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
])
syntax = fields.Selection('get_syntaxes', "Syntax", required=True)
filename = fields.Function(fields.Char("Filename"), 'get_filename')
number = fields.Char("Number", readonly=True, strip=False)
date = fields.Date("Date", readonly=True)
number = fields.Char(
"Number", readonly=True, strip=False,
states={
'required': Eval('state') == 'sent',
})
date = fields.Date(
"Date", readonly=True,
states={
'required': Eval('state') == 'sent',
})
data = fields.Binary(
"Data", filename='filename',
file_id=file_id, store_prefix=store_prefix, readonly=True)
data_file_id = fields.Char("Data File ID", readonly=True)
state = fields.Selection([
('draft', "Draft"),
('sent', "Sent"),
('done', "Done"),
('exception', "Exception"),
], "State", readonly=True, required=True, sort=False)
@classmethod
def __setup__(cls):
@@ -208,6 +229,37 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
'account_fr_chorus.msg_invoice_unique'),
]
cls._transitions |= {
('draft', 'sent'),
('sent', 'done'),
('sent', 'exception'),
('exception', 'sent'),
}
cls._buttons.update(
send={
'invisible': ~Eval('state').in_(['draft', 'exception']),
'depends': ['state'],
},
update={
'invisible': Eval('state') != 'sent',
'depends': ['state'],
},
)
@classmethod
def __register__(cls, module):
cursor = Transaction().connection.cursor()
table = cls.__table__()
table_h = cls.__table_handler__(module)
update_state = not table_h.column_exist('state')
super().__register__(module)
# Migration from 6.8: fill state
if update_state:
cursor.execute(*table.update([table.state], ['done']))
@classmethod
def default_syntax(cls):
pool = Pool()
@@ -219,6 +271,10 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
filename = EDOC2FILENAME[self.syntax] % self.invoice.number
return filename.replace('/', '-')
@classmethod
def default_state(cls):
return 'draft'
@classmethod
def order_number(cls, tables):
table, _ = tables[None]
@@ -254,6 +310,8 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
}
@classmethod
@ModelView.button
@Workflow.transition('sent')
def send(cls, records=None):
"""Send invoice to Chorus
@@ -264,11 +322,10 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
transaction = Transaction()
if not records:
records = cls.search(['OR',
('number', '=', None),
('number', '=', ''),
records = cls.search([
('invoice.company', '=',
transaction.context.get('company')),
('state', '=', 'draft'),
])
sessions = defaultdict(Credential.get_session)
@@ -291,6 +348,7 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
record.number = resp['numeroFluxDepot']
record.date = datetime.datetime.strptime(
resp['dateDepot'], '%Y-%m-%d').date()
record.state = 'sent'
record.save()
Transaction().commit()
@@ -305,3 +363,54 @@ class InvoiceChorus(ModelSQL, ModelView, _SyntaxMixin, metaclass=PoolMeta):
'syntaxeFlux': EDOC2SYNTAX[self.syntax],
'avecSignature': False,
}
@classmethod
@ModelView.button
def update(cls, records=None):
"Update state from Chorus"
pool = Pool()
Credential = pool.get('account.credential.chorus')
transaction = Transaction()
if not records:
records = cls.search([
('invoice.company', '=',
transaction.context.get('company')),
('state', '=', 'sent'),
])
sessions = defaultdict(Credential.get_session)
succeeded, failed = [], []
for record in records:
if not record.number:
continue
context = record._send_context()
with transaction.set_context(**context):
payload = {
'numeroFluxDepot': record.number,
}
resp = Credential.post(
'cpro/transverses/v1/consulterCR', payload,
session=sessions[tuple(context.items())])
if resp['codeRetour']:
logger.info(
"Error when retrieve information about %d: %s",
record.id, resp['libelle'])
elif resp['etatCourantFlux'] in SUCCEEDED:
succeeded.append(record)
elif resp['etatCourantFlux'] in FAILED:
failed.append(record)
if failed:
cls.fail(failed)
if succeeded:
cls.succeed(succeeded)
@classmethod
@Workflow.transition('done')
def succeed(cls, records):
pass
@classmethod
@Workflow.transition('exception')
def fail(cls, records):
pass
+46
View File
@@ -34,6 +34,34 @@ this repository contains the full copyright notices and license terms. -->
<field name="view" ref="invoice_chorus_view_form"/>
<field name="act_window" ref="act_invoice_chorus_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_invoice_chorus_form_domain_draft">
<field name="name">Draft</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('state', '=', 'draft')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_invoice_chorus_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_invoice_chorus_form_domain_sent">
<field name="name">Sent</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('state', '=', 'sent')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_invoice_chorus_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_invoice_chorus_form_domain_exception">
<field name="name">Exception</field>
<field name="sequence" eval="30"/>
<field name="domain" eval="[('state', '=', 'exception')]" pyson="1"/>
<field name="count" eval="True"/>
<field name="act_window" ref="act_invoice_chorus_form"/>
</record>
<record model="ir.action.act_window.domain" id="act_invoice_chorus_form_domain_all">
<field name="name">All</field>
<field name="sequence" eval="9999"/>
<field name="domain"></field>
<field name="act_window" ref="act_invoice_chorus_form"/>
</record>
<menuitem
parent="account_invoice.menu_invoice_out_form"
action="act_invoice_chorus_form"
@@ -56,10 +84,28 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.model.button" id="invoice_chorus_send_button">
<field name="name">send</field>
<field name="string">Send</field>
<field name="model" search="[('model', '=', 'account.invoice.chorus')]"/>
</record>
<record model="ir.model.button" id="invoice_chorus_update_button">
<field name="name">update</field>
<field name="string">Update</field>
<field name="model" search="[('model', '=', 'account.invoice.chorus')]"/>
</record>
<record model="ir.cron" id="cron_invoice_send">
<field name="method">account.invoice.chorus|send</field>
<field name="interval_number" eval="15"/>
<field name="interval_type">minutes</field>
</record>
<record model="ir.cron" id="cron_invoice_update">
<field name="method">account.invoice.chorus|update</field>
<field name="interval_number" eval="1"/>
<field name="interval_type">hours</field>
</record>
</data>
</tryton>
+5 -2
View File
@@ -9,5 +9,8 @@ class Cron(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super().__setup__()
cls.method.selection.append(
('account.invoice.chorus|send', "Send invoices to Chorus"))
cls.method.selection.extend([
('account.invoice.chorus|send', "Send invoices to Chorus"),
('account.invoice.chorus|update',
"Update invoices from Chorus"),
])
@@ -4,7 +4,10 @@ Account FR Chorus Invoice
Imports::
>>> import datetime as dt
>>> import os
>>> import time
>>> import uuid
>>> from decimal import Decimal
>>> from proteus import Model
@@ -17,6 +20,8 @@ Imports::
>>> from trytond.modules.account_invoice.tests.tools import (
... set_fiscalyear_invoice_sequences)
>>> today = dt.date.today()
Patch SIRET validation::
>>> from unittest.mock import patch
@@ -65,14 +70,6 @@ Create chart of accounts::
... ('group.kind', '=', 'sale'),
... ('name', 'ilike', '%normal%'),
... ], limit=1)
>>> tax.template_override = True
>>> tax.unece_code = 'VAT'
>>> tax.unece_category_code = 'S'
>>> for child in tax.childs:
... child.template_override = True
... child.unece_code = tax.unece_code
... child.unece_category_code = tax.unece_category_code
>>> tax.save()
Configure Chorus::
@@ -109,6 +106,11 @@ Create invoice::
>>> line.quantity = 5
>>> line.unit_price = Decimal('50.0000')
>>> line.taxes.append(tax)
>>> invoice.save()
>>> Invoice.write([invoice], {
... 'number': str(uuid.uuid4())[:20],
... 'invoice_date': today,
... }, config._context)
>>> invoice.click('post')
>>> invoice.state
'posted'
@@ -123,13 +125,48 @@ Check Chorus invoice::
Send to Chorus::
>>> cron, = Cron.find([('method', '=', 'account.invoice.chorus|send')])
>>> cron.click('run_once')
>>> invoice_chorus.reload()
>>> invoice_chorus.click('send')
>>> invoice_chorus.state
'sent'
>>> bool(invoice_chorus.number)
True
>>> bool(invoice_chorus.date)
True
>>> bool(invoice_chorus.data)
True
>>> number = invoice_chorus.number
Update from Chorus::
>>> while invoice_chorus.state == 'sent':
... invoice_chorus.click('update')
... time.sleep(1)
>>> invoice_chorus.state
'exception'
Add code to tax::
>>> tax.template_override = True
>>> tax.unece_code = 'VAT'
>>> tax.unece_category_code = 'S'
>>> for child in tax.childs:
... child.template_override = True
... child.unece_code = tax.unece_code
... child.unece_category_code = tax.unece_category_code
>>> tax.save()
Resend to Chorus::
>>> invoice_chorus.click('send')
>>> invoice_chorus.state
'sent'
>>> invoice_chorus.number != number
True
Update from Chorus::
>>> while invoice_chorus.state == 'sent':
... invoice_chorus.click('update')
... time.sleep(1)
>>> invoice_chorus.state
'done'
@@ -12,5 +12,12 @@ this repository contains the full copyright notices and license terms. -->
<field name="date"/>
<label name="data"/>
<field name="data"/>
<field name="data" colspan="3"/>
<label name="state"/>
<field name="state"/>
<group col="-1" colspan="2" id="buttons">
<button name="send"/>
<button name="update"/>
</group>
</form>
@@ -7,4 +7,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="number"/>
<field name="date"/>
<field name="data"/>
<field name="state"/>
</tree>