diff --git a/addons/l10n_in_edi/__manifest__.py b/addons/l10n_in_edi/__manifest__.py index 7f9b7ad33ba..62354afd4ac 100644 --- a/addons/l10n_in_edi/__manifest__.py +++ b/addons/l10n_in_edi/__manifest__.py @@ -18,7 +18,7 @@ We use "Tera Software Limited" as GSP Step 1: First you need to create an API username and password in the E-invoice portal. Step 2: Switch to company related to that GST number -Step 3: Set that username and password in Odoo (Goto: Invoicing/Accounting -> Configration -> Settings -> Customer Invoices or find "E-invoice" in search bar) +Step 3: Set that username and password in Odoo (Goto: Invoicing/Accounting -> Configuration -> Settings -> Customer Invoices or find "E-invoice" in search bar) Step 4: Repeat steps 1,2,3 for all GSTIN you have in odoo. If you have a multi-company with the same GST number then perform step 1 for the first company only. For the creation of API username and password please ref this document: diff --git a/addons/l10n_in_edi_ewaybill/__init__.py b/addons/l10n_in_edi_ewaybill/__init__.py new file mode 100755 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/l10n_in_edi_ewaybill/__manifest__.py b/addons/l10n_in_edi_ewaybill/__manifest__.py new file mode 100755 index 00000000000..41e13d52e13 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/__manifest__.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + "name": """Indian - E-waybill""", + "version": "1.03.00", + "icon": "/l10n_in/static/description/icon.png", + "category": "Accounting/Localizations/EDI", + "depends": [ + "l10n_in_edi", + ], + "description": """ +Indian - E-waybill +==================================== +To submit E-waybill through API to the government. +We use "Tera Software Limited" as GSP + +Step 1: First you need to create an API username and password in the E-waybill portal. +Step 2: Switch to company related to that GST number +Step 3: Set that username and password in Odoo (Goto: Invoicing/Accounting -> Configration -> Settings -> Indian Electronic WayBill or find "E-waybill" in search bar) +Step 4: Repeat steps 1,2,3 for all GSTIN you have in odoo. If you have a multi-company with the same GST number then perform step 1 for the first company only. + """, + "data": [ + "security/ir.model.access.csv", + "data/account_edi_data.xml", + "data/ewaybill_type_data.xml", + "views/account_move_views.xml", + "views/edi_pdf_report.xml", + "views/res_config_settings_views.xml", + ], + "demo": [ + "demo/demo_company.xml", + "demo/res_partner_demo.xml", + "demo/account_invoice_demo.xml", + ], + "installable": True, + # not applicable when company is related to service industry so auto install is False + "auto_install": False, + "license": "LGPL-3", +} diff --git a/addons/l10n_in_edi_ewaybill/data/account_edi_data.xml b/addons/l10n_in_edi_ewaybill/data/account_edi_data.xml new file mode 100755 index 00000000000..89f0abbbde6 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/data/account_edi_data.xml @@ -0,0 +1,7 @@ + + + + E-waybill (IN) + in_ewaybill_1_03 + + diff --git a/addons/l10n_in_edi_ewaybill/data/ewaybill_type_data.xml b/addons/l10n_in_edi_ewaybill/data/ewaybill_type_data.xml new file mode 100755 index 00000000000..8d17ab46504 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/data/ewaybill_type_data.xml @@ -0,0 +1,75 @@ + + + + + Tax Invoice + INV + Supply + 1 + both + + + + + Tax Invoice + INV + Export + 3 + out + + + + + Tax Invoice + INV + SKD/CKD/Lots + 9 + both + + + + + Bill of Supply + BIL + Supply + 1 + both + + + + + Bill of Supply + BIL + Export + 3 + out + + + + + Bill of Supply + BIL + SKD/CKD/Lots + 9 + both + + + + + Bill of Entry + BOE + Import + 2 + in + + + + Bill of Entry + BOE + SKD/CKD/Lots + 9 + in + + + + diff --git a/addons/l10n_in_edi_ewaybill/demo/account_invoice_demo.xml b/addons/l10n_in_edi_ewaybill/demo/account_invoice_demo.xml new file mode 100644 index 00000000000..a0ce8a780ea --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/demo/account_invoice_demo.xml @@ -0,0 +1,66 @@ + + + + + + + + + out_invoice + + + + + regular + + 20 + 1 + GJ11OD1234 + R + + + + diff --git a/addons/l10n_in_edi_ewaybill/demo/demo_company.xml b/addons/l10n_in_edi_ewaybill/demo/demo_company.xml new file mode 100755 index 00000000000..329498cdc86 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/demo/demo_company.xml @@ -0,0 +1,34 @@ + + + + E-Waybill Company + 05AAACH6188F1ZM + 46B 49B, RAIPUR + BHAGWANPUR, ROORKEE + Haridwar + + + 247667 + + + + E-Waybill Company + + 05AAACH6188F1ZM + abc123@@ + + + + + + + + + + + + + + + + diff --git a/addons/l10n_in_edi_ewaybill/demo/res_partner_demo.xml b/addons/l10n_in_edi_ewaybill/demo/res_partner_demo.xml new file mode 100755 index 00000000000..c0eec35e0f4 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/demo/res_partner_demo.xml @@ -0,0 +1,18 @@ + + + + + Registered Customer (E-Waybill) + + 1 + regular + 19, Ground Floor + Survey Road + Dehradun + 248001 + + + 05AAACH6605F1Z0 + + + diff --git a/addons/l10n_in_edi_ewaybill/i18n/l10n_in_edi_ewaybill.pot b/addons/l10n_in_edi_ewaybill/i18n/l10n_in_edi_ewaybill.pot new file mode 100644 index 00000000000..770e03b57d7 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/i18n/l10n_in_edi_ewaybill.pot @@ -0,0 +1,522 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * l10n_in_edi_ewaybill +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0+e\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2023-01-06 21:57+0000\n" +"PO-Revision-Date: 2023-01-06 21:57+0000\n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/ewaybill_type.py:0 +#, python-format +msgid " (Sub-Type: %s)" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "%s number should be set and not more than 16 characters" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "- Document Type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"- Transport document number and date is required when Transportation Mode is" +" Rail,Air or Ship" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "- Transportation Mode" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "- Transporter is required when E-waybill is managed by transporter" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"- Vehicle Number and Type is required when Transportation Mode is By Road" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "" +"" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.invoice_form_inherit_l10n_in_edi_ewaybill +msgid "km" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.l10n_in_einvoice_report_invoice_document_inherit +msgid "E-waybill Number:" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.l10n_in_einvoice_report_invoice_document_inherit +msgid "E-waybill Valid Till:" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"A username and password still needs to be set or it's wrong for the " +"E-waybill(IN). It needs to be added and verify in the Settings." +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__active +msgid "Active" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_mode__3 +msgid "Air" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__allowed_supply_type +msgid "Allowed for supply type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_mode__1 +msgid "By Road" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.invoice_form_inherit_l10n_in_edi_ewaybill +msgid "Cancel Reason" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model,name:l10n_in_edi_ewaybill.model_res_company +msgid "Companies" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model,name:l10n_in_edi_ewaybill.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__create_uid +msgid "Created by" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__create_date +msgid "Created on" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,help:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_transportation_doc_date +#: model:ir.model.fields,help:l10n_in_edi_ewaybill.field_account_move__l10n_in_transportation_doc_date +#: model:ir.model.fields,help:l10n_in_edi_ewaybill.field_account_payment__l10n_in_transportation_doc_date +msgid "Date on the transporter document" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__display_name +msgid "Display Name" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_distance +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_distance +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_distance +msgid "Distance" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_transportation_doc_date +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_transportation_doc_date +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_transportation_doc_date +msgid "Document Date" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_transportation_doc_no +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_transportation_doc_no +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_transportation_doc_no +msgid "Document Number" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_type_id +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_type_id +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_type_id +msgid "Document Type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_res_company__l10n_in_edi_ewaybill_password +msgid "E-Waybill (IN) Password" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_res_company__l10n_in_edi_ewaybill_username +msgid "E-Waybill (IN) Username" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_res_company__l10n_in_edi_ewaybill_auth_validity +msgid "E-Waybill (IN) Valid Until" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model,name:l10n_in_edi_ewaybill.model_l10n_in_ewaybill_type +msgid "E-Waybill Document Type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_move.py:0 +#, python-format +msgid "E-waybill is already created" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_edi_ewaybill_direct_api +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_edi_ewaybill_direct_api +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_edi_ewaybill_direct_api +msgid "E-waybill(IN) direct API" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model,name:l10n_in_edi_ewaybill.model_account_edi_format +msgid "EDI format" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "Error" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "HSN code is not set in product %s" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__id +msgid "ID" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "Impossible to send the Ewaybill." +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__l10n_in_ewaybill_type__allowed_supply_type__in +msgid "Incoming" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__l10n_in_ewaybill_type__allowed_supply_type__both +msgid "Incoming and Outgoing" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/res_config_settings.py:0 +#, python-format +msgid "" +"Incorrect username or password, or the GST number on company does not match." +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_res_config_settings__l10n_in_edi_ewaybill_password +msgid "Indian EDI Stock password" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_res_config_settings__l10n_in_edi_ewaybill_username +msgid "Indian EDI Stock username" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Indian Electronic WayBill" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "Invalid HSN Code (%s) in product %s" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_move.py:0 +#, python-format +msgid "" +"Invalid invoice configuration:\n" +"\n" +"%s" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model,name:l10n_in_edi_ewaybill.model_account_move +msgid "Journal Entry" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type____last_update +msgid "Last Modified on" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__write_date +msgid "Last Updated on" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_mode__0 +msgid "Managed by Transporter" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_vehicle_type__o +msgid "ODC" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Only check if you are in production." +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__l10n_in_ewaybill_type__allowed_supply_type__out +msgid "Outgoing" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Password" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Production Environment" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_mode__2 +msgid "Rail" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_vehicle_type__r +msgid "Regular" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.invoice_form_inherit_l10n_in_edi_ewaybill +msgid "Send E-waybill" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Setup E-Waybill" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Setup E-Waybill Service for this company" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields.selection,name:l10n_in_edi_ewaybill.selection__account_move__l10n_in_mode__4 +msgid "Ship" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_edi_ewaybill_show_send_button +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_edi_ewaybill_show_send_button +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_edi_ewaybill_show_send_button +msgid "Show Send E-waybill Button" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"Somehow this E-waybill has been canceled in the government portal before. " +"You can verify by checking the details into the government " +"(https://ewaybillgst.gov.in/Others/EBPrintnew.asp)" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"Somehow this E-waybill has been generated in the government portal before. " +"You can verify by checking the invoice details into the government " +"(https://ewaybillgst.gov.in/Others/EBPrintnew.asp)" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__sub_type +msgid "Sub-type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__sub_type_code +msgid "Sub-type Code" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"The following information are missing on the invoice (see eWayBill tab):" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_move.py:0 +#, python-format +msgid "" +"To cancel E-waybill set cancel reason and remarks at E-waybill tab in: \n" +"%s" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.invoice_form_inherit_l10n_in_edi_ewaybill +msgid "Transaction Details" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,help:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_transportation_doc_no +#: model:ir.model.fields,help:l10n_in_edi_ewaybill.field_account_move__l10n_in_transportation_doc_no +#: model:ir.model.fields,help:l10n_in_edi_ewaybill.field_account_payment__l10n_in_transportation_doc_no +msgid "" +"Transport document number. If it is more than 15 chars, last 15 chars may be" +" entered" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.invoice_form_inherit_l10n_in_edi_ewaybill +msgid "Transportation Details" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_mode +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_mode +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_mode +msgid "Transportation Mode" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_transporter_id +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_transporter_id +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_transporter_id +msgid "Transporter" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__name +msgid "Type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_l10n_in_ewaybill_type__code +msgid "Type Code" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"Unable to connect to the E-WayBill service.The web service may be temporary " +"down. Please try again in a moment." +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Username" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_vehicle_no +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_vehicle_no +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_vehicle_no +msgid "Vehicle Number" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_bank_statement_line__l10n_in_vehicle_type +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_move__l10n_in_vehicle_type +#: model:ir.model.fields,field_description:l10n_in_edi_ewaybill.field_account_payment__l10n_in_vehicle_type +msgid "Vehicle Type" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.res_config_settings_view_form_inherit_l10n_in_edi_ewaybill +msgid "Verify Username and Password" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_move.py:0 +#, python-format +msgid "You can only create E-waybill from posted invoice" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "" +"You need at least one product having \"Product Type\" as stockable or " +"consumable." +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: model_terms:ir.ui.view,arch_db:l10n_in_edi_ewaybill.invoice_form_inherit_l10n_in_edi_ewaybill +msgid "eWayBill" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "product is required to get HSN code" +msgstr "" + +#. module: l10n_in_edi_ewaybill +#: code:addons/l10n_in_edi_ewaybill/models/account_edi_format.py:0 +#, python-format +msgid "waiting For IRN generation To create E-waybill" +msgstr "" diff --git a/addons/l10n_in_edi_ewaybill/models/__init__.py b/addons/l10n_in_edi_ewaybill/models/__init__.py new file mode 100755 index 00000000000..ac043943e4b --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import ewaybill_type +from . import res_company +from . import res_config_settings +from . import account_move +from . import account_edi_format diff --git a/addons/l10n_in_edi_ewaybill/models/account_edi_format.py b/addons/l10n_in_edi_ewaybill/models/account_edi_format.py new file mode 100755 index 00000000000..465fcdd36e0 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/models/account_edi_format.py @@ -0,0 +1,607 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import re +import json +from datetime import timedelta + +from odoo import models, fields, api, _ +from odoo.tools import html_escape +from odoo.exceptions import AccessError +from odoo.addons.iap import jsonrpc +import logging + +_logger = logging.getLogger(__name__) + +DEFAULT_IAP_ENDPOINT = "https://l10n-in-edi.api.odoo.com" +DEFAULT_IAP_TEST_ENDPOINT = "https://l10n-in-edi-demo.api.odoo.com" + + +class AccountEdiFormat(models.Model): + _inherit = "account.edi.format" + + def _l10n_in_edi_ewaybill_base_irn_or_direct(self, move): + """ + There is two type of api call to create E-waybill + 1. base on IRN, IRN is number created when we do E-invoice + 2. direct call, when E-invoice not aplicable or it"s credit not + """ + if move.move_type == "out_refund": + return "direct" + einvoice_in_edi_format = move.journal_id.edi_format_ids.filtered(lambda f: f.code == "in_einvoice_1_03") + return einvoice_in_edi_format and einvoice_in_edi_format._is_required_for_invoice(move) and "irn" or "direct" + + def _is_compatible_with_journal(self, journal): + if self.code == "in_ewaybill_1_03": + return journal.type in ("sale", "purchase") + return super()._is_compatible_with_journal(journal) + + def _is_enabled_by_default_on_journal(self, journal): + """ + It's sent with a button action on the invoice so it's disabled by default + """ + self.ensure_one() + if self.code == "in_ewaybill_1_03": + return False + return super()._is_enabled_by_default_on_journal(journal) + + def _is_required_for_invoice(self, invoice): + self.ensure_one() + if self.code == "in_ewaybill_1_03": + return invoice.l10n_in_mode and invoice.is_invoice(include_receipts=True) + return super()._is_required_for_invoice(invoice) + + def _needs_web_services(self): + self.ensure_one() + return self.code == "in_ewaybill_1_03" or super()._needs_web_services() + + def _get_invoice_edi_content(self, move): + if self.code != "in_ewaybill_1_03": + return super()._get_invoice_edi_content(move) + base = self._l10n_in_edi_ewaybill_base_irn_or_direct(move) + if base == "irn": + json_dump = json.dumps(self._l10n_in_edi_irn_ewaybill_generate_json(move)) + else: + json_dump = json.dumps(self._l10n_in_edi_ewaybill_generate_json(move)) + return json_dump.encode() + + def _check_move_configuration(self, move): + if self.code != "in_ewaybill_1_03": + return super()._check_move_configuration(move) + error_message = [] + if not move.l10n_in_type_id: + error_message.append(_("- Document Type")) + if not move.l10n_in_mode: + error_message.append(_("- Transportation Mode")) + elif move.l10n_in_mode == "0" and not move.l10n_in_transporter_id: + error_message.append(_("- Transporter is required when E-waybill is managed by transporter")) + elif move.l10n_in_mode == "1": + if not move.l10n_in_vehicle_no and move.l10n_in_vehicle_type: + error_message.append(_("- Vehicle Number and Type is required when Transportation Mode is By Road")) + elif move.l10n_in_mode in ("2", "3", "4"): + if not move.l10n_in_transportation_doc_no and move.l10n_in_transportation_doc_date: + error_message.append(_("- Transport document number and date is required when Transportation Mode is Rail,Air or Ship")) + if error_message: + error_message.insert(0, _("The following information are missing on the invoice (see eWayBill tab):")) + base = self._l10n_in_edi_ewaybill_base_irn_or_direct(move) + if base == "irn": + # already checked by E-invoice (l10n_in_edi) so no need to check + return error_message + is_purchase = move.is_purchase_document(include_receipts=True) + error_message += self._l10n_in_validate_partner(move.partner_id) + error_message += self._l10n_in_validate_partner(move.company_id.partner_id, is_company=True) + if not re.match("^.{1,16}$", is_purchase and move.ref or move.name): + error_message.append(_("%s number should be set and not more than 16 characters", + (is_purchase and "Bill Reference" or "Invoice"))) + goods_line_is_available = False + for line in move.invoice_line_ids.filtered(lambda line: not (line.display_type or line.is_rounding_line or line.product_id.type == "service")): + goods_line_is_available = True + if line.product_id: + hsn_code = self._l10n_in_edi_extract_digits(line.product_id.l10n_in_hsn_code) + if not hsn_code: + error_message.append(_("HSN code is not set in product %s", line.product_id.name)) + elif not re.match("^[0-9]+$", hsn_code): + error_message.append(_( + "Invalid HSN Code (%s) in product %s", hsn_code, line.product_id.name + )) + else: + error_message.append(_("product is required to get HSN code")) + if not goods_line_is_available: + error_message.append(_('You need at least one product having "Product Type" as stockable or consumable.')) + if error_message: + error_message.insert(0, _("Impossible to send the Ewaybill.")) + return error_message + + def _post_invoice_edi(self, invoices): + if self.code != "in_ewaybill_1_03": + return super()._post_invoice_edi(invoices) + base = self._l10n_in_edi_ewaybill_base_irn_or_direct(invoices) + if base == "irn": + return self._l10n_in_edi_ewaybill_irn_post_invoice_edi(invoices) + else: + return self._l10n_in_edi_ewaybill_post_invoice_edi(invoices) + + def _cancel_invoice_edi(self, invoices): + if self.code != "in_ewaybill_1_03": + return super()._cancel_invoice_edi(invoices) + response = {} + res = {} + ewaybill_response_json = invoices._get_l10n_in_edi_ewaybill_response_json() + cancel_json = { + "ewbNo": ewaybill_response_json.get("ewayBillNo") or ewaybill_response_json.get("EwbNo"), + "cancelRsnCode": int(invoices.l10n_in_edi_cancel_reason), + "CnlRem": invoices.l10n_in_edi_cancel_remarks, + } + response = self._l10n_in_edi_ewaybill_cancel(invoices.company_id, cancel_json) + if response.get("error"): + error = response["error"] + error_codes = [e.get("code") for e in error] + if "238" in error_codes: + # Invalid token eror then create new token and send generate request again. + # This happen when authenticate called from another odoo instance with same credentials (like. Demo/Test) + authenticate_response = self._l10n_in_edi_ewaybill_authenticate(invoices.company_id) + if not authenticate_response.get("error"): + error = [] + response = self._l10n_in_edi_ewaybill_cancel(invoices.company_id, cancel_json) + if response.get("error"): + error = response["error"] + error_codes = [e.get("code") for e in error] + if "312" in error_codes: + # E-waybill is already canceled + # this happens when timeout from the Government portal but IRN is generated + error_message = "
".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in error]) + error = [] + response = {"data": ""} + odoobot = self.env.ref("base.partner_root") + invoices.message_post(author_id=odoobot.id, body= + "%s
%s:
%s" %( + _("Somehow this E-waybill has been canceled in the government portal before. You can verify by checking the details into the government (https://ewaybillgst.gov.in/Others/EBPrintnew.asp)"), + _("Error"), + error_message + ) + ) + if "no-credit" in error_codes: + res[invoices] = { + "success": False, + "error": self._l10n_in_edi_get_iap_buy_credits_message(invoices.company_id), + "blocking_level": "error", + } + elif error: + error_message = "
".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in error]) + blocking_level = "error" + if "404" in error_codes: + blocking_level = "warning" + res[invoices] = { + "success": False, + "error": error_message, + "blocking_level": blocking_level, + } + if not response.get("error"): + json_dump = json.dumps(response.get("data")) + json_name = "%s_ewaybill_cancel.json" % (invoices.name.replace("/", "_")) + attachment = self.env["ir.attachment"].create({ + "name": json_name, + "raw": json_dump.encode(), + "res_model": "account.move", + "res_id": invoices.id, + "mimetype": "application/json", + }) + inv_res = {"success": True, "attachment": attachment} + res[invoices] = inv_res + return res + + def _l10n_in_edi_ewaybill_irn_post_invoice_edi(self, invoices): + response = {} + res = {} + generate_json = self._l10n_in_edi_irn_ewaybill_generate_json(invoices) + response = self._l10n_in_edi_irn_ewaybill_generate(invoices.company_id, generate_json) + if response.get("error"): + error = response["error"] + error_codes = [e.get("code") for e in error] + if "1005" in error_codes: + # Invalid token eror then create new token and send generate request again. + # This happen when authenticate called from another odoo instance with same credentials (like. Demo/Test) + authenticate_response = self._l10n_in_edi_authenticate(invoices.company_id) + if not authenticate_response.get("error"): + error = [] + response = self._l10n_in_edi_irn_ewaybill_generate(invoices.company_id, generate_json) + if response.get("error"): + error = response["error"] + error_codes = [e.get("code") for e in error] + if "4002" in error_codes: + # Get E-waybill by details in case of IRN is already generated + # this happens when timeout from the Government portal but E-waybill is generated + response = self._l10n_in_edi_irn_ewaybill_get(invoices.company_id, generate_json.get("Irn")) + if not response.get("error"): + error = [] + odoobot = self.env.ref("base.partner_root") + invoices.message_post(author_id=odoobot.id, body= + _("Somehow this E-waybill has been generated in the government portal before. You can verify by checking the invoice details into the government (https://ewaybillgst.gov.in/Others/EBPrintnew.asp)") + ) + + if "no-credit" in error_codes: + res[invoices] = { + "success": False, + "error": self._l10n_in_edi_get_iap_buy_credits_message(invoices.company_id), + "blocking_level": "error", + } + elif error: + error_message = "
".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in error]) + blocking_level = "error" + if "404" in error_codes or "waiting" in error_codes: + blocking_level = "warning" + res[invoices] = { + "success": False, + "error": error_message, + "blocking_level": blocking_level, + } + if not response.get("error"): + json_dump = json.dumps(response.get("data")) + json_name = "%s_irn_ewaybill.json" % (invoices.name.replace("/", "_")) + attachment = self.env["ir.attachment"].create({ + "name": json_name, + "raw": json_dump.encode(), + "res_model": "account.move", + "res_id": invoices.id, + "mimetype": "application/json", + }) + inv_res = {"success": True, "attachment": attachment} + res[invoices] = inv_res + return res + + def _l10n_in_edi_irn_ewaybill_generate_json(self, invoice): + json_payload = { + "Irn": invoice._get_l10n_in_edi_response_json().get("Irn"), + "Distance": invoice.l10n_in_distance, + } + if invoice.l10n_in_mode == "0": + json_payload.update({ + "TransId": invoice.l10n_in_transporter_id.vat, + "TransName": invoice.l10n_in_transporter_id.name, + }) + elif invoice.l10n_in_mode == "1": + json_payload.update({ + "TransMode": invoice.l10n_in_mode, + "VehNo": invoice.l10n_in_vehicle_no, + "VehType": invoice.l10n_in_vehicle_type, + }) + elif invoice.l10n_in_mode in ("2", "3", "4"): + doc_date = invoice.l10n_in_transportation_doc_date + json_payload.update({ + "TransMode": invoice.l10n_in_mode, + "TransDocDt": doc_date and doc_date.strftime("%d/%m/%Y") or False, + "TransDocNo": invoice.l10n_in_transportation_doc_no, + }) + return json_payload + + def _l10n_in_edi_ewaybill_post_invoice_edi(self, invoices): + response = {} + res = {} + generate_json = self._l10n_in_edi_ewaybill_generate_json(invoices) + response = self._l10n_in_edi_ewaybill_generate(invoices.company_id, generate_json) + if response.get("error"): + error = response["error"] + error_codes = [e.get("code") for e in error] + if "238" in error_codes: + # Invalid token eror then create new token and send generate request again. + # This happen when authenticate called from another odoo instance with same credentials (like. Demo/Test) + authenticate_response = self._l10n_in_edi_ewaybill_authenticate(invoices.company_id) + if not authenticate_response.get("error"): + error = [] + response = self._l10n_in_edi_ewaybill_generate(invoices.company_id, generate_json) + if response.get("error"): + error = response["error"] + error_codes = [e.get("code") for e in error] + if "604" in error_codes: + # Get E-waybill by details in case of E-waybill is already generated + # this happens when timeout from the Government portal but E-waybill is generated + response = self._l10n_in_edi_ewaybill_get_by_consigner( + invoices.company_id, generate_json.get("docType"), generate_json.get("docNo")) + if not response.get("error"): + error = [] + odoobot = self.env.ref("base.partner_root") + invoices.message_post(author_id=odoobot.id, body= + _("Somehow this E-waybill has been generated in the government portal before. You can verify by checking the invoice details into the government (https://ewaybillgst.gov.in/Others/EBPrintnew.asp)") + ) + if "no-credit" in error_codes: + res[invoices] = { + "success": False, + "error": self._l10n_in_edi_get_iap_buy_credits_message(invoices.company_id), + "blocking_level": "error", + } + elif error: + error_message = "
".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in error]) + blocking_level = "error" + if "404" in error_codes: + blocking_level = "warning" + res[invoices] = { + "success": False, + "error": error_message, + "blocking_level": blocking_level, + } + if not response.get("error"): + json_dump = json.dumps(response.get("data")) + json_name = "%s_ewaybill.json" % (invoices.name.replace("/", "_")) + attachment = self.env["ir.attachment"].create({ + "name": json_name, + "raw": json_dump.encode(), + "res_model": "account.move", + "res_id": invoices.id, + "mimetype": "application/json", + }) + inv_res = {"success": True, "attachment": attachment} + res[invoices] = inv_res + return res + + def _get_l10n_in_edi_saler_buyer_party(self, move): + res = super()._get_l10n_in_edi_saler_buyer_party(move) + if move.is_purchase_document(include_receipts=True): + res = { + "seller_details": move.partner_id, + "dispatch_details": move._l10n_in_get_shipping_partner(), + "buyer_details": move.company_id.partner_id, + "ship_to_details": move._l10n_in_get_warehouse_address() or move.company_id.partner_id, + } + return res + + def _l10n_in_edi_ewaybill_generate_json(self, invoices): + def get_transaction_type(seller_details, dispatch_details, buyer_details, ship_to_details): + """ + 1 - Regular + 2 - Bill To - Ship To + 3 - Bill From - Dispatch From + 4 - Combination of 2 and 3 + """ + if seller_details != dispatch_details and buyer_details != ship_to_details: + return 4 + elif seller_details != dispatch_details: + return 3 + elif buyer_details != ship_to_details: + return 2 + else: + return 1 + + def filter_invl_to_apply(invoice_line): + if invoice_line.product_id.type == "service": + return False + return True + + saler_buyer = self._get_l10n_in_edi_saler_buyer_party(invoices) + seller_details = saler_buyer.get("seller_details") + dispatch_details = saler_buyer.get("dispatch_details") + buyer_details = saler_buyer.get("buyer_details") + ship_to_details = saler_buyer.get("ship_to_details") + sign = invoices.is_inbound() and -1 or 1 + extract_digits = self._l10n_in_edi_extract_digits + tax_details = self._l10n_in_prepare_edi_tax_details(invoices, filter_invl_to_apply=filter_invl_to_apply) + tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(tax_details.get("tax_details", {})) + invoice_line_tax_details = tax_details.get("invoice_line_tax_details") + json_payload = { + "supplyType": invoices.is_purchase_document(include_receipts=True) and "I" or "O", + "subSupplyType": invoices.l10n_in_type_id.sub_type_code, + "docType": invoices.l10n_in_type_id.code, + "transactionType": get_transaction_type(seller_details, dispatch_details, buyer_details, ship_to_details), + "transDistance": str(invoices.l10n_in_distance), + "docNo": invoices.is_purchase_document(include_receipts=True) and invoices.ref or invoices.name, + "docDate": invoices.date.strftime("%d/%m/%Y"), + "fromGstin": seller_details.commercial_partner_id.vat or "URP", + "fromTrdName": seller_details.commercial_partner_id.name, + "fromAddr1": dispatch_details.street or "", + "fromAddr2": dispatch_details.street2 or "", + "fromPlace": dispatch_details.city or "", + "fromPincode": dispatch_details.country_id.code == "IN" and int(extract_digits(dispatch_details.zip)) or "", + "fromStateCode": int(seller_details.state_id.l10n_in_tin) or "", + "actFromStateCode": dispatch_details.state_id.l10n_in_tin and int(dispatch_details.state_id.l10n_in_tin) or "", + "toGstin": buyer_details.commercial_partner_id.vat or "URP", + "toTrdName": buyer_details.commercial_partner_id.name, + "toAddr1": ship_to_details.street or "", + "toAddr2": ship_to_details.street2 or "", + "toPlace": ship_to_details.city or "", + "toPincode": int(extract_digits(ship_to_details.zip)), + "actToStateCode": int(ship_to_details.state_id.l10n_in_tin), + "toStateCode": invoices.l10n_in_state_id.l10n_in_tin and int(invoices.l10n_in_state_id.l10n_in_tin) or ( + buyer_details.state_id.l10n_in_tin or int(buyer_details.state_id.l10n_in_tin) or "" + ), + "itemList": [ + self._get_l10n_in_edi_ewaybill_line_details(line, line_tax_details, sign) + for line, line_tax_details in invoice_line_tax_details.items() + ], + "totalValue": self._l10n_in_round_value(tax_details.get("base_amount") * sign), + "cgstValue": self._l10n_in_round_value(tax_details_by_code.get("cgst_amount", 0.00) * sign), + "sgstValue": self._l10n_in_round_value(tax_details_by_code.get("sgst_amount", 0.00) * sign), + "igstValue": self._l10n_in_round_value(tax_details_by_code.get("igst_amount", 0.00) * sign), + "cessValue": self._l10n_in_round_value(tax_details_by_code.get("cess_amount", 0.00) * sign), + "cessNonAdvolValue": self._l10n_in_round_value(tax_details_by_code.get("cess_non_advol_amount", 0.00) * sign), + "otherValue": self._l10n_in_round_value(tax_details_by_code.get("other_amount", 0.00) * sign), + "totInvValue": self._l10n_in_round_value((tax_details.get("base_amount") + tax_details.get("tax_amount")) * sign), + } + is_overseas = invoices.l10n_in_gst_treatment in ("overseas", "special_economic_zone") + if invoices.is_purchase_document(include_receipts=True): + if is_overseas: + json_payload.update({"fromStateCode": 99}) + if is_overseas and dispatch_details.state_id.country_id.code != "IN": + json_payload.update({ + "actFromStateCode": 99, + "fromPincode": 999999, + }) + else: + json_payload.update({ + "actFromStateCode": dispatch_details.state_id.l10n_in_tin and int(dispatch_details.state_id.l10n_in_tin) or "", + "fromPincode": int(extract_digits(dispatch_details.zip)), + }) + else: + if is_overseas: + json_payload.update({"toStateCode": 99}) + if is_overseas and ship_to_details.state_id.country_id.code != "IN": + json_payload.update({ + "actToStateCode": 99, + "toPincode": 999999, + }) + else: + json_payload.update({ + "actToStateCode": int(ship_to_details.state_id.l10n_in_tin), + "toPincode": int(extract_digits(ship_to_details.zip)), + }) + + if invoices.l10n_in_mode == "0": + json_payload.update({ + "transporterId": invoices.l10n_in_transporter_id.vat or "", + "transporterName": invoices.l10n_in_transporter_id.name or "", + }) + if invoices.l10n_in_mode in ("2", "3", "4"): + json_payload.update({ + "transMode": invoices.l10n_in_mode, + "transDocNo": invoices.l10n_in_transporter_doc_no or "", + "transDocDate": invoices.l10n_in_transporter_doc_date and + invoices.l10n_in_transporter_doc_date.strftime("%d/%m/%Y") or "", + }) + if invoices.l10n_in_mode == "1": + json_payload.update({ + "transMode": invoices.l10n_in_mode, + "vehicleNo": invoices.l10n_in_vehicle_no or "", + "vehicleType": invoices.l10n_in_vehicle_type or "", + }) + return json_payload + + def _get_l10n_in_edi_ewaybill_line_details(self, line, line_tax_details, sign): + extract_digits = self._l10n_in_edi_extract_digits + tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(line_tax_details.get("tax_details", {})) + line_details = { + "productName": line.product_id.name, + "hsnCode": extract_digits(line.product_id.l10n_in_hsn_code), + "productDesc": line.name, + "quantity": line.quantity, + "qtyUnit": line.product_id.uom_id.l10n_in_code and line.product_id.uom_id.l10n_in_code.split("-")[0] or "OTH", + "taxableAmount": self._l10n_in_round_value(line.balance * sign), + } + if tax_details_by_code.get("igst_rate"): + line_details.update({"igstRate": self._l10n_in_round_value(tax_details_by_code["igst_rate"])}) + else: + line_details.update({ + "cgstRate": self._l10n_in_round_value(tax_details_by_code.get("cgst_rate", 0.00)), + "sgstRate": self._l10n_in_round_value(tax_details_by_code.get("sgst_rate", 0.00)), + }) + if tax_details_by_code.get("cess_rate"): + line_details.update({"cessRate": self._l10n_in_round_value(tax_details_by_code.get("cess_rate"))}) + return line_details + + #================================ E-invoice API methods =========================== + + @api.model + def _l10n_in_edi_irn_ewaybill_generate(self, company, json_payload): + # IRN is created by E-invoice API call so waiting for it. + if not json_payload.get("Irn"): + return {"error": [{ + "code": "waiting", + "message": _("waiting For IRN generation To create E-waybill")} + ]} + token = self._l10n_in_edi_get_token(company) + if not token: + return self._l10n_in_edi_no_config_response() + params = { + "auth_token": token, + "json_payload": json_payload, + } + return self._l10n_in_edi_connect_to_server(company, url_path="/iap/l10n_in_edi/1/generate_ewaybill_by_irn", params=params) + + @api.model + def _l10n_in_edi_irn_ewaybill_get(self, company, irn): + token = self._l10n_in_edi_get_token(company) + if not token: + return self._l10n_in_edi_no_config_response() + params = { + "auth_token": token, + "irn": irn, + } + return self._l10n_in_edi_connect_to_server(company, url_path="/iap/l10n_in_edi/1/get_ewaybill_by_irn", params=params) + + #=============================== E-waybill API methods =================================== + + @api.model + def _l10n_in_edi_ewaybill_no_config_response(self): + return {"error": [{ + "code": "000", + "message": _( + "A username and password still needs to be set or it's wrong for the E-waybill(IN). " + "It needs to be added and verify in the Settings." + )} + ]} + + @api.model + def _l10n_in_edi_ewaybill_check_authentication(self, company): + sudo_company = company.sudo() + if sudo_company.l10n_in_edi_ewaybill_username and sudo_company._l10n_in_edi_ewaybill_token_is_valid(): + return True + elif sudo_company.l10n_in_edi_ewaybill_username and sudo_company.l10n_in_edi_ewaybill_password: + authenticate_response = self._l10n_in_edi_ewaybill_authenticate(company) + if not authenticate_response.get("error"): + return True + return False + + @api.model + def _l10n_in_edi_ewaybill_connect_to_server(self, company, url_path, params): + user_token = self.env["iap.account"].get("l10n_in_edi") + params.update({ + "account_token": user_token.account_token, + "dbuuid": self.env["ir.config_parameter"].sudo().get_param("database.uuid"), + "username": company.sudo().l10n_in_edi_ewaybill_username, + "gstin": company.vat, + }) + if company.sudo().l10n_in_edi_production_env: + default_endpoint = DEFAULT_IAP_ENDPOINT + else: + default_endpoint = DEFAULT_IAP_TEST_ENDPOINT + endpoint = self.env["ir.config_parameter"].sudo().get_param("l10n_in_edi_ewaybill.endpoint", default_endpoint) + url = "%s%s" % (endpoint, url_path) + try: + return jsonrpc(url, params=params, timeout=25) + except AccessError as e: + _logger.warning("Connection error: %s", e.args[0]) + return { + "error": [{ + "code": "access_error", + "message": _("Unable to connect to the E-WayBill service." + "The web service may be temporary down. Please try again in a moment.") + }] + } + + @api.model + def _l10n_in_edi_ewaybill_authenticate(self, company): + params = {"password": company.sudo().l10n_in_edi_ewaybill_password} + response = self._l10n_in_edi_ewaybill_connect_to_server( + company, url_path="/iap/l10n_in_edi_ewaybill/1/authenticate", params=params + ) + if response and response.get("status_cd") == "1": + company.sudo().l10n_in_edi_ewaybill_auth_validity = fields.Datetime.now() + timedelta( + hours=6, minutes=00, seconds=00) + return response + + @api.model + def _l10n_in_edi_ewaybill_generate(self, company, json_payload): + is_authenticated = self._l10n_in_edi_ewaybill_check_authentication(company) + if not is_authenticated: + return self._l10n_in_edi_ewaybill_no_config_response() + params = {"json_payload": json_payload} + return self._l10n_in_edi_ewaybill_connect_to_server( + company, url_path="/iap/l10n_in_edi_ewaybill/1/generate", params=params + ) + + @api.model + def _l10n_in_edi_ewaybill_cancel(self, company, json_payload): + is_authenticated = self._l10n_in_edi_ewaybill_check_authentication(company) + if not is_authenticated: + return self._l10n_in_edi_ewaybill_no_config_response() + params = {"json_payload": json_payload} + return self._l10n_in_edi_ewaybill_connect_to_server( + company, url_path="/iap/l10n_in_edi_ewaybill/1/cancel", params=params + ) + + @api.model + def _l10n_in_edi_ewaybill_get_by_consigner(self, company, document_type, document_number): + is_authenticated = self._l10n_in_edi_ewaybill_check_authentication(company) + if not is_authenticated: + return self._l10n_in_edi_ewaybill_no_config_response() + params = {"document_type": document_type, "document_number": document_number} + return self._l10n_in_edi_ewaybill_connect_to_server( + company, url_path="/iap/l10n_in_edi_ewaybill/1/getewaybillgeneratedbyconsigner", params=params + ) diff --git a/addons/l10n_in_edi_ewaybill/models/account_move.py b/addons/l10n_in_edi_ewaybill/models/account_move.py new file mode 100755 index 00000000000..b8faacf7935 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/models/account_move.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +from odoo import fields, models, api, _ +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = "account.move" + + # Transaction Details + l10n_in_type_id = fields.Many2one("l10n.in.ewaybill.type", "E-waybill Document Type", tracking=True) + + # transportation details + l10n_in_distance = fields.Integer("Distance", tracking=True) + l10n_in_mode = fields.Selection([ + ("0", "Managed by Transporter"), + ("1", "By Road"), + ("2", "Rail"), + ("3", "Air"), + ("4", "Ship")], + string="Transportation Mode", copy=False, tracking=True) + + # Vehicle Number and Type required when transportation mode is By Road. + l10n_in_vehicle_no = fields.Char("Vehicle Number", copy=False, tracking=True) + l10n_in_vehicle_type = fields.Selection([ + ("R", "Regular"), + ("O", "ODC")], + string="Vehicle Type", copy=False, tracking=True) + + # Document number and date required in case of transportation mode is Rail, Air or Ship. + l10n_in_transportation_doc_no = fields.Char( + string="E-waybill Document Number", + help="""Transport document number. If it is more than 15 chars, last 15 chars may be entered""", + copy=False, tracking=True) + l10n_in_transportation_doc_date = fields.Date( + string="Document Date", + help="Date on the transporter document", + copy=False, + tracking=True) + + # transporter id required when transportation done by other party. + l10n_in_transporter_id = fields.Many2one("res.partner", "Transporter", copy=False, tracking=True) + # show and hide fields base on this + l10n_in_edi_ewaybill_direct_api = fields.Boolean(string="E-waybill(IN) direct API", compute="_compute_l10n_in_edi_ewaybill_direct") + l10n_in_edi_ewaybill_show_send_button = fields.Boolean(string="Show Send E-waybill Button", compute="_compute_l10n_in_edi_ewaybill_show_send_button") + + @api.depends('state', 'edi_document_ids', 'edi_document_ids.state') + def _compute_l10n_in_edi_ewaybill_show_send_button(self): + edi_format = self.env.ref('l10n_in_edi_ewaybill.edi_in_ewaybill_json_1_03') + posted_moves = self.filtered(lambda x: x.is_invoice() and x.state == 'posted') + for move in posted_moves: + already_sent = move.edi_document_ids.filtered(lambda x: x.edi_format_id == edi_format and x.state in ('sent', 'to_cancel', 'to_send')) + if already_sent: + move.l10n_in_edi_ewaybill_show_send_button = False + else: + move.l10n_in_edi_ewaybill_show_send_button = True + (self - posted_moves).l10n_in_edi_ewaybill_show_send_button = False + + @api.depends("l10n_in_gst_treatment") + def _compute_l10n_in_edi_ewaybill_direct(self): + for move in self: + base = self.env["account.edi.format"]._l10n_in_edi_ewaybill_base_irn_or_direct(move) + move.l10n_in_edi_ewaybill_direct_api = base == "direct" + + @api.depends("edi_document_ids") + def _compute_l10n_in_edi_show_cancel(self): + super()._compute_l10n_in_edi_show_cancel() + for invoice in self: + if invoice.edi_document_ids.filtered(lambda i: i.edi_format_id.code == "in_ewaybill_1_03" and i.state in ("sent", "to_cancel", "cancelled")): + invoice.l10n_in_edi_show_cancel = True + + def _get_l10n_in_edi_ewaybill_response_json(self): + self.ensure_one() + l10n_in_edi = self.edi_document_ids.filtered(lambda i: i.edi_format_id.code == "in_ewaybill_1_03" + and i.state in ("sent", "to_cancel")) + if l10n_in_edi and l10n_in_edi.attachment_id: + return json.loads(l10n_in_edi.attachment_id.raw.decode("utf-8")) + else: + return {} + + def button_cancel_posted_moves(self): + """Mark the edi.document related to this move to be canceled.""" + reason_and_remarks_not_set = self.env["account.move"] + for move in self: + send_l10n_in_edi_ewaybill = move.edi_document_ids.filtered(lambda doc: doc.edi_format_id.code == "in_ewaybill_1_03") + # check submitted E-waybill does not have reason and remarks + # because it's needed to cancel E-waybill + if send_l10n_in_edi_ewaybill and (not move.l10n_in_edi_cancel_reason or not move.l10n_in_edi_cancel_remarks): + reason_and_remarks_not_set += move + if reason_and_remarks_not_set: + raise UserError(_( + "To cancel E-waybill set cancel reason and remarks at E-waybill tab in: \n%s", + ("\n".join(reason_and_remarks_not_set.mapped("name"))), + )) + return super().button_cancel_posted_moves() + + def l10n_in_edi_ewaybill_send(self): + edi_format = self.env.ref('l10n_in_edi_ewaybill.edi_in_ewaybill_json_1_03') + edi_document_vals_list = [] + for move in self: + if move.state != 'posted': + raise UserError(_("You can only create E-waybill from posted invoice")) + errors = edi_format._check_move_configuration(move) + if errors: + raise UserError(_("Invalid invoice configuration:\n\n%s") % '\n'.join(errors)) + existing_edi_document = move.edi_document_ids.filtered(lambda x: x.edi_format_id == edi_format) + if existing_edi_document: + if existing_edi_document.state in ('sent', 'to_cancel'): + raise UserError(_("E-waybill is already created") % '\n'.join(errors)) + existing_edi_document.write({ + 'state': 'to_send', + 'attachment_id': False, + }) + else: + edi_document_vals_list.append({ + 'edi_format_id': edi_format.id, + 'move_id': move.id, + 'state': 'to_send', + }) + self.env['account.edi.document'].create(edi_document_vals_list) + self.env.ref('account_edi.ir_cron_edi_network')._trigger() diff --git a/addons/l10n_in_edi_ewaybill/models/ewaybill_type.py b/addons/l10n_in_edi_ewaybill/models/ewaybill_type.py new file mode 100755 index 00000000000..bf9e6920c2a --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/models/ewaybill_type.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 EWayBillType(models.Model): + _name = "l10n.in.ewaybill.type" + _description = "E-Waybill Document Type" + + name = fields.Char("Type") + code = fields.Char("Type Code") + sub_type = fields.Char("Sub-type") + sub_type_code = fields.Char("Sub-type Code") + allowed_supply_type = fields.Selection( + [ + ("both", "Incoming and Outgoing"), + ("out", "Outgoing"), + ("in", "Incoming"), + ], + string="Allowed for supply type", + ) + active = fields.Boolean("Active", default=True) + + def name_get(self): + """Show name and sub_type in name""" + result_dict = dict() + for ewaybill_type in self: + name = ewaybill_type.name + name += _(" (Sub-Type: %s)", ewaybill_type.sub_type) + result_dict[ewaybill_type.id] = name + return list(result_dict.items()) diff --git a/addons/l10n_in_edi_ewaybill/models/res_company.py b/addons/l10n_in_edi_ewaybill/models/res_company.py new file mode 100755 index 00000000000..427c97a9222 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/models/res_company.py @@ -0,0 +1,18 @@ +# -*- 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_in_edi_ewaybill_username = fields.Char("E-Waybill (IN) Username", groups="base.group_system") + l10n_in_edi_ewaybill_password = fields.Char("E-Waybill (IN) Password", groups="base.group_system") + l10n_in_edi_ewaybill_auth_validity = fields.Datetime("E-Waybill (IN) Valid Until", groups="base.group_system") + + def _l10n_in_edi_ewaybill_token_is_valid(self): + self.ensure_one() + if self.l10n_in_edi_ewaybill_auth_validity and self.l10n_in_edi_ewaybill_auth_validity > fields.Datetime.now(): + return True + return False diff --git a/addons/l10n_in_edi_ewaybill/models/res_config_settings.py b/addons/l10n_in_edi_ewaybill/models/res_config_settings.py new file mode 100755 index 00000000000..1eacd3b94dd --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/models/res_config_settings.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields, _ +from odoo.exceptions import UserError +from odoo.tools import html_escape + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + l10n_in_edi_ewaybill_username = fields.Char("Indian EDI Stock username", + related="company_id.l10n_in_edi_ewaybill_username", readonly=False) + l10n_in_edi_ewaybill_password = fields.Char("Indian EDI Stock password", + related="company_id.l10n_in_edi_ewaybill_password", readonly=False) + + def l10n_in_edi_ewaybill_test(self): + response = self.env["account.edi.format"]._l10n_in_edi_ewaybill_authenticate(self.company_id) + if response.get("error") or not self.company_id.sudo()._l10n_in_edi_ewaybill_token_is_valid(): + error_message = _("Incorrect username or password, or the GST number on company does not match.") + if response.get("error"): + error_message = "\n".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in response["error"]]) + raise UserError(error_message) diff --git a/addons/l10n_in_edi_ewaybill/security/ir.model.access.csv b/addons/l10n_in_edi_ewaybill/security/ir.model.access.csv new file mode 100755 index 00000000000..6861e132204 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +l10n_in_edi_ewaybill.access_l10n_in_ewaybill_type,access_l10n_in_ewaybill_type,l10n_in_edi_ewaybill.model_l10n_in_ewaybill_type,base.group_user,1,0,0,0 diff --git a/addons/l10n_in_edi_ewaybill/tests/__init__.py b/addons/l10n_in_edi_ewaybill/tests/__init__.py new file mode 100644 index 00000000000..711dc501eac --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_edi_ewaybill_json diff --git a/addons/l10n_in_edi_ewaybill/tests/test_edi_ewaybill_json.py b/addons/l10n_in_edi_ewaybill/tests/test_edi_ewaybill_json.py new file mode 100644 index 00000000000..82c4473e448 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/tests/test_edi_ewaybill_json.py @@ -0,0 +1,115 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo.addons.l10n_in_edi.tests.test_edi_json import TestEdiJson +from odoo.tests import tagged + + +@tagged("post_install_l10n", "post_install", "-at_install") +class TestEdiEwaybillJson(TestEdiJson): + + def test_edi_json(self): + (self.invoice + self.invoice_full_discount + self.invoice_zero_qty).write({ + "l10n_in_type_id": self.env.ref("l10n_in_edi_ewaybill.type_tax_invoice_sub_type_supply"), + "l10n_in_distance": 20, + "l10n_in_mode": "1", + "l10n_in_vehicle_no": "GJ11AA1234", + "l10n_in_vehicle_type": "R", + }) + json_value = self.env["account.edi.format"]._l10n_in_edi_ewaybill_generate_json(self.invoice) + expected = { + "supplyType": "O", + "docType": "INV", + "subSupplyType": "1", + "transactionType": 1, + "transDistance": "20", + "transMode": "1", + "vehicleNo": "GJ11AA1234", + "vehicleType": "R", + "docNo": "INV/2019/00001", + "docDate": "01/01/2019", + "fromGstin": "36AABCT1332L011", + "fromTrdName": "company_1_data", + "fromAddr1": "Block no. 401", + "fromAddr2": "Street 2", + "fromPlace": "City 1", + "fromPincode": 500001, + "fromStateCode": 36, + "actFromStateCode": 36, + "toGstin": "36BBBFF5679L8ZR", + "toTrdName": "partner_a", + "toAddr1": "Block no. 401", + "toAddr2": "Street 2", + "toPlace": "City 2", + "toPincode": 500001, + "actToStateCode": 36, + "toStateCode": 36, + "itemList": [ + { + "productName": "product_a", + "hsnCode": "01111", + "productDesc": "product_a", + "quantity": 1.0, + "qtyUnit": "UNT", + "taxableAmount": 900.0, + "cgstRate": 2.5, + "sgstRate": 2.5 + }, + { + "productName": "product_with_cess", + "hsnCode": "02222", + "productDesc": "product_with_cess", + "quantity": 1.0, + "qtyUnit": "UNT", + "taxableAmount": 900.0, + "cgstRate": 6.0, + "sgstRate": 6.0, + "cessRate": 5.0 + } + ], + "totalValue": 1800.0, + "cgstValue": 76.5, + "sgstValue": 76.5, + "igstValue": 0.0, + "cessValue": 45.0, + "cessNonAdvolValue": 1.59, + "otherValue": 0.0, + "totInvValue": 1999.59 + } + self.assertDictEqual(json_value, expected, "Indian EDI send json value is not matched") + + #=================================== Full discount test ===================================== + json_value = self.env["account.edi.format"]._l10n_in_edi_ewaybill_generate_json(self.invoice_full_discount) + expected.update({ + "docNo": "INV/2019/00002", + "itemList": [{ + "productName": "product_a", "hsnCode": "01111", "productDesc": "product_a", "quantity": 1.0, + "qtyUnit": "UNT", "taxableAmount": 0.0, "cgstRate": 0.0, "sgstRate": 0.0 + }], + "totalValue": 0.0, + "cgstValue": 0.0, + "sgstValue": 0.0, + "igstValue": 0.0, + "cessValue": 0.0, + "cessNonAdvolValue": 0.00, + "otherValue": 0.0, + "totInvValue": 0.0 + }) + self.assertDictEqual(json_value, expected, "Indian EDI with 100% discount sent json value is not matched") + + #=================================== Zero quantity test ============================================= + json_value = self.env["account.edi.format"]._l10n_in_edi_ewaybill_generate_json(self.invoice_zero_qty) + expected.update({ + "docNo": "INV/2019/00003", + "itemList": [{ + "productName": "product_a", "hsnCode": "01111", "productDesc": "product_a", "quantity": 0.0, + "qtyUnit": "UNT", "taxableAmount": 0.0, "cgstRate": 0.0, "sgstRate": 0.0 + }], + "totalValue": 0.0, + "cgstValue": 0.0, + "sgstValue": 0.0, + "igstValue": 0.0, + "cessValue": 0.0, + "cessNonAdvolValue": 0.00, + "otherValue": 0.0, + "totInvValue": 0.0 + }) + self.assertDictEqual(json_value, expected, "Indian EDI with 0(zero) quantity sent json value is not matched") diff --git a/addons/l10n_in_edi_ewaybill/views/account_move_views.xml b/addons/l10n_in_edi_ewaybill/views/account_move_views.xml new file mode 100755 index 00000000000..b2733caaea8 --- /dev/null +++ b/addons/l10n_in_edi_ewaybill/views/account_move_views.xml @@ -0,0 +1,59 @@ + + + + account.move.form.inherit.l10n.in.ewaybill + account.move + + + + +