19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:31:34 +01:00
parent c5006a6999
commit 80293571e7
420 changed files with 21812 additions and 44297 deletions

View file

@ -1,7 +1,7 @@
# Kenya Tremol Device EDI Integration
This module integrates with the Kenyan G03 Tremol control unit device to the KRA through TIMS.
This module integrates with the Kenyan G03 Tremol control unit device to the KRA through TIMS.
## Installation
@ -12,37 +12,14 @@ pip install odoo-bringout-oca-ocb-l10n_ke_edi_tremol
## Dependencies
This addon depends on:
- l10n_ke
## Manifest Information
- **Name**: Kenya Tremol Device EDI Integration
- **Version**: 1.0
- **Category**: Accounting/Localizations/EDI
- **License**: LGPL-3
- **Installable**: False
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `l10n_ke_edi_tremol`.
- Repository: https://github.com/OCA/OCB
- Branch: 19.0
- Path: addons/l10n_ke_edi_tremol
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Reports: doc/REPORTS.md
- Security: doc/SECURITY.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md
This package preserves the original LGPL-3 license.

View file

@ -1 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
#
from . import models
from . import wizard

View file

@ -1,11 +1,10 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': "Kenya Tremol Device EDI Integration",
'summary': """
Kenya Tremol Device EDI Integration
""",
'countries': ['ke'],
'summary': "Kenya Tremol Device EDI Integration",
'description': """
This module integrates with the Kenyan G03 Tremol control unit device to the KRA through TIMS.
This module integrates with the Kenyan G03 Tremol control unit device to the KRA through TIMS.
""",
'author': 'Odoo',
'category': 'Accounting/Localizations/EDI',
@ -14,14 +13,13 @@
'depends': ['l10n_ke'],
'data': [
'views/account_move_view.xml',
'views/product_view.xml',
'views/report_invoice.xml',
'views/res_config_settings_view.xml',
'views/res_partner_views.xml',
],
'assets': {
'web.assets_backend': [
'l10n_ke_edi_tremol/static/src/js/send_invoice.js',
'l10n_ke_edi_tremol/static/src/components/*',
],
},
}

View file

@ -4,10 +4,10 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Project-Id-Version: Odoo Server 19.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-01-08 23:34+0000\n"
"PO-Revision-Date: 2023-01-08 23:34+0000\n"
"POT-Creation-Date: 2025-12-30 19:07+0000\n"
"PO-Revision-Date: 2025-12-30 19:07+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@ -35,46 +35,59 @@ msgstr ""
msgid "<b>Serial Number: </b><br/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid ""
"<span class=\"o_form_label\">Tremol Device Settings</span>\n"
" <span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-specific.\" aria-label=\"Values set here are company-specific.\" groups=\"base.group_multi_company\" role=\"img\"/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "<strong class=\"text-center\">TIMS URL</strong><br/><br/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_account_move_send
msgid "Account Move Send"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_account_move_send_wizard
msgid "Account Move Send Wizard"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"An OSCU has been initialized for this company. Please send the e-invoice via"
" Send and Print -> Send to eTIMS instead."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_invoice_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_invoice_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_payment__l10n_ke_cu_invoice_number
msgid "CU Invoice Number"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_qrcode
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_qrcode
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_payment__l10n_ke_cu_qrcode
msgid "CU QR Code"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_serial_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_serial_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_payment__l10n_ke_cu_serial_number
msgid "CU Serial Number"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_datetime
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_datetime
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_payment__l10n_ke_cu_datetime
msgid "CU Signing Date and Time"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid "Check Invoice(s)"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_res_company
msgid "Companies"
@ -85,23 +98,46 @@ msgstr ""
msgid "Config Settings"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid "Connection lost, please try again later."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_res_partner
msgid "Contact"
msgstr ""
#. module: l10n_ke_edi_tremol
#. openerp-web
#: code:addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js:0
#, python-format
msgid "Error trying to connect to Odoo. Check your internet connection"
#: model:ir.model,website_form_label:l10n_ke_edi_tremol.model_res_partner
msgid "Create a Customer"
msgstr ""
#. module: l10n_ke_edi_tremol
#. openerp-web
#: code:addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js:0
#, python-format
msgid "Error trying to connect to the middleware. Is the middleware running?"
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send_wizard__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_company__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_config_settings__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_partner__display_name
msgid "Display Name"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Error trying to connect to Odoo. Check your internet connection. Error "
"message: %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Error trying to connect to the middleware. Is the middleware running? \n"
" Error message: %s"
msgstr ""
#. module: l10n_ke_edi_tremol
@ -117,16 +153,21 @@ msgid "Fiscal Device Proxy Address"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_product_template_form_view
msgid "HS Code"
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send_wizard__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_company__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_config_settings__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_partner__id
msgid "ID"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"Invalid invoice configuration on %s:\n"
"%s\n"
"Invalid invoice configuration on %(invoice)s:\n"
"%(error_list)s\n"
"\n"
msgstr ""
@ -135,19 +176,6 @@ msgstr ""
msgid "Journal Entry"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_product_product__l10n_ke_hsn_code
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_product_template__l10n_ke_hsn_code
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_product_product_form_view
msgid "KRA Item Code"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_product_product__l10n_ke_hsn_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_product_template__l10n_ke_hsn_name
msgid "KRA Item Description"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_partner_view_form
msgid "Kenya Accounting Details"
@ -164,41 +192,37 @@ msgid "Kenya TIMS Integration"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_dialog.xml:0
msgid "OK"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"On line %s, a product with a HS Code and HS Name must be selected, since the"
" tax is 0%% or exempt."
"On line %s, a tax with a KRA item code must be selected, since the tax is "
"0%% or exempt."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid "On line %s, you must select one and only one tax."
msgid "On line %s, you must select one and only one VAT tax."
msgstr ""
#. module: l10n_ke_edi_tremol
#. openerp-web
#: code:addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js:0
#, python-format
msgid "Posting an invoice has failed, with the message: \n"
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid "Posting invoice: %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_product_template
msgid "Product Template"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_product_product__l10n_ke_hsn_name
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_product_template__l10n_ke_hsn_name
msgid "Product code description needed when not 16% VAT rated. "
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_product_product__l10n_ke_hsn_code
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_product_template__l10n_ke_hsn_code
msgid "Product code needed when not 16% VAT rated. "
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Posting the invoice %s has failed with the message: \n"
" %s"
msgstr ""
#. module: l10n_ke_edi_tremol
@ -217,18 +241,15 @@ msgid "Send to fiscal device"
msgstr ""
#. module: l10n_ke_edi_tremol
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"Tax '%s' is used, but only taxes of 16%%, 8%%, 0%% or Exempt can be sent. "
"Please reconfigure or change the tax."
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_dialog.xml:0
msgid "Sending Invoices to Fiscal Device"
msgstr ""
#. module: l10n_ke_edi_tremol
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"Tax exempt report line cannot be found, please update the l10n_ke module."
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_show_send_button
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_show_send_button
msgid "Show Send to Tremol button"
msgstr ""
#. module: l10n_ke_edi_tremol
@ -238,16 +259,16 @@ msgid "The address of the proxy server for the fiscal device."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"The document already has details related to the fiscal device. Please make "
"sure that the invoice has not already been sent."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid "The document being sent should be an invoice or credit note."
msgstr ""
@ -259,39 +280,54 @@ msgid ""
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid ""
"The tremol device makes use of a proxy server, which can be running locally on your computer or on an IoT Box.\n"
" The proxy server must be on the same network as the fiscal device."
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid "The following documents have no details related to the fiscal device."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid ""
"The tremol device makes use of a proxy server, which can be running locally "
"on your computer or on an IoT Box. The proxy server must be on the same "
"network as the fiscal device."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"This credit note must reference the previous invoice, and this previous "
"invoice must have already been submitted."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid ""
"This document does not have details related to the fiscal device, a proforma"
" invoice will be used."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"This invoice is not a Kenyan invoice and therefore can not be sent to the "
"device."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"This invoice's company currency is not in Kenyan Shillings, conversion to "
"KES is not possible."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
#, python-format
msgid ""
"This invoice/credit note has not been posted. Please confirm it to continue."
msgstr ""
@ -301,7 +337,26 @@ msgstr ""
msgid "To Send to TIMS"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid "Tremol Device Settings"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_account_move_form
msgid "Tremol GO3 Fiscal Device"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Unexpected Error:\n"
" %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid "View Invoice(s)"
msgstr ""

View file

@ -0,0 +1,362 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_ke_edi_tremol
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 19.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-12-30 19:07+0000\n"
"PO-Revision-Date: 2025-11-13 14:26+0000\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: sw\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "<b>Date and Time of Signing: </b><br/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "<b>Invoice Number: </b><br/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "<b>Kenyan Fiscal Device Info</b>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "<b>Serial Number: </b><br/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "<strong class=\"text-center\">TIMS URL</strong><br/><br/>"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_account_move_send
msgid "Account Move Send"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_account_move_send_wizard
msgid "Account Move Send Wizard"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"An OSCU has been initialized for this company. Please send the e-invoice via "
"Send and Print -> Send to eTIMS instead."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_invoice_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_invoice_number
msgid "CU Invoice Number"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_qrcode
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_qrcode
msgid "CU QR Code"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_serial_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_serial_number
msgid "CU Serial Number"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_datetime
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_datetime
msgid "CU Signing Date and Time"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid "Check Invoice(s)"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_res_company
msgid "Companies"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid "Connection lost, please try again later."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_res_partner
msgid "Contact"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,website_form_label:l10n_ke_edi_tremol.model_res_partner
msgid "Create a Customer"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send_wizard__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_company__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_config_settings__display_name
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_partner__display_name
msgid "Display Name"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Error trying to connect to Odoo. Check your internet connection. Error "
"message: %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Error trying to connect to the middleware. Is the middleware running? \n"
" Error message: %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_partner__l10n_ke_exemption_number
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_users__l10n_ke_exemption_number
msgid "Exemption Number"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_company__l10n_ke_cu_proxy_address
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_config_settings__l10n_ke_cu_proxy_address
msgid "Fiscal Device Proxy Address"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move_send_wizard__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_company__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_config_settings__id
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_res_partner__id
msgid "ID"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"Invalid invoice configuration on %(invoice)s:\n"
"%(error_list)s\n"
"\n"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model,name:l10n_ke_edi_tremol.model_account_move
msgid "Journal Entry"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_partner_view_form
msgid "Kenya Accounting Details"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_account_move_search_view
msgid "Kenya CU Invoice Number"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid "Kenya TIMS Integration"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_dialog.xml:0
msgid "OK"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"On line %s, a tax with a KRA item code must be selected, since the tax is 0%"
"% or exempt."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid "On line %s, you must select one and only one VAT tax."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid "Posting invoice: %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Posting the invoice %s has failed with the message: \n"
" %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_invoice
msgid "QR Code"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_account_move_form
msgid "Send To Fiscal Device"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.actions.server,name:l10n_ke_edi_tremol.action_send_invoices_to_device
msgid "Send to fiscal device"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_dialog.xml:0
msgid "Sending Invoices to Fiscal Device"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_bank_statement_line__l10n_ke_cu_show_send_button
#: model:ir.model.fields,field_description:l10n_ke_edi_tremol.field_account_move__l10n_ke_cu_show_send_button
msgid "Show Send to Tremol button"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_res_company__l10n_ke_cu_proxy_address
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_res_config_settings__l10n_ke_cu_proxy_address
msgid "The address of the proxy server for the fiscal device."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"The document already has details related to the fiscal device. Please make "
"sure that the invoice has not already been sent."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid "The document being sent should be an invoice or credit note."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_res_partner__l10n_ke_exemption_number
#: model:ir.model.fields,help:l10n_ke_edi_tremol.field_res_users__l10n_ke_exemption_number
msgid "The exemption number of the partner. Provided by the Kenyan government."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid "The following documents have no details related to the fiscal device."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid ""
"The tremol device makes use of a proxy server, which can be running locally "
"on your computer or on an IoT Box. The proxy server must be on the same "
"network as the fiscal device."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"This credit note must reference the previous invoice, and this previous "
"invoice must have already been submitted."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid ""
"This document does not have details related to the fiscal device, a proforma "
"invoice will be used."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"This invoice is not a Kenyan invoice and therefore can not be sent to the "
"device."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"This invoice's company currency is not in Kenyan Shillings, conversion to "
"KES is not possible."
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move.py:0
msgid ""
"This invoice/credit note has not been posted. Please confirm it to continue."
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_account_move_search_view
msgid "To Send to TIMS"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.res_config_settings_view_form
msgid "Tremol Device Settings"
msgstr ""
#. module: l10n_ke_edi_tremol
#: model_terms:ir.ui.view,arch_db:l10n_ke_edi_tremol.l10n_ke_inherit_account_move_form
msgid "Tremol GO3 Fiscal Device"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-javascript
#: code:addons/l10n_ke_edi_tremol/static/src/components/ke_proxy_hook.js:0
msgid ""
"Unexpected Error:\n"
" %s"
msgstr ""
#. module: l10n_ke_edi_tremol
#. odoo-python
#: code:addons/l10n_ke_edi_tremol/models/account_move_send.py:0
msgid "View Invoice(s)"
msgstr ""

View file

@ -1,5 +1,5 @@
from . import account_move
from . import product
from . import account_move_send
from . import res_company
from . import res_config_settings
from . import res_partner

View file

@ -5,11 +5,12 @@ import json
import re
from datetime import datetime
from odoo import models, fields, _
from odoo import models, fields, _, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class AccountMove(models.Model):
_inherit = 'account.move'
@ -17,6 +18,18 @@ class AccountMove(models.Model):
l10n_ke_cu_serial_number = fields.Char(string='CU Serial Number', copy=False)
l10n_ke_cu_invoice_number = fields.Char(string='CU Invoice Number', copy=False)
l10n_ke_cu_qrcode = fields.Char(string='CU QR Code', copy=False)
l10n_ke_cu_show_send_button = fields.Boolean(string='Show Send to Tremol button', compute='_compute_l10n_ke_cu_show_send_button')
@api.depends('country_code', 'l10n_ke_cu_qrcode', 'state', 'move_type', 'company_id')
def _compute_l10n_ke_cu_show_send_button(self):
for move in self:
move.l10n_ke_cu_show_send_button = (
move.country_code == 'KE'
and not move.l10n_ke_cu_qrcode
and move.state == 'posted'
and move.move_type in ['out_invoice', 'out_refund']
and not move.company_id.l10n_ke_oscu_is_active
)
# -------------------------------------------------------------------------
# HELPERS
@ -72,14 +85,27 @@ class AccountMove(models.Model):
if not vat_taxes or len(vat_taxes) > 1:
move_errors.append(_("On line %s, you must select one and only one VAT tax.", line.name))
else:
if vat_taxes[0].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))
if vat_taxes[0].amount == 0 and not line.tax_ids[0].l10n_ke_item_code_id:
move_errors.append(_("On line %s, a tax with a KRA item code must be selected, since the tax is 0%% or exempt.", line.name))
if move_errors:
errors.append((move.name, move_errors))
return errors
def _l10n_ke_fiscal_device_details_filled(self):
self.ensure_one()
# If the company is configured for OSCU, don't block the Send & Print.
if self.company_id.l10n_ke_oscu_is_active:
return True
return all([
self.country_code == 'KE',
self.l10n_ke_cu_invoice_number,
self.l10n_ke_cu_serial_number,
self.l10n_ke_cu_qrcode,
self.l10n_ke_cu_datetime,
])
# -------------------------------------------------------------------------
# SERIALISERS
# -------------------------------------------------------------------------
@ -163,45 +189,30 @@ class AccountMove(models.Model):
discount_dict[candidate.id] += rest_to_discount
break
vat_class = {16.0: 'A', 8.0: 'B'}
msgs = []
tax_details = self._prepare_invoice_aggregated_taxes()
for line in self.invoice_line_ids.filtered(lambda l: l.display_type == 'product' and l.quantity and l.price_total > 0 and not discount_dict.get(l.id) >= 100):
# Here we use the original discount of the line, since it the distributed discount has not been applied in the price_total
price_total = 0
percentage = 0
item_code = line.tax_ids[0].l10n_ke_item_code_id
for tax in tax_details['tax_details_per_record'][line]['tax_details']:
if tax['tax'].amount in (16, 8, 0): # This should only occur once
if tax.amount in (16, 8, 0): # This should only occur once
line_tax_details = tax_details['tax_details_per_record'][line]['tax_details'][tax]
price_total = abs(line_tax_details['base_amount_currency']) + abs(line_tax_details['tax_amount_currency'])
percentage = tax['tax'].amount
percentage = tax.amount
price = round(price_total / abs(line.quantity) * 100 / (100 - line.discount), line.currency_id.decimal_places) * currency_rate
price = ('%.5f' % price).rstrip('0').rstrip('.')
# Letter to classify tax, 0% taxes are handled conditionally, as the tax can be zero-rated or exempt
letter = ''
if percentage in vat_class:
letter = vat_class[percentage]
else:
report_line_ids = line.tax_ids.invoice_repartition_line_ids.tag_ids._get_related_tax_report_expressions().report_line_id.ids
try:
exempt_report_line = self.env.ref('l10n_ke.tax_report_line_exempt_sales')
except ValueError:
raise UserError(_("Tax exempt report line cannot be found, please update the l10n_ke module."))
letter = 'E' if exempt_report_line.id in report_line_ids else 'C'
uom = line.product_uom_id and line.product_uom_id.name or ''
hscode = re.sub('[^0-9.]+', '', line.product_id.l10n_ke_hsn_code)[:10].ljust(10).encode('cp1251') if letter not in ('A', 'B') else b''.ljust(10)
hsname = self._l10n_ke_fmt(line.product_id.l10n_ke_hsn_name, 20) if letter not in ('A', 'B') else b''.ljust(20)
line_data = b';'.join([
self._l10n_ke_fmt(line.name, 36), # 36 symbols for the article's name
self._l10n_ke_fmt(letter, 1), # 1 symbol for article's vat class ('A', 'B', 'C', 'D', or 'E')
self._l10n_ke_fmt(line.name, 36), # 36 symbols for the article's name
self._l10n_ke_fmt(item_code.tax_rate or 'A', 1), # 1 symbol for article's vat class ('A', 'B', 'C', 'D', or 'E')
price[:15].encode('cp1251'), # 1 to 15 symbols for article's price with up to 5 digits after decimal point
self._l10n_ke_fmt(uom, 3), # 3 symbols for unit of measure
hscode, # 10 symbols for HS code in the format xxxx.xx.xx (can be empty)
hsname, # 20 symbols for the HS name (can be empty)
str(percentage).encode('cp1251')[:5] # up to 5 symbols for vat rate
self._l10n_ke_fmt(uom, 3), # 3 symbols for unit of measure
(item_code.code or '').ljust(10).encode('cp1251'), # 10 symbols for KRA item code in the format xxxx.xx.xx (can be empty)
self._l10n_ke_fmt(item_code.description or '', 20), # 20 symbols for KRA item code description (can be empty)
str(percentage).encode('cp1251')[:5] # up to 5 symbols for vat rate
])
# 1 to 10 symbols for quantity
line_data += b'*' + str(abs(line.quantity)).encode('cp1251')[:10]
@ -235,41 +246,48 @@ class AccountMove(models.Model):
def l10n_ke_action_cu_post(self):
""" Returns the client action descriptor dictionary for sending the
invoice(s) to the fiscal device.
invoice(s) to the control unit (the fiscal device).
"""
# If l10n_ke_edi_oscu is configured for the company, disable sending via TREMOL.
if self.company_id.l10n_ke_oscu_is_active:
raise UserError(
_('An OSCU has been initialized for this company. Please send the e-invoice via Send and Print -> Send to eTIMS instead.')
)
# Check the configuration of the invoice
errors = self._l10n_ke_validate_move()
if errors:
error_msg = ""
for move, error_list in errors:
error_list = '\n'.join(error_list)
error_msg += _("Invalid invoice configuration on %s:\n%s\n\n", move, error_list)
error_msg += _("Invalid invoice configuration on %(invoice)s:\n%(error_list)s\n\n", invoice=move, error_list=error_list)
raise UserError(error_msg)
return {
'type': 'ir.actions.client',
'tag': 'post_send',
'params': {
'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
}
}
'tag': 'l10n_ke_post_send',
'params': [
{
'move_id': 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,
'name': move.name,
} for move in self
]
}
def l10n_ke_cu_response(self, response):
def l10n_ke_cu_responses(self, responses):
""" 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.
client action. The fields are prefixed with l10n_ke_cu_*, which refers
to the fact that they originate from the control unit.
"""
move = self.browse(int(response['move_id']))
replies = [msg for msg in response['replies']]
move.update({
'l10n_ke_cu_serial_number': response['serial_number'],
'l10n_ke_cu_invoice_number': replies[-2].split(';')[0],
'l10n_ke_cu_qrcode': replies[-2].split(';')[1].strip(),
'l10n_ke_cu_datetime': datetime.strptime(replies[-1], '%d-%m-%Y %H:%M'),
})
for response in responses:
move = self.browse(int(response['move_id']))
replies = [msg for msg in response['replies']]
move.update({
'l10n_ke_cu_serial_number': response['serial_number'],
'l10n_ke_cu_invoice_number': replies[-2].split(';')[0],
'l10n_ke_cu_qrcode': replies[-2].split(';')[1].strip(),
'l10n_ke_cu_datetime': datetime.strptime(replies[-1], '%d-%m-%Y %H:%M'),
})

View file

@ -0,0 +1,45 @@
from odoo import _, models, api
class AccountMoveSend(models.AbstractModel):
_inherit = 'account.move.send'
@api.model
def _get_l10n_ke_edi_tremol_warning_moves(self, moves):
return moves.filtered(lambda m: m.country_code == 'KE' and not m._l10n_ke_fiscal_device_details_filled())
@api.model
def _get_l10n_ke_edi_tremol_warning_message(self, warning_moves):
return '\n'.join([
_("The following documents have no details related to the fiscal device."),
*(warning_moves.mapped('name'))
])
# -------------------------------------------------------------------------
# ALERTS
# -------------------------------------------------------------------------
def _get_alerts(self, moves, moves_data):
# EXTENDS 'account'
alerts = super()._get_alerts(moves, moves_data)
if warning_moves := self._get_l10n_ke_edi_tremol_warning_moves(moves):
alerts['l10n_ke_edi_tremol_warning_moves'] = {
'message': self._get_l10n_ke_edi_tremol_warning_message(warning_moves),
'action_text': _("View Invoice(s)"),
'action': warning_moves._get_records_action(name=_("Check Invoice(s)")),
}
return alerts
# -------------------------------------------------------------------------
# SENDING METHODS
# -------------------------------------------------------------------------
def _hook_invoice_document_before_pdf_report_render(self, invoice, invoice_data):
# EXTENDS account
super()._hook_invoice_document_before_pdf_report_render(invoice, invoice_data)
if invoice.country_code == 'KE' and not invoice._l10n_ke_fiscal_device_details_filled():
invoice_data['error'] = {
'error_title': _(
"This document does not have details related to the fiscal device, a proforma invoice will be used."
)
}

View file

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class ProductTemplate(models.Model):
_inherit = "product.template"
l10n_ke_hsn_code = fields.Char(
string='KRA Item Code',
help='Product code needed when not 16% VAT rated. ',
)
l10n_ke_hsn_name = fields.Char(
string='KRA Item Description',
help='Product code description needed when not 16% VAT rated. ',
)
class ProductProduct(models.Model):
_inherit = "product.product"
l10n_ke_hsn_code = fields.Char(
string='HSN code',
related='product_tmpl_id.l10n_ke_hsn_code',
help="Product code needed in case of not 16%. ",
readonly=False,
)
l10n_ke_hsn_name = fields.Char(
string='HSN description',
related='product_tmpl_id.l10n_ke_hsn_name',
help="Product code description needed in case of not 16%. ",
readonly=False,
)

View file

@ -0,0 +1,20 @@
import { registry } from "@web/core/registry";
import { KEProxyDialog } from "./ke_proxy_dialog";
export function KESendInvoiceClientAction(env, action) {
return new Promise((resolve) => {
env.services.dialog.add(
KEProxyDialog,
{
invoices: action.params,
},
{
onClose: () => {
resolve({ type: "ir.actions.act_window_close" });
},
}
);
});
}
registry.category("actions").add("l10n_ke_post_send", KESendInvoiceClientAction);

View file

@ -0,0 +1,24 @@
import { Dialog } from "@web/core/dialog/dialog";
import { useService } from "@web/core/utils/hooks";
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
import { useKEProxy } from "./ke_proxy_hook";
import { Component } from "@odoo/owl";
export class KEProxyDialog extends Component {
static template = "l10n_ke_edi_tremol.KEProxyDialog";
static components = { Dialog };
static props = {
invoices: Object,
close: Function,
};
setup() {
// prevent the escape key from exiting the dialog
useHotkey("escape", () => {});
this.action = useService("action");
this.sender = useKEProxy({ onAllSent: this.props.close });
this.state = this.sender.state;
this.sender.postInvoices(this.props.invoices);
};
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="l10n_ke_edi_tremol.KEProxyDialog">
<Dialog>
<t t-set-slot="header">
<h4 class="modal-title text-break">
Sending Invoices to Fiscal Device
</h4>
</t>
<t t-set-slot="default">
<div t-att-class="{'alert alert-danger': state.error}">
<p t-esc="state.message"/>
<t t-if="!state.error">
<div t-attf-class="o_progressbar align-items-center w-100 d-flex">
<div class="o_progress w-100 flex-fill"
aria-valuemin="0"
t-att-aria-valuemax="props.invoices.length"
t-att-aria-valuenow="state.successfullySent">
<div class="bg-primary h-100"
t-att-style="'width: min(' + 100 * state.successfullySent / props.invoices.length + '%, 100%)'"/>
</div>
<div class="o_progressbar_value flex-shrink-0 ms-2"
t-esc="state.successfullySent + ' / ' + props.invoices.length"/>
</div>
</t>
</div>
</t>
<t t-set-slot="footer">
<button t-if="state.error"
class="btn btn-primary o-default-button"
t-on-click="props.close">
OK
</button>
</t>
</Dialog>
</t>
</templates>

View file

@ -0,0 +1,83 @@
import { useState } from "@odoo/owl";
import { rpc } from "@web/core/network/rpc";
import { useService } from "@web/core/utils/hooks";
import { _t } from "@web/core/l10n/translation";
export function useKEProxy({onAllSent}) {
onAllSent = onAllSent || (() => {});
const state = useState({
successfullySent: 0,
error: false,
message: "",
});
const orm = useService("orm");
const http = useService("http");
/**
* Send each of the invoices provided to the proxy connected to the fiscal device. The proxy should return
* details, from each successfully posted invoice, which are propagated back to the account move using the orm
* service. Alternatively, the proxy can return an error, in which case the execution of this function should
* halt, though the successfully posted invoices are still updated as above.
*
* @param Array<Object> invoices array representing serialised invoice data and the proxy address to send each invoice to.
*/
async function postInvoices(invoices) {
// Ping the server to prevent posting to the device when there is no connection to the odoo server
try {
await rpc("/web/webclient/version_info", {});
} catch (e) {
// Allow the default error handler to execute after displaying an error message in the dialog
state.message = _t("Connection lost, please try again later.");
state.error = true;
throw e;
}
let progress; // keep track of when an error occurs
for (const index in invoices) {
let { move_id, messages, proxy_address, company_vat, name } = invoices[index];
state.message = _t("Posting invoice: %s", name);
try {
progress = 'postToDevice';
let deviceResponse = await http.post(
proxy_address + '/hw_proxy/l10n_ke_cu_send', {
messages: messages,
company_vat: company_vat,
},
);
progress = 'parseResponse';
if (deviceResponse.status === "ok") {
progress = 'updateInvoice'
deviceResponse.move_id = move_id;
await orm.call("account.move", "l10n_ke_cu_responses", [[], [deviceResponse]]);
state.successfullySent++;
} else {
throw new Error(deviceResponse.status)
}
} catch (e) {
state.error = true;
switch (progress) {
case 'postToDevice':
state.message = _t("Error trying to connect to the middleware. Is the middleware running? \n Error message: %s", e.message);
break;
case 'parseResponse':
state.message = _t("Posting the invoice %s has failed with the message: \n %s", name, e.message);
break;
case 'updateInvoice':
state.message = _t("Error trying to connect to Odoo. Check your internet connection. Error message: %s", e.message);
break;
default:
state.message = _t("Unexpected Error:\n %s", e.message);
}
break;
}
}
if (state.successfullySent == invoices.length) {
onAllSent();
}
}
return {
postInvoices,
state,
};
}

View file

@ -1,49 +0,0 @@
odoo.define('l10n_ke_edi_tremol.action_post_send_invoice', function (require) {
const core = require('web.core');
const ajax = require('web.ajax');
const Dialog = require('web.Dialog');
var rpc = require('web.rpc');
var _t = core._t;
async function post_send(parent, {params}) {
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"));
break;
}
} else {
Dialog.alert(this, _t("Posting an invoice has failed, with the message: \n") + res_obj.status);
break;
}
} catch(_e) {
Dialog.alert(this, _t("Error trying to connect to the middleware. Is the middleware running?"));
break;
}
}
if (refresh) {
parent.services.action.doAction({
'type': 'ir.actions.client',
'tag': 'reload',
});
}
}
core.action_registry.add('post_send', post_send);
return post_send;
});

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from .import test_move_export
from . import test_account_move_send
from . import test_move_export

View file

@ -0,0 +1,50 @@
from datetime import timedelta
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.addons.account.tests.test_account_move_send import TestAccountMoveSendCommon
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestKEAccountMoveSend(TestAccountMoveSendCommon):
@classmethod
@TestAccountMoveSendCommon.setup_country('ke')
def setUpClass(cls):
super().setUpClass()
def test_sent_to_fiscal_device(self):
invoice = self.init_invoice("out_invoice", amounts=[1000], post=True, partner=self.partner_a)
# Write data as if the invoice was successfully sent to fiscal device
invoice.write({
'l10n_ke_cu_invoice_number': 'test_ke_invoice_number',
'l10n_ke_cu_serial_number': 'test_ke_serial_number',
'l10n_ke_cu_qrcode': 'test_ke_qrcode',
'l10n_ke_cu_datetime': fields.Datetime.now() - timedelta(days=1),
})
wizard = self.create_send_and_print(invoice)
self.assertFalse(wizard.alerts)
wizard.action_send_and_print()
self.assertTrue(invoice.invoice_pdf_report_id)
def test_not_sent_to_fiscal_device_but_allow_fallback(self):
invoice = self.init_invoice("out_invoice", amounts=[1000], post=True, partner=self.partner_a)
wizard = self.create_send_and_print(invoice)
self.assertTrue('l10n_ke_edi_tremol_warning_moves' in wizard.alerts)
wizard.action_send_and_print(allow_fallback_pdf=True)
# The PDF is not generated but a proforma.
self.assertFalse(invoice.invoice_pdf_report_id)
self.assertTrue(self.env['ir.attachment'].search([
('name', '=', invoice._get_invoice_proforma_pdf_report_filename()),
]))
def test_not_sent_to_fiscal_device_raises(self):
invoice = self.init_invoice("out_invoice", amounts=[1000], post=True, partner=self.partner_a)
wizard = self.create_send_and_print(invoice)
self.assertTrue('l10n_ke_edi_tremol_warning_moves' in wizard.alerts)
with self.assertRaisesRegex(UserError, wizard._get_l10n_ke_edi_tremol_warning_message(invoice)):
wizard.action_send_and_print()

View file

@ -9,8 +9,9 @@ from freezegun import freeze_time
class TestKeMoveExport(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_ke.l10nke_chart_template'):
super().setUpClass(chart_template_ref=chart_template_ref)
@AccountTestInvoicingCommon.setup_country('ke')
def setUpClass(cls):
super().setUpClass()
cls.partner_a.write({
'name': 'Sirius Cybernetics Corporation',
@ -24,14 +25,13 @@ class TestKeMoveExport(AccountTestInvoicingCommon):
cls.product_a.write({
'name': 'Infinite Improbability Drive',
'l10n_ke_hsn_code': '0039.11.53',
'l10n_ke_hsn_name': 'Spacecraft including satellites and suborbital and spacecraft launch vehicles'
})
cls.standard_rate_tax = cls.env['account.tax'].create({
'name': '16% tax',
'amount': 16.0,
cls.spaceship_tax = cls.env['account.tax'].create({
'name': 'Exempt Spaceship tax',
'amount': 0,
'amount_type': 'percent',
'l10n_ke_item_code_id': cls.env.ref('l10n_ke.item_code_2023_00391153').id,
})
@classmethod
@ -66,7 +66,7 @@ class TestKeMoveExport(AccountTestInvoicingCommon):
'product_id': self.product_a.id,
'quantity': 10,
'price_unit': 1234.56,
'tax_ids': [(6, 0, [self.company_data['company'].account_sale_tax_id.id])],
'tax_ids': [(6, 0, [self.spaceship_tax.id])],
'discount': 25,
}),
],
@ -75,9 +75,13 @@ class TestKeMoveExport(AccountTestInvoicingCommon):
generated_messages = simple_invoice._l10n_ke_get_cu_messages()
expected_sale_line = self.line_dict_to_bytes({
'name': b'Infinite Improbability Drive ',
'price': b'1432.09', # This is the unit price, tax included
'price': b'1234.56', # This is the unit price, (though this is tax exempt)
'item_code': b'0039.11.53',
'item_desc': b'Spacecraft including',
'quantity': b'10.0',
'discount': b'-25.0%',
'vat_rate': b'0.0',
'vat_class': b'E',
})
expected_messages = [
# open invoice

View file

@ -8,17 +8,18 @@
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_post']" position="after">
<field name="l10n_ke_cu_qrcode" invisible="1"/>
<field name="l10n_ke_cu_show_send_button" invisible="1"/>
<button name="l10n_ke_action_cu_post" type="object"
class="oe_highlight"
groups="account.group_account_manager"
string="Send To Fiscal Device"
attrs="{'invisible': ['|', '|', '|', ('country_code', '!=', 'KE'), ('l10n_ke_cu_qrcode', '!=', False), ('state', '!=', 'posted'), ('move_type', 'not in', ['out_invoice', 'out_refund'])]}"/>
invisible="not l10n_ke_cu_show_send_button"/>
</xpath>
<xpath expr="//group[@id='header_right_group']" position="inside">
<field name="l10n_ke_cu_invoice_number" attrs="{'invisible': [('country_code', '!=', 'KE')]}" readonly="1"/>
<field name="l10n_ke_cu_invoice_number" readonly="1" invisible="country_code != 'KE'"/>
</xpath>
<notebook position="inside">
<page string="Tremol GO3 Fiscal Device" attrs="{'invisible': [('country_code', '!=', 'KE')]}">
<page string="Tremol GO3 Fiscal Device" name="page_tremol_go3_fiscal_device" invisible="country_code != 'KE'">
<group>
<group>
<field name="l10n_ke_cu_qrcode" widget="url" readonly="1"/>
@ -32,11 +33,11 @@
</record>
<record id="l10n_ke_inherit_account_move_tree_view" model="ir.ui.view">
<field name="name">l10n.ke.inherit.account.move.tree</field>
<field name="name">l10n.ke.inherit.account.move.list</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_out_invoice_tree" />
<field name="arch" type="xml">
<field name="state" position="after">
<field name="status_in_payment" position="after">
<field name="l10n_ke_cu_invoice_number" optional="hide"/>
</field>
</field>
@ -46,7 +47,7 @@
<field name="name">Send 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="binding_view_types">list,kanban</field>
<field name="state">code</field>
<field name="code">
action = records.l10n_ke_action_cu_post()
@ -69,5 +70,3 @@
</record>
</data>
</odoo>

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="l10n_ke_inherit_product_template_form_view" model="ir.ui.view">
<field name="name">l10n.ke.inherit.product.template.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="account.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='invoicing']//group[@name='accounting']" position="inside">
<group name="HS Code" string="HS Code" attrs="{'invisible': [('product_variant_count', '>', 1), ('is_product_variant', '=', False)]}">
<field name="l10n_ke_hsn_code"/>
<field name="l10n_ke_hsn_name"/>
</group>
</xpath>
</field>
</record>
<record id="l10n_ke_inherit_product_product_form_view" model="ir.ui.view">
<field name="name">l10n.ke.inherit.product.product.form</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_variant_easy_edit_view"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<group>
<group name="KRA Item Code" string="KRA Item Code">
<field name="l10n_ke_hsn_code"/>
<field name="l10n_ke_hsn_name"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View file

@ -2,10 +2,10 @@
<odoo>
<template id="l10n_ke_invoice" inherit_id="account.report_invoice_document">
<xpath expr="//div[@id='qrcode']" position="before">
<div t-if="o.country_code == 'KE'" id="l10n_ke_control_unit_information" style="page-break-inside:avoid;">
<div t-if="o.country_code == 'KE' and o.l10n_ke_cu_invoice_number" id="l10n_ke_control_unit_information" style="page-break-inside:avoid;">
<b>Kenyan Fiscal Device Info</b>
<div class="row mt-4 mb-4">
<div class="col-auto col-3 mw-100 mb-2">
<div class="col">
<p>
<b>Invoice Number: </b><br></br>
<span t-field="o.l10n_ke_cu_invoice_number"/>
@ -19,10 +19,10 @@
<span t-field="o.l10n_ke_cu_datetime"/>
</p>
</div>
<div class="col-auto col-3 mw-100 mb-2">
<div class="col">
<p t-if="o.l10n_ke_cu_qrcode">
<strong class="text-center">TIMS URL</strong><br/><br/>
<img style="display:block;" t-att-src="'/report/barcode/?barcode_type=%s&amp;value=%s&amp;width=%s&amp;height=%s' % ('QR', quote_plus(o.l10n_ke_cu_qrcode), 130, 130)" alt="QR Code"/>
<img style="display:block;" t-att-src="'/report/barcode/?barcode_type=%s&amp;value=%s&amp;width=%s&amp;height=%s&amp;quiet=%s' % ('QR', quote_plus(o.l10n_ke_cu_qrcode), 130, 130, 0)" alt="QR Code"/>
</p>
</div>
</div>

View file

@ -5,27 +5,21 @@
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='account_vendor_bills']" position="after">
<div attrs="{'invisible':[('country_code', '!=', 'KE')]}">
<h2>Kenya TIMS Integration</h2>
<div class="row mt16 o_settings_container" id="l10n_ke_cu_details">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_right_pane">
<span class="o_form_label">Tremol Device Settings</span>
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." aria-label="Values set here are company-specific." groups="base.group_multi_company" role="img"/>
<div class="text-muted">
The tremol device makes use of a proxy server, which can be running locally on your computer or on an IoT Box.
The proxy server must be on the same network as the fiscal device.
</div>
<xpath expr="//block[@id='account_vendor_bills']" position="after">
<div invisible="country_code != 'KE'">
<block title="Kenya TIMS Integration" id="l10n_ke_cu_details">
<setting
string="Tremol Device Settings"
help="The tremol device makes use of a proxy server, which can be running locally on your computer or on an IoT Box. The proxy server must be on the same network as the fiscal device."
company_dependent="1">
<div class="content-group">
<div class="row mt8">
<label for="l10n_ke_cu_proxy_address" class="col-lg-5 o_light_label"/>
<field name="l10n_ke_cu_proxy_address"/>
</div>
</div>
</div>
</div>
</div>
</setting>
</block>
</div>
</xpath>
</field>

View file

@ -5,7 +5,7 @@
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form"/>
<field name="arch" type="xml">
<group name="accounting_entries" position="after">
<group name="general" position="after">
<group string="Kenya Accounting Details" name="l10n_ke_details">
<field name="l10n_ke_exemption_number"/>
</group>

View file

@ -0,0 +1 @@
from . import account_move_send_wizard

View file

@ -0,0 +1,14 @@
from odoo import models
from odoo.exceptions import UserError
class AccountMoveSendWizard(models.TransientModel):
_inherit = 'account.move.send.wizard'
def action_send_and_print(self, allow_fallback_pdf=False):
# EXTENDS account - prevent Send & Print if KE invoices aren't validated and no fallback is allowed.
self.ensure_one()
if not allow_fallback_pdf:
if warning_moves := self._get_l10n_ke_edi_tremol_warning_moves(self.move_id):
raise UserError(self._get_l10n_ke_edi_tremol_warning_message(warning_moves))
return super().action_send_and_print(allow_fallback_pdf=allow_fallback_pdf)

View file

@ -2,13 +2,13 @@
name = "odoo-bringout-oca-ocb-l10n_ke_edi_tremol"
version = "16.0.0"
description = "Kenya Tremol Device EDI Integration -
Kenya Tremol Device EDI Integration
Kenya Tremol Device EDI Integration
"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-l10n_ke>=16.0.0",
"odoo-bringout-oca-ocb-l10n_ke>=19.0.0",
"requests>=2.25.1"
]
readme = "README.md"
@ -18,7 +18,7 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]