diff --git a/addons/hw_drivers/iot_handlers/drivers/L10nKeEDISerialDriver.py b/addons/hw_drivers/iot_handlers/drivers/L10nKeEDISerialDriver.py
new file mode 100644
index 0000000000000000000000000000000000000000..6214e6b9b3795bbdcd17959f3bc165666c0cbc73
--- /dev/null
+++ b/addons/hw_drivers/iot_handlers/drivers/L10nKeEDISerialDriver.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+import serial
+import time
+import struct
+import json
+from functools import reduce
+
+from odoo import http
+from odoo.addons.hw_drivers.iot_handlers.drivers.SerialBaseDriver import SerialDriver, SerialProtocol, serial_connection
+from odoo.addons.hw_drivers.main import iot_devices
+
+_logger = logging.getLogger(__name__)
+
+TremolG03Protocol = SerialProtocol(
+    name='Tremol G03',
+    baudrate=115200,
+    bytesize=serial.EIGHTBITS,
+    stopbits=serial.STOPBITS_ONE,
+    parity=serial.PARITY_NONE,
+    timeout=3,
+    writeTimeout=0.2,
+    measureRegexp=None,
+    statusRegexp=None,
+    commandTerminator=b'',
+    commandDelay=0.4,
+    measureDelay=0.4,
+    newMeasureDelay=0.2,
+    measureCommand=b'',
+    emptyAnswerValid=False,
+)
+
+STX = 0x02
+ETX = 0x0A
+ACK = 0x06
+NACK = 0x15
+
+FD_ERRORS = {
+    0x30: 'OK',
+    0x32: 'Registers overflow',
+    0x33: 'Clock failure or incorrect date & time',
+    0x34: 'Opened fiscal receipt',
+    0x39: 'Incorrect password',
+    0x3b: '24 hours block - missing Z report',
+    0x3d: 'Interrupt power supply in fiscal receipt (one time until status is read)',
+    0x3e: 'Overflow EJ',
+    0x3f: 'Insufficient conditions',
+}
+
+COMMAND_ERRORS = {
+    0x30: 'OK',
+    0x31: 'Invalid command',
+    0x32: 'Illegal command',
+    0x33: 'Z daily report is not zero',
+    0x34: 'Syntax error',
+    0x35: 'Input registers orverflow',
+    0x36: 'Zero input registers',
+    0x37: 'Unavailable transaction for correction',
+    0x38: 'Insufficient amount on hand',
+}
+
+
+class TremolG03Driver(SerialDriver):
+    """Driver for the Kenyan Tremol G03 fiscal device."""
+
+    _protocol = TremolG03Protocol
+
+    def __init__(self, identifier, device):
+        super().__init__(identifier, device)
+        self.device_type = 'fiscal_data_module'
+        self.message_number = 0
+
+    @classmethod
+    def get_default_device(cls):
+        fiscal_devices = list(filter(lambda d: iot_devices[d].device_type == 'fiscal_data_module', iot_devices))
+        return len(fiscal_devices) and iot_devices[fiscal_devices[0]]
+
+    @classmethod
+    def supported(cls, device):
+        """Checks whether the device, which port info is passed as argument, is supported by the driver.
+
+        :param device: path to the device
+        :type device: str
+        :return: whether the device is supported by the driver
+        :rtype: bool
+        """
+        protocol = cls._protocol
+        try:
+            protocol = cls._protocol
+            with serial_connection(device['identifier'], protocol) as connection:
+                connection.write(b'\x09')
+                time.sleep(protocol.commandDelay)
+                response = connection.read(1)
+                if response == b'\x40':
+                    return True
+
+        except serial.serialutil.SerialTimeoutException:
+            pass
+        except Exception:
+            _logger.exception('Error while probing %s with protocol %s', device, protocol.name)
+
+    # ----------------
+    # HELPERS
+    # ----------------
+
+    @staticmethod
+    def generate_checksum(message):
+        """ Generate the checksum bytes for the bytes provided.
+
+        :param message: bytes representing the part of the message from which the checksum is calculated
+        :returns:       two checksum bytes calculated from the message
+
+         This checksum is calculated as:
+        1) XOR of all bytes of the bytes
+        2) Conversion of the one XOR byte into the two bytes of the checksum by
+           adding 30h to each half-byte of the XOR
+
+        eg. to_check = \x12\x23\x34\x45\x56
+            XOR of all bytes in to_check = \x16
+            checksum generated as \x16 -> \x31 \x36
+        """
+        xor = reduce(lambda a, b: a ^ b, message)
+        return bytes([(xor >> 4) + 0x30, (xor & 0xf) + 0x30])
+
+    # ----------------
+    # COMMUNICATION
+    # ----------------
+
+    def send(self, msgs):
+        """ Send and receive messages to/from the fiscal device over serial connection
+
+        Generate the wrapped message from the msgs and send them to the device.
+        The wrapping contains the <STX> (starting byte) <LEN> (length byte)
+        and <NBL> (message number byte) at the start and two <CS> (checksum
+        bytes), and the <ETX> line-feed byte at the end.
+        :param msgs: A list of byte strings representing the <CMD> and <DATA>
+                     components of the serial message.
+        :return:     A list of the responses (if any) from the device. If the
+                     response is an ack, it wont be part of this list.
+        """
+
+        with self._device_lock:
+            replies = []
+            for msg in msgs:
+                self.message_number += 1
+                core_message = struct.pack('BB%ds' % (len(msg)), len(msg) + 34, self.message_number + 32, msg)
+                request = struct.pack('B%ds2sB' % (len(core_message)), STX, core_message, self.generate_checksum(core_message), ETX)
+                time.sleep(self._protocol.commandDelay)
+                self._connection.write(request)
+                time.sleep(self._protocol.measureDelay)
+                response = self._connection.read_all()
+                if not response:
+                    self.data['status'] = "no response"
+                    _logger.error("Sent request: %s,\n Received no response", request)
+                    self.abort_post()
+                    break
+                if response[0] == ACK:
+                    # In the case where either byte is not 0x30, there has been an error
+                    if response[2] != 0x30 or response[3] != 0x30:
+                        self.data['status'] = response[2:4].decode('cp1251')
+                        _logger.error(
+                            "Sent request: %s,\n Received fiscal device error: %s \n Received command error: %s",
+                            request, FD_ERRORS.get(response[2], 'Unknown fiscal device error'),
+                            COMMAND_ERRORS.get(response[3], 'Unknown command error'),
+                        )
+                        self.abort_post()
+                        break
+                    replies.append('')
+                elif response[0] == NACK:
+                    self.data['status'] = "Received NACK"
+                    _logger.error("Sent request: %s,\n Received NACK \x15", request)
+                    self.abort_post()
+                    break
+                elif response[0] == 0x02:
+                    self.data['status'] = "ok"
+                    size = response[1] - 35
+                    reply = response[4:4 + size]
+                    replies.append(reply.decode('cp1251'))
+        return {'replies': replies, 'status': self.data['status']}
+
+    def abort_post(self):
+        """ Cancel the posting of the invoice
+
+        In the event of an error, it is better to try to cancel the posting of
+        the invoice, since the state of the invoice on the device will remain
+        open otherwise, blocking further invoices being sent.
+        """
+        self.message_number += 1
+        abort = struct.pack('BBB', 37, self.message_number + 32, 0x39)
+        request = struct.pack('B3s2sB', STX, abort, self.generate_checksum(abort), ETX)
+        time.sleep(self._protocol.commandDelay)
+        self._connection.write(request)
+        time.sleep(self._protocol.measureDelay)
+        response = self._connection.read_all()
+        if response and response[0] == 0x02:
+            self.data['status'] += "\n The invoice could not be cancelled."
+        else:
+            self.data['status'] += "\n The invoice was successfully cancelled"
+
+
+class TremolG03Controller(http.Controller):
+
+    @http.route('/hw_proxy/l10n_ke_cu_send', type='http', auth='none', cors='*', csrf=False, save_session=False, methods=['POST'])
+    def l10n_ke_cu_send(self, messages, company_vat):
+        """ Posts the messages sent to this endpoint to the fiscal device connected to the server
+
+        :param messages:     The messages (consisting of <CMD> and <DATA>) to
+                             send to the fiscal device.
+        :returns:            Dictionary containing a list of the responses from
+                             fiscal device and status of the fiscal device.
+        """
+        device = TremolG03Driver.get_default_device()
+        if device:
+            # First run the command to get the fiscal device numbers
+            device_numbers = device.send([b'\x60'])
+            # If the vat doesn't match, abort
+            if device_numbers['status'] != 'ok':
+                return device_numbers
+            serial_number, device_vat, _dummy = device_numbers['replies'][0].split(';')
+            if device_vat != company_vat:
+                return json.dumps({'status': 'The company vat number does not match that of the device'})
+            messages = json.loads(messages)
+            resp = json.dumps({**device.send([msg.encode('cp1251') for msg in messages]), 'serial_number': serial_number})
+            return resp
+        else:
+            return json.dumps({'status': 'The fiscal device is not connected to the proxy server'})
diff --git a/addons/l10n_ke/__manifest__.py b/addons/l10n_ke/__manifest__.py
index 03f11c74ad2d23690374ef12c14cca356aa72543..3484373977d62b5b7e5d178169a89ffbda6dfe40 100644
--- a/addons/l10n_ke/__manifest__.py
+++ b/addons/l10n_ke/__manifest__.py
@@ -17,6 +17,7 @@ This provides a base chart of accounts and taxes template for use in Odoo.
         'data/account.account.template.csv',
         'data/l10n_ke_chart_data.xml',
         'data/account_tax_group_data.xml',
+        'data/account_tax_report_data.xml',
         'data/account_tax_template_data.xml',
         'data/account_fiscal_position_template.xml',
         'data/account_chart_template_configure_data.xml',
diff --git a/addons/l10n_ke/data/account_tax_report_data.xml b/addons/l10n_ke/data/account_tax_report_data.xml
new file mode 100644
index 0000000000000000000000000000000000000000..94683c37cb19acfd6951e63ccef8854adb3e2170
--- /dev/null
+++ b/addons/l10n_ke/data/account_tax_report_data.xml
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <record id="tax_report_ke" model="account.report">
+        <field name="name">Tax Report</field>
+        <field name="root_report_id" ref="account.generic_tax_report"/>
+        <field name="country_id" ref="base.ke"/>
+        <field name="filter_fiscal_position" eval="True"/>
+        <field name="availability_condition">country</field>
+        <field name="column_ids">
+            <record id="tax_report_base_column" model="account.report.column">
+                <field name="name">Base</field>
+                <field name="expression_label">base</field>
+            </record>
+            <record id="tax_report_tax_column" model="account.report.column">
+                <field name="name">VAT</field>
+                <field name="expression_label">tax</field>
+            </record>
+        </field>
+        <field name="line_ids">
+            <record id="tax_report_line_general_rate_sales" model="account.report.line">
+                <field name="name">1. Taxable Sales (General Rate 16%)</field>
+                <field name="sequence">1</field>
+                <field name="code">box_1</field>
+                <field name="expression_ids">
+                        <record id="tax_report_general_rate_sales_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">16% Sales Base</field>
+                    </record>
+                    <record id="tax_report_general_rate_sales_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">16% Sales Tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_other_rate_sales" model="account.report.line">
+                <field name="name">2. Taxable Sales (Other Rate 8%)</field>
+                <field name="sequence">2</field>
+                <field name="code">box_2</field>
+                <field name="expression_ids">
+                        <record id="tax_report_other_rate_sales_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">8% Sales Base</field>
+                    </record>
+                    <record id="tax_report_other_rate_sales_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">8% Sales Tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_zero_rated_sales" model="account.report.line">
+                <field name="name">3. Sales (Zero Rated 0%)</field>
+                <field name="sequence">3</field>
+                <field name="code">box_3</field>
+                <field name="expression_ids">
+                        <record id="tax_report_zero_rated_sales_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">Zero Rated Sales Base</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_exempt_sales" model="account.report.line">
+                <field name="name">4. Sales (Exempt)</field>
+                <field name="sequence">4</field>
+                <field name="code">box_4</field>
+                <field name="expression_ids">
+                        <record id="tax_report_exempt_sales_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">Exempt Sales Base</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_total_sales" model="account.report.line">
+                <field name="name">5. Total Sales</field>
+                <field name="sequence">5</field>
+                <field name="code">box_5</field>
+                <field name="expression_ids">
+                        <record id="tax_report_total_sales_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_1.base + box_2.base + box_3.base + box_4.base</field>
+                    </record>
+                    <record id="tax_report_total_sales_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_1.tax + box_2.tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_output_vat" model="account.report.line">
+                <field name="name">6. Total Output VAT</field>
+                <field name="sequence">6</field>
+                <field name="code">box_6</field>
+                <field name="expression_ids">
+                        <record id="tax_report_output_vat_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_1.base + box_2.base + box_3.base</field>
+                    </record>
+                    <record id="tax_report_output_vat_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_1.tax + box_2.tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_general_rate_purchases" model="account.report.line">
+                <field name="name">7. Taxable Purchases (General Rate 16%)</field>
+                <field name="sequence">7</field>
+                <field name="code">box_7</field>
+                <field name="expression_ids">
+                        <record id="tax_report_general_rate_purchases_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">16% Purchases Base</field>
+                    </record>
+                    <record id="tax_report_general_rate_purchases_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">16% Purchases Tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_other_rate_purchases" model="account.report.line">
+                <field name="name">8. Taxable Purchases (Other Rate 8%)</field>
+                <field name="sequence">8</field>
+                <field name="code">box_8</field>
+                <field name="expression_ids">
+                        <record id="tax_report_other_rate_purchases_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">8% purchases Base</field>
+                    </record>
+                    <record id="tax_report_other_rate_purchases_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">8% Purchases Tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_zero_rated_purchases" model="account.report.line">
+                <field name="name">9. Purchases (Zero Rated 0%)</field>
+                <field name="sequence">9</field>
+                <field name="code">box_9</field>
+                <field name="expression_ids">
+                        <record id="tax_report_zero_rated_purchases_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">Zero Rated Purchases Base</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_exempt_purchases" model="account.report.line">
+                <field name="name">10. Purchases (Exempt)</field>
+                <field name="sequence">10</field>
+                <field name="code">box_10</field>
+                <field name="expression_ids">
+                        <record id="tax_report_exempt_purchases_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">tax_tags</field>
+                        <field name="formula">Exempt Purchases Base</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_total_purchases" model="account.report.line">
+                <field name="name">11. Total Purchases</field>
+                <field name="sequence">11</field>
+                <field name="code">box_11</field>
+                <field name="expression_ids">
+                        <record id="tax_report_total_purchases_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_7.base + box_8.base + box_9.base + box_10.base</field>
+                    </record>
+                    <record id="tax_report_total_purchases_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_7.tax + box_8.tax</field>
+                    </record>
+                </field>
+            </record>
+            <record id="tax_report_line_input_vat" model="account.report.line">
+                <field name="name">12. Total Input VAT</field>
+                <field name="sequence">12</field>
+                <field name="code">box_12</field>
+                <field name="expression_ids">
+                        <record id="tax_report_input_vat_base_tag" model="account.report.expression">
+                        <field name="label">base</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_7.base + box_8.base + box_9.base</field>
+                    </record>
+                    <record id="tax_report_input_vat_tax_tag" model="account.report.expression">
+                        <field name="label">tax</field>
+                        <field name="engine">aggregation</field>
+                        <field name="formula">box_7.tax + box_8.tax</field>
+                    </record>
+                </field>
+            </record>
+        </field>
+    </record>
+</odoo>
diff --git a/addons/l10n_ke/data/account_tax_template_data.xml b/addons/l10n_ke/data/account_tax_template_data.xml
index 7f11bf14a74b920e4836742ca514e82b12403658..12e7ae6be559a51c7112fa0ed7092ed517bf9f80 100644
--- a/addons/l10n_ke/data/account_tax_template_data.xml
+++ b/addons/l10n_ke/data/account_tax_template_data.xml
@@ -9,17 +9,27 @@
         <field name="amount">16</field>
         <field name="tax_group_id" ref="tax_group_16"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_general_rate_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke2200'),
+                    'plus_report_expression_ids': [ref('tax_report_general_rate_sales_tax_tag')],
                 }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_general_rate_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke2200'),
+                    'minus_report_expression_ids': [ref('tax_report_general_rate_sales_tax_tag')],
                 }),
             ]"/>
     </record>
@@ -32,17 +42,27 @@
         <field name="amount">8</field>
         <field name="tax_group_id" ref="tax_group_8"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_other_rate_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke2200'),
+                    'plus_report_expression_ids': [ref('tax_report_other_rate_sales_tax_tag')],
                 }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_other_rate_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke2200'),
+                    'minus_report_expression_ids': [ref('tax_report_other_rate_sales_tax_tag')],
                 }),
             ]"/>
     </record>
@@ -55,12 +75,24 @@
         <field name="amount">0</field>
         <field name="tax_group_id" ref="tax_group_0"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_zero_rated_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_zero_rated_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
     </record>
     <record id="STEX" model="account.tax.template">
@@ -72,12 +104,24 @@
         <field name="amount">0</field>
         <field name="tax_group_id" ref="tax_group_0"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_exempt_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_exempt_sales_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
     </record>
     <record id="SWT3" model="account.tax.template">
@@ -273,17 +317,27 @@
         <field name="amount">16</field>
         <field name="tax_group_id" ref="tax_group_16"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_general_rate_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke1110'),
+                    'plus_report_expression_ids': [ref('tax_report_general_rate_purchases_tax_tag')],
                 }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_general_rate_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke1110'),
+                    'minus_report_expression_ids': [ref('tax_report_general_rate_purchases_tax_tag')],
                 }),
             ]"/>
     </record>
@@ -296,17 +350,27 @@
         <field name="amount">8</field>
         <field name="tax_group_id" ref="tax_group_8"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_other_rate_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke1110'),
+                    'plus_report_expression_ids': [ref('tax_report_other_rate_purchases_tax_tag')],
                 }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
                 (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_other_rate_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
                     'repartition_type': 'tax',
                     'account_id': ref('ke1110'),
+                    'minus_report_expression_ids': [ref('tax_report_other_rate_purchases_tax_tag')],
                 }),
             ]"/>
     </record>
@@ -319,12 +383,24 @@
         <field name="amount">0</field>
         <field name="tax_group_id" ref="tax_group_0"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_zero_rated_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_zero_rated_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
     </record>
     <record id="PTEX" model="account.tax.template">
@@ -336,12 +412,24 @@
         <field name="amount">0</field>
         <field name="tax_group_id" ref="tax_group_0"/>
         <field name="invoice_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'plus_report_expression_ids': [ref('tax_report_exempt_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
             <field name="refund_repartition_line_ids" eval="[(5, 0, 0),
-                (0,0, {'repartition_type': 'base'}),
-                (0,0, {'repartition_type': 'tax'}),
+                (0,0, {
+                    'repartition_type': 'base',
+                    'minus_report_expression_ids': [ref('tax_report_exempt_purchases_base_tag')],
+                }),
+                (0,0, {
+                    'factor_percent': 100,
+                    'repartition_type': 'tax',
+                }),
             ]"/>
     </record>
     <record id="PWT3" model="account.tax.template">
diff --git a/addons/l10n_ke_edi_tremol/__init__.py b/addons/l10n_ke_edi_tremol/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0650744f6bc69b9f0b865e8c7174c813a5f5995e
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/addons/l10n_ke_edi_tremol/__manifest__.py b/addons/l10n_ke_edi_tremol/__manifest__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4adae9254a280c76e0e67fa69d2f4fe76eecdb43
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/__manifest__.py
@@ -0,0 +1,27 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+{
+    'name': "Kenya Tremol Device EDI Integration",
+    'summary': """
+            Kenya Tremol Device EDI Integration
+        """,
+    'description': """
+       This module integrates with the Kenyan G03 Tremol control unit device to the KRA through TIMS.
+    """,
+    'author': 'Odoo',
+    'category': 'Accounting/Localizations/EDI',
+    'version': '1.0',
+    'license': 'LGPL-3',
+    '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',
+        ],
+    },
+}
diff --git a/addons/l10n_ke_edi_tremol/models/__init__.py b/addons/l10n_ke_edi_tremol/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..b090c18797a2cf16ddc714f63510c059c8e817fe
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/models/__init__.py
@@ -0,0 +1,5 @@
+from . import account_move
+from . import product
+from . import res_company
+from . import res_config_settings
+from . import res_partner
diff --git a/addons/l10n_ke_edi_tremol/models/account_move.py b/addons/l10n_ke_edi_tremol/models/account_move.py
new file mode 100644
index 0000000000000000000000000000000000000000..51ac1a5a85e3ce5db95c76f929d05494caa5af94
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/models/account_move.py
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+import logging
+import json
+import re
+from datetime import datetime
+
+from odoo import models, fields, _
+from odoo.exceptions import UserError
+
+_logger = logging.getLogger(__name__)
+
+class AccountMove(models.Model):
+    _inherit = 'account.move'
+
+    l10n_ke_cu_datetime = fields.Datetime(string='CU Signing Date and Time', copy=False)
+    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)
+
+    # -------------------------------------------------------------------------
+    # HELPERS
+    # -------------------------------------------------------------------------
+
+    def _l10n_ke_fmt(self, string, length, ljust=True):
+        """ Function for common formatting behaviour
+
+        :param string: string to be formatted/encoded
+        :param length: integer length to justify (if enabled), and then truncate the string to
+        :param ljust:  boolean representing whether the string should be justified
+        :returns:      byte-string justified/truncated, with all non-alphanumeric characters removed
+        """
+        if not string:
+            string = ''
+        return re.sub('[^A-Za-z0-9 ]+', '', str(string)).encode('cp1251').ljust(length if ljust else 0)[:length]
+
+    # -------------------------------------------------------------------------
+    # CHECKS
+    # -------------------------------------------------------------------------
+
+    def _l10n_ke_validate_move(self):
+        """ Returns list of errors related to misconfigurations
+
+        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 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))
+
+        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))
+
+        return errors
+
+    # -------------------------------------------------------------------------
+    # SERIALISERS
+    # -------------------------------------------------------------------------
+
+    def _l10n_ke_cu_open_invoice_message(self):
+        """ Serialise the required fields for opening an invoice
+
+        :returns: a list containing one byte-string representing the <CMD> and
+                  <DATA> of the message sent to the fiscal device.
+        """
+        headquarter_address = (self.commercial_partner_id.street or '') + (self.commercial_partner_id.street2 or '')
+        customer_address = (self.partner_id.street or '') + (self.partner_id.street2 or '')
+        postcode_and_city = (self.partner_id.zip or '') + '' +  (self.partner_id.city or '')
+        invoice_elements = [
+            b'1',                                                   # Reserved - 1 symbol with value '1'
+            b'     0',                                              # Reserved - 6 symbols with value ‘     0’
+            b'0',                                                   # Reserved - 1 symbol with value '0'
+            b'1' if self.move_type == 'out_invoice' else b'A',      # 1 symbol with value '1' (new invoice), 'A' (credit note), or '@' (debit note)
+            self._l10n_ke_fmt(self.commercial_partner_id.name, 30), # 30 symbols for Company name
+            self._l10n_ke_fmt(self.commercial_partner_id.vat, 14),  # 14 Symbols for the client PIN number
+            self._l10n_ke_fmt(headquarter_address, 30),             # 30 Symbols for customer headquarters
+            self._l10n_ke_fmt(customer_address, 30),                # 30 Symbols for the address
+            self._l10n_ke_fmt(postcode_and_city, 30),               # 30 symbols for the customer post code and city
+            self._l10n_ke_fmt('', 30),                              # 30 symbols for the exemption number
+        ]
+        if self.move_type == 'out_refund':
+            invoice_elements.append(self._l10n_ke_fmt(self.reversed_entry_id.l10n_ke_cu_invoice_number, 19)), # 19 symbols for related invoice number
+        invoice_elements.append(re.sub('[^A-Za-z0-9 ]+', '', self.name)[-15:].ljust(15).encode('cp1251'))     # 15 symbols for trader system invoice number
+
+        # Command: Open fiscal record (0x30)
+        return [b'\x30' + b';'.join(invoice_elements)]
+
+    def _l10n_ke_cu_lines_messages(self):
+        """ Serialise the data of each line on the invoice
+
+        This function transforms the lines in order to handle the differences
+        between the KRA expected data and the lines in odoo.
+
+        If a discount line (as a negative line) has been added to the invoice
+        lines, find a suitable line/lines to distribute the discount accross
+
+        :returns: List of byte-strings representing each command <CMD> and the
+                  <DATA> of the line, which will be sent to the fiscal device
+                  in order to add a line to the opened invoice.
+        """
+        def is_discount_line(line):
+            return line.price_unit < 0.0
+
+        def is_candidate(discount_line, other_line):
+            """ If the of one line match those of the discount line, the discount can be distributed accross that line """
+            discount_taxes = discount_line.tax_ids.flatten_taxes_hierarchy()
+            other_line_taxes = other_line.tax_ids.flatten_taxes_hierarchy()
+            return set(discount_taxes.ids) == set(other_line_taxes.ids)
+
+        lines = self.invoice_line_ids.filtered(lambda l: l.display_type == 'product' and l.quantity and l.price_total)
+        # The device expects all monetary values in Kenyan Shillings
+        if self.currency_id == self.company_id.currency_id:
+            currency_rate = 1
+        else:
+            currency_rate = abs(self.invoice_line_ids[0].balance / self.invoice_line_ids[0].price_subtotal)
+
+        discount_dict = {line.id: line.discount for line in lines if line.price_total > 0}
+        for line in lines:
+            if not is_discount_line(line):
+                continue
+            # Search for non-discount lines
+            candidate_vals_list = [l for l in lines if not is_discount_line(l) and is_candidate(l, line)]
+            candidate_vals_list = sorted(candidate_vals_list, key=lambda x: x.price_unit * x.quantity, reverse=True)
+            line_to_discount = abs(line.price_unit * line.quantity)
+            for candidate in candidate_vals_list:
+                still_to_discount = abs(candidate.price_unit * candidate.quantity * (100.0 - discount_dict[candidate.id]) / 100.0)
+                if line_to_discount >= still_to_discount:
+                    discount_dict[candidate.id] = 100.0
+                    line_to_discount -= still_to_discount
+                else:
+                    rest_to_discount = abs((line_to_discount / (candidate.price_unit * candidate.quantity)) * 100.0)
+                    discount_dict[candidate.id] += rest_to_discount
+                    break
+
+        vat_class = {16.0: 'A', 8.0: 'B'}
+        msgs = []
+        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 = round(line.price_total / line.quantity * 100 / (100 - line.discount), 2) * currency_rate
+            percentage = line.tax_ids[0].amount
+
+            # 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 = re.sub('[^0-9.]+', '', line.product_id.l10n_ke_hsn_name)[:20].ljust(20).encode('cp1251') 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')
+                str(price)[:13].encode('cp1251'),               # 1 to 13 symbols for article's price
+                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
+            ])
+            # 1 to 10 symbols for quantity
+            line_data += b'*' + str(line.quantity).encode('cp1251')[:10]
+            if discount_dict.get(line.id):
+                # 1 to 7 symbols for percentage of discount/addition
+                discount_sign = b'-' if discount_dict[line.id] > 0 else b'+'
+                discount = discount_sign + str(abs(discount_dict[line.id])).encode('cp1251')[:6]
+                line_data += b',' + discount + b'%'
+
+            # Command: Sale of article (0x31)
+            msgs += [b'\x31' + line_data]
+        return msgs
+
+    def _l10n_ke_get_cu_messages(self):
+        self.ensure_one()
+        msgs = self._l10n_ke_cu_open_invoice_message()
+        msgs += self._l10n_ke_cu_lines_messages()
+        # Command: Close fiscal reciept (0x38)
+        msgs += [b'\x38']
+        # Command: Read date and time (0x68)
+        msgs += [b'\x68']
+        return msgs
+
+    # -------------------------------------------------------------------------
+    # POST COMMANDS / RECEIVE DATA
+    # -------------------------------------------------------------------------
+
+    def l10n_ke_action_cu_post(self):
+        self.ensure_one()
+        # 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))
+        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,
+            }
+        }
+
+    def l10n_ke_cu_response(self, response):
+        move = self.browse(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'),
+        })
diff --git a/addons/l10n_ke_edi_tremol/models/product.py b/addons/l10n_ke_edi_tremol/models/product.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a59d27ae83d22b932ad3dedad52c9ed8e18b6ea
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/models/product.py
@@ -0,0 +1,32 @@
+# -*- 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='HSN code',
+        help="Product code needed in case of not 16%. ",
+    )
+    l10n_ke_hsn_name = fields.Char(
+        string='HSN description',
+        help="Product code description needed in case of not 16%. ",
+    )
+
+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,
+    )
diff --git a/addons/l10n_ke_edi_tremol/models/res_company.py b/addons/l10n_ke_edi_tremol/models/res_company.py
new file mode 100644
index 0000000000000000000000000000000000000000..69c502ac18b60ad9dc0ea79bbbe558b5518c3fe6
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/models/res_company.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResCompany(models.Model):
+    _inherit = 'res.company'
+
+    l10n_ke_cu_proxy_address = fields.Char(
+        default="http://localhost:8069",
+        string='Control Unit Proxy Address',
+        help='The address of the proxy server for the control unit.',
+    )
diff --git a/addons/l10n_ke_edi_tremol/models/res_config_settings.py b/addons/l10n_ke_edi_tremol/models/res_config_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..3614cae3e150e51b2502f8657e08edaa52557f1b
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/models/res_config_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResConfigSettings(models.TransientModel):
+    _inherit = 'res.config.settings'
+
+    l10n_ke_cu_proxy_address = fields.Char(related='company_id.l10n_ke_cu_proxy_address', readonly=False)
diff --git a/addons/l10n_ke_edi_tremol/models/res_partner.py b/addons/l10n_ke_edi_tremol/models/res_partner.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a1c9788800c92e5354fb6aa30589b1727bc8440
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/models/res_partner.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import fields, models
+
+
+class ResPartner(models.Model):
+    _inherit = 'res.partner'
+
+    l10n_ke_exemption_number = fields.Char(
+        string='Exemption Number',
+        help='The exemption number of the partner. Provided by the Kenyan government.',
+    )
+
+    def _commercial_fields(self):
+        return super()._commercial_fields() + ['l10n_ke_exemption_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
new file mode 100644
index 0000000000000000000000000000000000000000..8d88d67ac260f032e1f35ca1d7779a33d3b80a45
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/static/src/js/send_invoice.js
@@ -0,0 +1,34 @@
+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}) {
+        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"));
+                })
+            }
+        }, function () {
+            Dialog.alert(this, _t("Error trying to connect to the middleware. Is the middleware running?"));
+        })
+    }
+    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
new file mode 100644
index 0000000000000000000000000000000000000000..7c4ae0096b2c2cf8a6fa98708fa3cee901c4e3fe
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/views/account_move_view.xml
@@ -0,0 +1,58 @@
+<odoo>
+    <data>
+        <record id="l10n_ke_inherit_account_move_form" model="ir.ui.view">
+            <field name="name">l10n.ke.inherit.account.move.form</field>
+            <field name="model">account.move</field>
+            <field name="inherit_id" ref="account.view_move_form"/>
+            <field name="priority" eval="40"/>
+            <field name="arch" type="xml">
+                <xpath expr="//header/button[@name='action_post']" position="after">
+                    <field name="l10n_ke_cu_qrcode" invisible="1"/>
+                    <button name="l10n_ke_action_cu_post" type="object"
+                            class="oe_highlight"
+                            groups="account.group_account_manager"
+                            string="Send Invoice To Device"
+                            attrs="{'invisible': ['|', '|', '|', ('country_code', '!=', 'KE'), ('l10n_ke_cu_qrcode', '!=', False), ('state', '!=', 'posted'), ('move_type', 'not in', ['out_invoice', 'out_refund'])]}"/>
+                </xpath>
+                <xpath expr="//group[@id='header_right_group']" position="inside">
+                    <field name="l10n_ke_cu_invoice_number" attrs="{'invisible': [('country_code', '!=', 'KE')]}" readonly="1"/>
+                </xpath>
+                <notebook position="inside">
+                    <page string="Tremol GO3 Control Unit" attrs="{'invisible': [('country_code', '!=', 'KE')]}">
+                        <group>
+                            <group>
+                                <field name="l10n_ke_cu_qrcode" widget="url" readonly="1"/>
+                                <field name="l10n_ke_cu_serial_number" readonly="1"/>
+                                <field name="l10n_ke_cu_datetime" readonly="1"/>
+                            </group>
+                        </group>
+                    </page>
+                </notebook>
+            </field>
+        </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="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="l10n_ke_cu_invoice_number" optional="hide"/>
+                </field>
+            </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>
+            <field name="inherit_id" ref="account.view_account_invoice_filter" />
+            <field name="arch" type="xml">
+                <xpath expr="//field[@name='journal_id']" position="after">
+                    <field name="l10n_ke_cu_invoice_number" string="Kenya CU Invoice Number" filter_domain="[('l10n_ke_cu_invoice_number', 'ilike', self')]" />
+                </xpath>
+            </field>
+        </record>
+    </data>
+</odoo>
+
+
diff --git a/addons/l10n_ke_edi_tremol/views/product_view.xml b/addons/l10n_ke_edi_tremol/views/product_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5829232e1e82d37ee2e8c8489876490b442351f8
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/views/product_view.xml
@@ -0,0 +1,33 @@
+<?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="HS Code" string="HS Code">
+                        <field name="l10n_ke_hsn_code"/>
+                        <field name="l10n_ke_hsn_name"/>
+                    </group>
+                </group>
+            </xpath>
+        </field>
+    </record>
+</odoo>
diff --git a/addons/l10n_ke_edi_tremol/views/report_invoice.xml b/addons/l10n_ke_edi_tremol/views/report_invoice.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c7491ec277978edefc2978b3356cd9abf0f0f10e
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/views/report_invoice.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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;">
+                <b>Kenyan Control Unit Info</b>
+                <div class="row mt-4 mb-4">
+                    <div class="col-auto col-3 mw-100 mb-2">
+                        <p>
+                            <b>Invoice Number: </b><br></br>
+                            <span t-field="o.l10n_ke_cu_invoice_number"/>
+                        </p>
+                        <p>
+                            <b>Serial Number: </b><br></br>
+                            <span t-field="o.l10n_ke_cu_serial_number"/>
+                        </p>
+                        <p>
+                            <b>Date and Time of Signing: </b><br></br>
+                            <span t-field="o.l10n_ke_cu_datetime"/>
+                        </p>
+                    </div>
+                    <div class="col-auto col-3 mw-100 mb-2">
+                        <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"/>
+                        </p>
+                    </div>
+                </div>
+            </div>
+        </xpath>
+    </template>
+</odoo>
diff --git a/addons/l10n_ke_edi_tremol/views/res_config_settings_view.xml b/addons/l10n_ke_edi_tremol/views/res_config_settings_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7b7c899f3972d926234bc72b74f208a4136dbd94
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/views/res_config_settings_view.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <record id="res_config_settings_view_form" model="ir.ui.view">
+        <field name="name">l10n.ke.tremol.inherit.res.config.settings.form</field>
+        <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>
+                                <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>
+                </div>
+            </xpath>
+        </field>
+    </record>
+</odoo>
diff --git a/addons/l10n_ke_edi_tremol/views/res_partner_views.xml b/addons/l10n_ke_edi_tremol/views/res_partner_views.xml
new file mode 100644
index 0000000000000000000000000000000000000000..31c65cf33f38523a8620bfc62bb62449a0288873
--- /dev/null
+++ b/addons/l10n_ke_edi_tremol/views/res_partner_views.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+    <record id="res_partner_view_form" model="ir.ui.view">
+        <field name="name">l10n.ke.tremol.inherit.res.partner.form</field>
+        <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 string="Kenya Accounting Details" name="l10n_ke_details">
+                    <field name="l10n_ke_exemption_number"/>
+                </group>
+            </group>
+        </field>
+    </record>
+</odoo>