[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 <jco@odoo.com>
Signed-off-by: Daniel Kosky (dako) <dako@odoo.com>
This commit is contained in:
dbkosky
2023-01-13 16:28:03 +01:00
committed by Daniel Kosky (dako)
parent fee5e773d1
commit fcf3da325c
3 changed files with 107 additions and 43 deletions
@@ -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'],
@@ -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;
@@ -42,6 +42,17 @@
</field>
</record>
<record id="action_send_invoices_to_device" model="ir.actions.server">
<field name="name">Send invoices to fiscal device</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">
action = records.l10n_ke_action_cu_post()
</field>
</record>
<record id="l10n_ke_inherit_account_move_search_view" model="ir.ui.view">
<field name="name">l10n.ke.inherit.account.move.search</field>
<field name="model">account.move</field>