From fcf3da325ce008da2bd2b2b77198e1542c01429b Mon Sep 17 00:00:00 2001 From: dbkosky Date: Tue, 13 Dec 2022 13:18:22 +0000 Subject: [PATCH] [FIX] l10n_ke_edi_tremol: batch invoice processing At present the invoice can only be sent to the device from the form view. The functions involved in posting the invoice expect just one invoice. This commit adapts the functions involved in posting the invoice in such a way that multiple moves can be processed. In order for this to happen: The validation check function now creates a list of errors per move, and the error message provided lists the errors under the name of each move. The post function has been adapted to iterate over the moves provided, and construct the params passed to the javascript action as a list of dictionaries, rather than a flat dictionary. The javascript action that forwards the data to the device is adapted to iterate over the params provided to it, instead of sending just the one invoice. A sever action has been added on the account move (tree view) to allow a user to select multiple entries from the invoice tree view and send all of them to the fiscal device. closes odoo/odoo#109520 Task-id: 2950308 X-original-commit: 2b590e69310bdce9201ad54a1b14f22007098173 Signed-off-by: Josse Colpaert Signed-off-by: Daniel Kosky (dako) --- .../l10n_ke_edi_tremol/models/account_move.py | 81 ++++++++++++++----- .../static/src/js/send_invoice.js | 58 ++++++++----- .../views/account_move_view.xml | 11 +++ 3 files changed, 107 insertions(+), 43 deletions(-) diff --git a/addons/l10n_ke_edi_tremol/models/account_move.py b/addons/l10n_ke_edi_tremol/models/account_move.py index 51ac1a5a85e..3edbd62ff82 100644 --- a/addons/l10n_ke_edi_tremol/models/account_move.py +++ b/addons/l10n_ke_edi_tremol/models/account_move.py @@ -39,28 +39,47 @@ class AccountMove(models.Model): # ------------------------------------------------------------------------- def _l10n_ke_validate_move(self): - """ Returns list of errors related to misconfigurations + """ Returns list of errors related to misconfigurations per move Find misconfigurations on the move, the lines of the move, and the taxes on those lines that would result in rejection by the KRA. """ - self.ensure_one() errors = [] - # The credit note should refer to the control unit number (receipt number) of the original - # invoice to which it relates. - if self.move_type == 'out_refund' and not self.reversed_entry_id.l10n_ke_cu_invoice_number: - errors.append(_("This credit note must reference the previous invoice, and this previous invoice must have already been submitted.")) + for move in self: + move_errors = [] + if move.country_code != 'KE': + move_errors.append(_("This invoice is not a Kenyan invoice and therefore can not be sent to the device.")) - for line in self.invoice_line_ids.filtered(lambda l: l.display_type == 'product'): - if not line.tax_ids or len(line.tax_ids) > 1: - errors.append(_("On line %s, you must select one and only one tax.", line.name)) - else: - if line.tax_ids.amount == 0 and not (line.product_id and line.product_id.l10n_ke_hsn_code and line.product_id.l10n_ke_hsn_name): - errors.append(_("On line %s, a product with a HS Code and HS Name must be selected, since the tax is 0%% or exempt.", line.name)) + if move.company_id.currency_id != self.env.ref('base.KES'): + move_errors.append(_("This invoice's company currency is not in Kenyan Shillings, conversion to KES is not possible.")) - for tax in self.invoice_line_ids.tax_ids: - if tax.amount not in (16, 8, 0): - errors.append(_("Tax '%s' is used, but only taxes of 16%%, 8%%, 0%% or Exempt can be sent. Please reconfigure or change the tax.", tax.name)) + if move.state != 'posted': + move_errors.append(_("This invoice/credit note has not been posted. Please confirm it to continue.")) + + if move.move_type not in ('out_refund', 'out_invoice'): + move_errors.append(_("The document being sent should be an invoice or credit note.")) + + if any([move.l10n_ke_cu_invoice_number, move.l10n_ke_cu_serial_number, move.l10n_ke_cu_qrcode, move.l10n_ke_cu_datetime]): + move_errors.append(_("The document already has details related to the fiscal device. Please make sure that the invoice has not already been sent.")) + + # The credit note should refer to the control unit number (receipt number) of the original + # invoice to which it relates. + if move.move_type == 'out_refund' and not move.reversed_entry_id.l10n_ke_cu_invoice_number: + move_errors.append(_("This credit note must reference the previous invoice, and this previous invoice must have already been submitted.")) + + for line in self.invoice_line_ids.filtered(lambda l: l.display_type == 'product'): + if not line.tax_ids or len(line.tax_ids) > 1: + move_errors.append(_("On line %s, you must select one and only one tax.", line.name)) + else: + if line.tax_ids.amount == 0 and not (line.product_id and line.product_id.l10n_ke_hsn_code and line.product_id.l10n_ke_hsn_name): + move_errors.append(_("On line %s, a product with a HS Code and HS Name must be selected, since the tax is 0%% or exempt.", line.name)) + + for tax in move.invoice_line_ids.tax_ids: + if tax.amount not in (16, 8, 0): + move_errors.append(_("Tax '%s' is used, but only taxes of 16%%, 8%%, 0%% or Exempt can be sent. Please reconfigure or change the tax.", tax.name)) + + if move_errors: + errors.append((move.name, move_errors)) return errors @@ -187,6 +206,10 @@ class AccountMove(models.Model): return msgs def _l10n_ke_get_cu_messages(self): + """ Composes a list of all the command and data parts of the messages + required for the fiscal device to open an invoice, add lines and + subsequently close it. + """ self.ensure_one() msgs = self._l10n_ke_cu_open_invoice_message() msgs += self._l10n_ke_cu_lines_messages() @@ -201,24 +224,38 @@ class AccountMove(models.Model): # ------------------------------------------------------------------------- def l10n_ke_action_cu_post(self): - self.ensure_one() + """ Returns the client action descriptor dictionary for sending the + invoice(s) to the fiscal device. + """ # Check the configuration of the invoice errors = self._l10n_ke_validate_move() if errors: - raise UserError(_("Invalid invoice configuration:\n\n%s") % '\n'.join(errors)) + error_msg = "" + for move, error_list in errors: + error_list = '\n'.join(error_list) + error_msg += _("Invalid invoice configration on %s:\n\n%s", move, error_list) + raise UserError(error_msg) return { 'type': 'ir.actions.client', 'tag': 'post_send', 'params': { - 'messages': json.dumps([m.decode('cp1251') for m in self._l10n_ke_get_cu_messages()]), - 'move_id': self.id, - 'proxy_address': self.company_id.l10n_ke_cu_proxy_address, - 'company_vat': self.company_id.vat, + 'invoices': { + move.id: { + 'messages': json.dumps([msg.decode('cp1251') for msg in move._l10n_ke_get_cu_messages()]), + 'proxy_address': move.company_id.l10n_ke_cu_proxy_address, + 'company_vat': move.company_id.vat + } for move in self + } } } def l10n_ke_cu_response(self, response): - move = self.browse(response['move_id']) + """ Set the fields related to the fiscal device on the invoice. + + This is intended to be utilized by an RPC call from the javascript + client action. + """ + move = self.browse(int(response['move_id'])) replies = [msg for msg in response['replies']] move.update({ 'l10n_ke_cu_serial_number': response['serial_number'], diff --git a/addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js b/addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js index 8d88d67ac26..a58e5dc7c85 100644 --- a/addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js +++ b/addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js @@ -6,28 +6,44 @@ odoo.define('l10n_ke_edi_tremol.action_post_send_invoice', function (require) { var _t = core._t; async function post_send(parent, {params}) { - const move_id = params.move_id; - await ajax.post(params.proxy_address + '/hw_proxy/l10n_ke_cu_send', params).then(function (res) { - const res_obj = JSON.parse(res); - if (res_obj.status != "ok") { - Dialog.alert(this, "Posting the invoice has failed, with the message: \n" + res_obj.status); - } else { - rpc.query({ - model: 'account.move', - method: 'l10n_ke_cu_response', - args: [[], {'replies': res_obj.replies, 'serial_number': res_obj.serial_number, 'move_id': move_id}], - }).then(function () { - parent.services.action.doAction({ - 'type': 'ir.actions.client', - 'tag': 'reload', - }); - }, function () { - Dialog.alert(this, _t("Error trying to connect to Odoo. Check your internet connection")); - }) + let refresh = false; + for (let move_id in params.invoices) { + try { + const res = await ajax.post( + params.invoices[move_id].proxy_address + '/hw_proxy/l10n_ke_cu_send', { + messages: params.invoices[move_id].messages, + company_vat: params.invoices[move_id].company_vat + } + ); + const res_obj = JSON.parse(res); + if (res_obj.status === "ok") { + try { + await rpc.query({ + model: 'account.move', + method: 'l10n_ke_cu_response', + args: [[], {'replies': res_obj.replies, 'serial_number': res_obj.serial_number, 'move_id': move_id}], + }); + refresh = true; + } catch (e) { + Dialog.alert(this, _t('Error trying to connect to Odoo. Check your internet connection.\n Error code: ') + e.status); + break; + } + } else { + Dialog.alert(this, _t("Posting an invoice has failed, with the message: \n") + res_obj.status); + break; + } + } catch(e) { + console.log(e) + Dialog.alert(this, _t('Error trying to connect to the middleware. Is the middleware running?\n Error code: ') + e.status); + break; } - }, function () { - Dialog.alert(this, _t("Error trying to connect to the middleware. Is the middleware running?")); - }) + } + if (refresh) { + parent.services.action.doAction({ + 'type': 'ir.actions.client', + 'tag': 'reload', + }); + } } core.action_registry.add('post_send', post_send); return post_send; diff --git a/addons/l10n_ke_edi_tremol/views/account_move_view.xml b/addons/l10n_ke_edi_tremol/views/account_move_view.xml index 72e74393ecc..92dc801e431 100644 --- a/addons/l10n_ke_edi_tremol/views/account_move_view.xml +++ b/addons/l10n_ke_edi_tremol/views/account_move_view.xml @@ -42,6 +42,17 @@ + + Send invoices to fiscal device + + + list + code + + action = records.l10n_ke_action_cu_post() + + + l10n.ke.inherit.account.move.search account.move