mirror of
https://github.com/bringout/oca-ocb-l10n_asia-pacific.git
synced 2026-04-25 13:22:03 +02:00
19.0 vanilla
This commit is contained in:
parent
7dc55599c6
commit
7f43bbbfcc
650 changed files with 45260 additions and 33436 deletions
|
|
@ -5,12 +5,10 @@ Indian - E-invoicing
|
|||
====================
|
||||
To submit invoicing 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-invoice portal.
|
||||
Step 2: Switch to company related to that GST number
|
||||
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: <https://service.odoo.co.in/einvoice_create_api_user>
|
||||
|
||||
|
||||
|
|
@ -22,39 +20,15 @@ pip install odoo-bringout-oca-ocb-l10n_in_edi
|
|||
|
||||
## Dependencies
|
||||
|
||||
This addon depends on:
|
||||
- account_edi
|
||||
- l10n_in
|
||||
- iap
|
||||
|
||||
## Manifest Information
|
||||
|
||||
- **Name**: Indian - E-invoicing
|
||||
- **Version**: 1.03.00
|
||||
- **Category**: Accounting/Localizations/EDI
|
||||
- **License**: LGPL-3
|
||||
- **Installable**: True
|
||||
|
||||
## Source
|
||||
|
||||
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `l10n_in_edi`.
|
||||
- Repository: https://github.com/OCA/OCB
|
||||
- Branch: 19.0
|
||||
- Path: addons/l10n_in_edi
|
||||
|
||||
## License
|
||||
|
||||
This package maintains the original LGPL-3 license from the upstream Odoo project.
|
||||
|
||||
## Documentation
|
||||
|
||||
- Overview: doc/OVERVIEW.md
|
||||
- Architecture: doc/ARCHITECTURE.md
|
||||
- Models: doc/MODELS.md
|
||||
- Controllers: doc/CONTROLLERS.md
|
||||
- Wizards: doc/WIZARDS.md
|
||||
- Reports: doc/REPORTS.md
|
||||
- Security: doc/SECURITY.md
|
||||
- Install: doc/INSTALL.md
|
||||
- Usage: doc/USAGE.md
|
||||
- Configuration: doc/CONFIGURATION.md
|
||||
- Dependencies: doc/DEPENDENCIES.md
|
||||
- Troubleshooting: doc/TROUBLESHOOTING.md
|
||||
- FAQ: doc/FAQ.md
|
||||
This package preserves the original LGPL-3 license.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,2 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
|
|
|||
|
|
@ -1,38 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
"name": """Indian - E-invoicing""",
|
||||
"version": "1.03.00",
|
||||
"icon": "/l10n_in/static/description/icon.png",
|
||||
"category": "Accounting/Localizations/EDI",
|
||||
"depends": [
|
||||
'name': "Indian - E-invoicing",
|
||||
'version': "1.03.00",
|
||||
'countries': ['in'],
|
||||
'category': "Accounting/Localizations/EDI",
|
||||
'depends': [
|
||||
"account_edi",
|
||||
"l10n_in",
|
||||
"iap",
|
||||
],
|
||||
"description": """
|
||||
'description': """
|
||||
Indian - E-invoicing
|
||||
====================
|
||||
To submit invoicing 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-invoice portal.
|
||||
Step 2: Switch to company related to that GST number
|
||||
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: <https://service.odoo.co.in/einvoice_create_api_user>
|
||||
""",
|
||||
"data": [
|
||||
"data/account_edi_data.xml",
|
||||
"views/res_config_settings_views.xml",
|
||||
"views/edi_pdf_report.xml",
|
||||
"views/account_move_views.xml",
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/account_move_views.xml',
|
||||
'views/edi_pdf_report.xml',
|
||||
'views/res_config_settings_views.xml',
|
||||
'wizard/l10n_in_edi_cancel_views.xml',
|
||||
],
|
||||
"demo": [
|
||||
'demo': [
|
||||
"demo/demo_company.xml",
|
||||
],
|
||||
"installable": True,
|
||||
# only applicable for taxpayers turnover higher than Rs.50 crore so auto_install is False
|
||||
"license": "LGPL-3",
|
||||
'installable': True,
|
||||
'author': "Odoo S.A.",
|
||||
'license': "LGPL-3",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="edi_in_einvoice_json_1_03" model="account.edi.format">
|
||||
<field name="name">E-Invoice (IN)</field>
|
||||
<field name="code">in_einvoice_1_03</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
-- disable l10n_in_edi integration
|
||||
UPDATE res_company
|
||||
SET l10n_in_edi_production_env = false,
|
||||
l10n_in_edi_username = NULL,
|
||||
SET l10n_in_edi_username = NULL,
|
||||
l10n_in_edi_password = NULL,
|
||||
l10n_in_edi_token = NULL,
|
||||
l10n_in_edi_token_validity = NULL;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- This is testing credentials -->
|
||||
<record id="l10n_in.demo_company_in" model="res.company">
|
||||
<record id="base.demo_company_in" model="res.company">
|
||||
<field name="l10n_in_edi_feature">True</field>
|
||||
<field name="l10n_in_edi_username">MGSTTEST</field>
|
||||
<field name="l10n_in_edi_password">mgst@123</field>
|
||||
</record>
|
||||
|
|
|
|||
633
odoo-bringout-oca-ocb-l10n_in_edi/l10n_in_edi/i18n/hi.po
Normal file
633
odoo-bringout-oca-ocb-l10n_in_edi/l10n_in_edi/i18n/hi.po
Normal file
|
|
@ -0,0 +1,633 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * l10n_in_edi
|
||||
#
|
||||
# Weblate <noreply-mt-weblate@weblate.org>, 2025.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 19.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-02-20 19:05+0000\n"
|
||||
"PO-Revision-Date: 2025-11-17 03:12+0000\n"
|
||||
"Last-Translator: Weblate <noreply-mt-weblate@weblate.org>\n"
|
||||
"Language-Team: Hindi <https://translate.odoo.com/projects/odoo-19-l10n/"
|
||||
"l10n_in_edi/hi/>\n"
|
||||
"Language: hi\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n==0 || n==1);\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"\n"
|
||||
"\n"
|
||||
"You must contact your system administrator to update the GSP."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- City required min 3 and max 100 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "- Email: invalid or longer than 100 characters."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "- Phone number: must be 10–12 digits."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- State TIN Number must be exactly 2 digits."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- State required min 3 and max 50 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- Street required min 3 and max 100 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "- Street2: must be 3–100 characters."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- ZIP code required 6 digits ranging from 100000 to 999999"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_einvoice_report_invoice_document_inherit
|
||||
msgid "<strong>Acknowledgement</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_einvoice_report_invoice_document_inherit
|
||||
msgid "<strong>IRN:</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_config_settings.py:0
|
||||
msgid "API credentials validated successfully"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_account_move_send
|
||||
msgid "Account Move Send"
|
||||
msgstr "अकाउंट मूव सेंड"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid ""
|
||||
"Are you sure you want to cancel this invoice without waiting for the EDI "
|
||||
"document to be canceled?"
|
||||
msgstr "क्या आप इनवॉइस को ईडीआई दस्तावेज़ के रद्द हुए बिना ही रद्द करना चाहते हैं?"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr "अटैचमेंट"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
#: model:ir.model,name:l10n_in_edi.model_l10n_in_edi_cancel
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.view_ewaybill_cancel_form
|
||||
msgid "Cancel E-Invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__cancel_reason
|
||||
msgid "Cancel Reason"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__cancel_remarks
|
||||
msgid "Cancel Remarks"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_status__cancelled
|
||||
msgid "Cancelled"
|
||||
msgstr "रद्द किया गया"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid "Check Company Data"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid "Check Invoice Line(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Check Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "Check Partner Data"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_res_company
|
||||
msgid "Companies"
|
||||
msgstr "कंपनियां"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid ""
|
||||
"Companies should have a complete address, verify their Street, City, State, "
|
||||
"Country and Zip code."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr "कॉन्फ़िगरेशन सेटिंग"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_res_partner
|
||||
msgid "Contact"
|
||||
msgstr "संपर्क"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,website_form_label:l10n_in_edi.model_res_partner
|
||||
msgid "Create a Customer"
|
||||
msgstr "ग्राहक बनाएं"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__create_uid
|
||||
msgid "Created by"
|
||||
msgstr "इन्होंने बनाया"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__create_date
|
||||
msgid "Created on"
|
||||
msgstr "इस तारीख को बनाया गया"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__2
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__2
|
||||
msgid "Data Entry Mistake"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.view_ewaybill_cancel_form
|
||||
msgid "Discard"
|
||||
msgstr "खारिज करें"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_line__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_send__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_ir_attachment__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_partner__display_name
|
||||
msgid "Display Name"
|
||||
msgstr "डिस्प्ले का नाम"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "Download EDI JSON"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__1
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__1
|
||||
msgid "Duplicate"
|
||||
msgstr "नकली"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Duplicate IRN found for this invoice, but the buyer details or invoice "
|
||||
"values do not match."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"E-Invoice has been cancelled successfully. Cancellation Reason: %(reason)s "
|
||||
"and Cancellation Remark: %(remark)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_attachment_id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_attachment_id
|
||||
msgid "E-Invoice(IN) Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_cancel_reason
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_cancel_reason
|
||||
msgid "E-Invoice(IN) Cancel Reason"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_cancel_remarks
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_cancel_remarks
|
||||
msgid "E-Invoice(IN) Cancel Remarks"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_content
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_content
|
||||
msgid "E-Invoice(IN) Content"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_attachment_file
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_attachment_file
|
||||
msgid "E-Invoice(IN) File"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_password
|
||||
msgid "E-invoice (IN) Password"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_token
|
||||
msgid "E-invoice (IN) Token"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_username
|
||||
msgid "E-invoice (IN) Username"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_token_validity
|
||||
msgid "E-invoice (IN) Valid Until"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "E-invoice submitted successfully."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_send.py:0
|
||||
msgid "E-invoicing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "EDI Cancel Reason"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "EDI Cancel Remarks"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Ensure GST Number set on company setting and API are Verified."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_send.py:0
|
||||
msgid "Error when sending the invoice to government:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Following:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "Force Cancel"
|
||||
msgstr "जबरदस्ती रद्द करें"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Force cancelled %(invoice)s by %(username)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_line__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_send__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_ir_attachment__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_partner__id
|
||||
msgid "ID"
|
||||
msgstr "आईडी"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_config_settings.py:0
|
||||
msgid ""
|
||||
"Incorrect username or password, or the GST number on company does not match."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_status
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_status
|
||||
msgid "India E-Invoice Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_edi_inherit_account_move_search_view
|
||||
msgid "Indian E-Invoices In Error"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_edi_inherit_account_move_search_view
|
||||
msgid "Indian E-Invoices To Send"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_feature
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__l10n_in_edi_feature
|
||||
msgid "Indian E-Invoicing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_edi_inherit_account_move_search_view
|
||||
msgid "Indian E-invoice status"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__l10n_in_edi_password
|
||||
msgid "Indian EDI password"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__l10n_in_edi_username
|
||||
msgid "Indian EDI username"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__move_id
|
||||
msgid "Invoice"
|
||||
msgstr "बीजक"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Invoice number should not be more than 16 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_account_move
|
||||
msgid "Journal Entry"
|
||||
msgstr "जर्नल एंट्री"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_account_move_line
|
||||
msgid "Journal Item"
|
||||
msgstr "जर्नल आइटम"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_error
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_error
|
||||
msgid "L10N In Edi Error"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr "इन्होंने आखिरी बार अपडेट किया"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr "आखिरी बार अपडेट हुआ"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid ""
|
||||
"Missing or invalid HSN/SAC code: Ensure that invoice lines contain 4, 6 or 8 "
|
||||
"digits"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid "Negative discount is not allowed"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Negative lines will be decreased from positive invoice lines having the same "
|
||||
"taxes and HSN code"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__3
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__3
|
||||
msgid "Order Cancelled"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__4
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__4
|
||||
msgid "Others"
|
||||
msgstr "अन्य"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid ""
|
||||
"Partners should have a complete address, verify their Street, City, State, "
|
||||
"Country and Zip code."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Password"
|
||||
msgstr "पासवर्ड"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Retrying to send cancellation request for E-Invoice to government portal."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Retrying to send your E-Invoice to government portal."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_send.py:0
|
||||
msgid ""
|
||||
"Send the e-invoice json to the Indian Invoice Registration Portal (IRP)."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_status__sent
|
||||
msgid "Sent"
|
||||
msgstr "भेजा गया"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid ""
|
||||
"Set an appropriate GST tax on invoice lines (if it's zero rated or nil rated "
|
||||
"then apply it too)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Somehow this invoice had been cancelled to government before.%(br)sNormally, "
|
||||
"this should not happen too often%(br)sJust verify by logging into government "
|
||||
"website %(link)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Somehow this invoice has been submited to government before.%(br)sNormally, "
|
||||
"this should not happen too often%(br)sJust verify value of invoice by upload "
|
||||
"json to government website %(link)s."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "This electronic document is being processed already."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_status__to_send
|
||||
msgid "To Send"
|
||||
msgstr "भेजने के लिए"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid ""
|
||||
"Unable to connect to the online E-invoice service. The web service may be "
|
||||
"temporary down. Please try again in a moment."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Unable to connect to the online E-invoice service.The web service may be "
|
||||
"temporary down. Please try again in a moment."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Verify Username and Password"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "View %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid "View Companies"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid "View Invoice Line(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "View Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "View Partners"
|
||||
msgstr "पार्टनर देखें"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/ir_attachment.py:0
|
||||
msgid "You can't unlink an attachment that you received from the government"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "here"
|
||||
msgstr "क्लिक करें"
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"⚠️ Important Notice – GSP Deprecation \n"
|
||||
"The currently selected GSP (Tera Soft) will be deprecated soon.\n"
|
||||
"To ensure uninterrupted e-Invoice and E-way operations, please switch to BVM "
|
||||
"GSP as per the"
|
||||
msgstr ""
|
||||
|
|
@ -4,10 +4,10 @@
|
|||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 16.0+e\n"
|
||||
"Project-Id-Version: Odoo Server 19.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-08-20 11:49+0000\n"
|
||||
"PO-Revision-Date: 2025-08-20 11:49+0000\n"
|
||||
"POT-Creation-Date: 2026-02-20 19:05+0000\n"
|
||||
"PO-Revision-Date: 2026-02-20 19:05+0000\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
|
@ -17,70 +17,64 @@ msgstr ""
|
|||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"\n"
|
||||
"\n"
|
||||
"You must contact your system administrator to update the GSP."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- City required min 3 and max 100 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "- Email address should be valid and not more then 100 characters"
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "- Email: invalid or longer than 100 characters."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "- Mobile number should be minimum 10 or maximum 12 digits"
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "- Phone number: must be 10–12 digits."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- State TIN Number must be exactly 2 digits."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- State required min 3 and max 50 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- Street required min 3 and max 100 characters"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "- Street2 should be min 3 and max 100 characters"
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "- Street2: must be 3–100 characters."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "- Zip code required 6 digits"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid ""
|
||||
"<span class=\"o_form_label\">Setup E-invoice</span>\n"
|
||||
" <span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-specific.\" aria-label=\"Values set here are company-specific.\" groups=\"base.group_multi_company\" role=\"img\"/>"
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "- ZIP code required 6 digits ranging from 100000 to 999999"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_einvoice_report_invoice_document_inherit
|
||||
msgid "<strong>Acknowledgement:</strong>"
|
||||
msgid "<strong>Acknowledgement</strong>"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
|
|
@ -91,39 +85,71 @@ msgstr ""
|
|||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "API credentials validated successfully"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_account_move_send
|
||||
msgid "Account Move Send"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid ""
|
||||
"Are you sure you want to cancel this invoice without waiting for the EDI "
|
||||
"document to be canceled?"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_ir_attachment
|
||||
msgid "Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "Buy Credits"
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
#: model:ir.model,name:l10n_in_edi.model_l10n_in_edi_cancel
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.view_ewaybill_cancel_form
|
||||
msgid "Cancel E-Invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Buy credits"
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__cancel_reason
|
||||
msgid "Cancel Reason"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_cancel_reason
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_cancel_reason
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_payment__l10n_in_edi_cancel_reason
|
||||
msgid "Cancel reason"
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__cancel_remarks
|
||||
msgid "Cancel Remarks"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_cancel_remarks
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_cancel_remarks
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_payment__l10n_in_edi_cancel_remarks
|
||||
msgid "Cancel remarks"
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_status__cancelled
|
||||
msgid "Cancelled"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Check the"
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid "Check Company Data"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid "Check Invoice Line(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Check Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "Check Partner Data"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
|
|
@ -131,31 +157,123 @@ msgstr ""
|
|||
msgid "Companies"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid ""
|
||||
"Companies should have a complete address, verify their Street, City, State, "
|
||||
"Country and Zip code."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_res_config_settings
|
||||
msgid "Config Settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid ""
|
||||
"Costs 1 credit per transaction. Free 200 credits will be available for the "
|
||||
"first time."
|
||||
#: model:ir.model,name:l10n_in_edi.model_res_partner
|
||||
msgid "Contact"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,website_form_label:l10n_in_edi.model_res_partner
|
||||
msgid "Create a Customer"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__create_uid
|
||||
msgid "Created by"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__create_date
|
||||
msgid "Created on"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__2
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__2
|
||||
msgid "Data Entry Mistake"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.view_ewaybill_cancel_form
|
||||
msgid "Discard"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_line__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_send__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_ir_attachment__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__display_name
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_partner__display_name
|
||||
msgid "Display Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Documentation"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "Download EDI JSON"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__1
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__1
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_production_env
|
||||
msgid "E-invoice (IN) Is production OSE environment"
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Duplicate IRN found for this invoice, but the buyer details or invoice "
|
||||
"values do not match."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"E-Invoice has been cancelled successfully. Cancellation Reason: %(reason)s "
|
||||
"and Cancellation Remark: %(remark)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_attachment_id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_attachment_id
|
||||
msgid "E-Invoice(IN) Attachment"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_cancel_reason
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_cancel_reason
|
||||
msgid "E-Invoice(IN) Cancel Reason"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_cancel_remarks
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_cancel_remarks
|
||||
msgid "E-Invoice(IN) Cancel Remarks"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_content
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_content
|
||||
msgid "E-Invoice(IN) Content"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_attachment_file
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_attachment_file
|
||||
msgid "E-Invoice(IN) File"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
|
|
@ -179,55 +297,100 @@ msgid "E-invoice (IN) Valid Until"
|
|||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_show_cancel
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_show_cancel
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_payment__l10n_in_edi_show_cancel
|
||||
msgid "E-invoice(IN) is sent?"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_account_edi_format
|
||||
msgid "EDI format"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,help:l10n_in_edi.field_res_company__l10n_in_edi_production_env
|
||||
#: model:ir.model.fields,help:l10n_in_edi.field_res_config_settings__l10n_in_edi_production_env
|
||||
msgid "Enable the use of production credentials"
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "E-invoice submitted successfully."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/account_move_send.py:0
|
||||
msgid "E-invoicing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "EDI Cancel Reason"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "EDI Cancel Remarks"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Ensure GST Number set on company setting and API are Verified."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Go to Company"
|
||||
#: code:addons/l10n_in_edi/models/account_move_send.py:0
|
||||
msgid "Error when sending the invoice to government:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "HSN code is not set in product %s"
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Following:"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.invoice_form_inherit_l10n_in_edi
|
||||
msgid "Force Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Force cancelled %(invoice)s by %(username)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_line__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move_send__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_ir_attachment__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__id
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_partner__id
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/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
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__l10n_in_edi_production_env
|
||||
msgid "Indian EDI Testing Environment"
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_status
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_status
|
||||
msgid "India E-Invoice Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_edi_inherit_account_move_search_view
|
||||
msgid "Indian E-Invoices In Error"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_edi_inherit_account_move_search_view
|
||||
msgid "Indian E-Invoices To Send"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_company__l10n_in_edi_feature
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_res_config_settings__l10n_in_edi_feature
|
||||
msgid "Indian E-Invoicing"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.l10n_in_edi_inherit_account_move_search_view
|
||||
msgid "Indian E-invoice status"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
|
|
@ -241,30 +404,13 @@ msgid "Indian EDI username"
|
|||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Indian Electronic Invoicing"
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__move_id
|
||||
msgid "Invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "Invalid HSN Code (%s) in product %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Invoice lines having a negative amount are not allowed to generate the IRN. "
|
||||
"Please create a credit note instead."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "Invoice number should not be more than 16 characters"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -273,23 +419,69 @@ msgstr ""
|
|||
msgid "Journal Entry"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model,name:l10n_in_edi.model_account_move_line
|
||||
msgid "Journal Item"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_bank_statement_line__l10n_in_edi_error
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_account_move__l10n_in_edi_error
|
||||
msgid "L10N In Edi Error"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__write_uid
|
||||
msgid "Last Updated by"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields,field_description:l10n_in_edi.field_l10n_in_edi_cancel__write_date
|
||||
msgid "Last Updated on"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "Negative discount is not allowed, set in line %s"
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid ""
|
||||
"Missing or invalid HSN/SAC code: Ensure that invoice lines contain 4, 6 or 8"
|
||||
" digits"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid "Negative discount is not allowed"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Negative lines will be decreased from positive invoice lines having the same"
|
||||
" taxes and HSN code"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__3
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__3
|
||||
msgid "Order Cancelled"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_cancel_reason__4
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__l10n_in_edi_cancel__cancel_reason__4
|
||||
msgid "Others"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid ""
|
||||
"Partners should have a complete address, verify their Street, City, State, "
|
||||
"Country and Zip code."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Password"
|
||||
|
|
@ -297,73 +489,77 @@ msgstr ""
|
|||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "Please buy more credits and retry: "
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "Please enter a GST number in company."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "Production Environment"
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Retrying to send cancellation request for E-Invoice to government portal."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid "Retrying EDI processing for the following documents:%s"
|
||||
msgid "Retrying to send your E-Invoice to government portal."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/account_move_send.py:0
|
||||
msgid ""
|
||||
"Set an appropriate GST tax on line \"%s\" (if it's zero rated or nil rated "
|
||||
"then select it also)"
|
||||
"Send the e-invoice json to the Indian Invoice Registration Portal (IRP)."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_status__sent
|
||||
msgid "Sent"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid ""
|
||||
"Somehow this invoice had been cancelled to government before.<br/>Normally, "
|
||||
"this should not happen too often<br/>Just verify by logging into government "
|
||||
"website <a href='https://einvoice1.gst.gov.in'>here<a>."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Somehow this invoice had been submited to government before.<br/>Normally, "
|
||||
"this should not happen too often<br/>Just verify value of invoice by uploade"
|
||||
" json to government website <a "
|
||||
"href='https://einvoice1.gst.gov.in/Others/VSignedInvoice'>here<a>."
|
||||
"Set an appropriate GST tax on invoice lines (if it's zero rated or nil rated"
|
||||
" then apply it too)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
#, python-format
|
||||
msgid ""
|
||||
"To cancel E-invoice set cancel reason and remarks at Other info tab in invoices: \n"
|
||||
"%s"
|
||||
"Somehow this invoice had been cancelled to government before.%(br)sNormally,"
|
||||
" this should not happen too often%(br)sJust verify by logging into "
|
||||
"government website %(link)s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Somehow this invoice has been submited to government before.%(br)sNormally, "
|
||||
"this should not happen too often%(br)sJust verify value of invoice by upload"
|
||||
" json to government website %(link)s."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "This electronic document is being processed already."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model:ir.model.fields.selection,name:l10n_in_edi.selection__account_move__l10n_in_edi_status__to_send
|
||||
msgid "To Send"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid ""
|
||||
"Unable to connect to the online E-invoice service. The web service may be "
|
||||
"temporary down. Please try again in a moment."
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"Unable to connect to the online E-invoice service.The web service may be "
|
||||
"temporary down. Please try again in a moment."
|
||||
|
|
@ -381,31 +577,52 @@ msgstr ""
|
|||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "You have insufficient credits to send this document!"
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "View %s"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_config_settings.py:0
|
||||
#, python-format
|
||||
msgid "You must enable production environment to buy credits"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "documentation"
|
||||
#: code:addons/l10n_in_edi/models/res_company.py:0
|
||||
msgid "View Companies"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_edi_format.py:0
|
||||
#, python-format
|
||||
msgid "product is required to get HSN code"
|
||||
#: code:addons/l10n_in_edi/models/account_move_line.py:0
|
||||
msgid "View Invoice Line(s)"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#: model_terms:ir.ui.view,arch_db:l10n_in_edi.res_config_settings_view_form_inherit_l10n_in_edi
|
||||
msgid "to get credentials"
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "View Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/res_partner.py:0
|
||||
msgid "View Partners"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/ir_attachment.py:0
|
||||
msgid "You can't unlink an attachment that you received from the government"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid "here"
|
||||
msgstr ""
|
||||
|
||||
#. module: l10n_in_edi
|
||||
#. odoo-python
|
||||
#: code:addons/l10n_in_edi/models/account_move.py:0
|
||||
msgid ""
|
||||
"⚠️ Important Notice – GSP Deprecation \n"
|
||||
"The currently selected GSP (Tera Soft) will be deprecated soon.\n"
|
||||
"To ensure uninterrupted e-Invoice and E-way operations, please switch to BVM GSP as per the"
|
||||
msgstr ""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import account_edi_format
|
||||
from . import account_move
|
||||
from . import account_move_line
|
||||
from . import account_move_send
|
||||
from . import ir_attachment
|
||||
from . import res_company
|
||||
from . import res_config_settings
|
||||
from . import res_partner
|
||||
|
|
|
|||
|
|
@ -1,736 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import re
|
||||
import json
|
||||
import pytz
|
||||
import markupsafe
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.tools import html_escape, float_is_zero, float_compare
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
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 _is_enabled_by_default_on_journal(self, journal):
|
||||
self.ensure_one()
|
||||
if self.code == "in_einvoice_1_03":
|
||||
return journal.company_id.country_id.code == 'IN'
|
||||
return super()._is_enabled_by_default_on_journal(journal)
|
||||
|
||||
def _get_l10n_in_base_tags(self):
|
||||
return (
|
||||
self.env.ref('l10n_in.tax_tag_base_sgst').ids
|
||||
+ self.env.ref('l10n_in.tax_tag_base_cgst').ids
|
||||
+ self.env.ref('l10n_in.tax_tag_base_igst').ids
|
||||
+ self.env.ref('l10n_in.tax_tag_base_cess').ids
|
||||
+ self.env.ref('l10n_in.tax_tag_zero_rated').ids
|
||||
+ self.env.ref("l10n_in.tax_tag_exempt").ids
|
||||
+ self.env.ref("l10n_in.tax_tag_nil_rated").ids
|
||||
+ self.env.ref("l10n_in.tax_tag_non_gst_supplies").ids
|
||||
)
|
||||
|
||||
def _get_l10n_in_gst_tags(self):
|
||||
return (
|
||||
self.env.ref('l10n_in.tax_tag_base_sgst')
|
||||
+ self.env.ref('l10n_in.tax_tag_base_cgst')
|
||||
+ self.env.ref('l10n_in.tax_tag_base_igst')
|
||||
+ self.env.ref('l10n_in.tax_tag_base_cess')
|
||||
+ self.env.ref('l10n_in.tax_tag_zero_rated')
|
||||
).ids
|
||||
|
||||
def _get_l10n_in_non_taxable_tags(self):
|
||||
return (
|
||||
self.env.ref("l10n_in.tax_tag_exempt")
|
||||
+ self.env.ref("l10n_in.tax_tag_nil_rated")
|
||||
+ self.env.ref("l10n_in.tax_tag_non_gst_supplies")
|
||||
).ids
|
||||
|
||||
def _get_move_applicability(self, move):
|
||||
# EXTENDS account_edi
|
||||
self.ensure_one()
|
||||
if self.code != 'in_einvoice_1_03':
|
||||
return super()._get_move_applicability(move)
|
||||
is_under_gst = any(move_line_tag.id in self._get_l10n_in_gst_tags() for move_line_tag in move.line_ids.tax_tag_ids)
|
||||
if move.is_sale_document(include_receipts=True) and move.country_code == 'IN' and is_under_gst and move.l10n_in_gst_treatment in (
|
||||
"regular",
|
||||
"composition",
|
||||
"overseas",
|
||||
"special_economic_zone",
|
||||
"deemed_export",
|
||||
):
|
||||
return {
|
||||
'post': self._l10n_in_edi_post_invoice,
|
||||
'cancel': self._l10n_in_edi_cancel_invoice,
|
||||
'edi_content': self._l10n_in_edi_xml_invoice_content,
|
||||
}
|
||||
|
||||
def _needs_web_services(self):
|
||||
self.ensure_one()
|
||||
return self.code == "in_einvoice_1_03" or super()._needs_web_services()
|
||||
|
||||
def _l10n_in_edi_xml_invoice_content(self, invoice):
|
||||
return json.dumps(self._l10n_in_edi_generate_invoice_json(invoice)).encode()
|
||||
|
||||
def _l10n_in_edi_extract_digits(self, string):
|
||||
if not string:
|
||||
return string
|
||||
matches = re.findall(r"\d+", string)
|
||||
result = "".join(matches)
|
||||
return result
|
||||
|
||||
def _check_move_configuration(self, move):
|
||||
if self.code != "in_einvoice_1_03":
|
||||
return super()._check_move_configuration(move)
|
||||
error_message = []
|
||||
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}$", move.name):
|
||||
error_message.append(_("Invoice number should not be more than 16 characters"))
|
||||
all_base_tags = self._get_l10n_in_gst_tags() + self._get_l10n_in_non_taxable_tags()
|
||||
for line in move.invoice_line_ids.filtered(lambda line: line.display_type not in ('line_note', 'line_section', 'rounding')):
|
||||
if line.price_subtotal < 0:
|
||||
# Line having a negative amount is not allowed.
|
||||
if not move._l10n_in_edi_is_managing_invoice_negative_lines_allowed():
|
||||
raise ValidationError(_("Invoice lines having a negative amount are not allowed to generate the IRN. "
|
||||
"Please create a credit note instead."))
|
||||
if line.display_type == 'product' and line.discount < 0:
|
||||
error_message.append(_("Negative discount is not allowed, set in line %s", line.name))
|
||||
if not line.tax_tag_ids or not any(move_line_tag.id in all_base_tags for move_line_tag in line.tax_tag_ids):
|
||||
error_message.append(_(
|
||||
"""Set an appropriate GST tax on line "%s" (if it's zero rated or nil rated then select it also)""", line.product_id.name))
|
||||
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"))
|
||||
return error_message
|
||||
|
||||
def _l10n_in_edi_get_iap_buy_credits_message(self, company):
|
||||
url = self.env["iap.account"].get_credits_url(service_name="l10n_in_edi")
|
||||
return markupsafe.Markup("""<p><b>%s</b></p><p>%s <a href="%s">%s</a></p>""") % (
|
||||
_("You have insufficient credits to send this document!"),
|
||||
_("Please buy more credits and retry: "),
|
||||
url,
|
||||
_("Buy Credits")
|
||||
)
|
||||
|
||||
def _l10n_in_edi_post_invoice(self, invoice):
|
||||
generate_json = self._l10n_in_edi_generate_invoice_json(invoice)
|
||||
response = self._l10n_in_edi_generate(invoice.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(invoice.company_id)
|
||||
if not authenticate_response.get("error"):
|
||||
error = []
|
||||
response = self._l10n_in_edi_generate(invoice.company_id, generate_json)
|
||||
if response.get("error"):
|
||||
error = response["error"]
|
||||
error_codes = [e.get("code") for e in error]
|
||||
if "2150" in error_codes:
|
||||
# Get IRN by details in case of IRN is already generated
|
||||
# this happens when timeout from the Government portal but IRN is generated
|
||||
response = self._l10n_in_edi_get_irn_by_details(invoice.company_id, {
|
||||
"doc_type": invoice.move_type == "out_refund" and "CRN" or "INV",
|
||||
"doc_num": invoice.name,
|
||||
"doc_date": invoice.invoice_date and invoice.invoice_date.strftime("%d/%m/%Y") or False,
|
||||
})
|
||||
if not response.get("error"):
|
||||
error = []
|
||||
odoobot = self.env.ref("base.partner_root")
|
||||
invoice.message_post(author_id=odoobot.id, body=_(
|
||||
"Somehow this invoice had been submited to government before." \
|
||||
"<br/>Normally, this should not happen too often" \
|
||||
"<br/>Just verify value of invoice by uploade json to government website " \
|
||||
"<a href='https://einvoice1.gst.gov.in/Others/VSignedInvoice'>here<a>."
|
||||
))
|
||||
if "no-credit" in error_codes:
|
||||
return {invoice: {
|
||||
"success": False,
|
||||
"error": self._l10n_in_edi_get_iap_buy_credits_message(invoice.company_id),
|
||||
"blocking_level": "error",
|
||||
}}
|
||||
elif error:
|
||||
error_message = "<br/>".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in error])
|
||||
return {invoice: {
|
||||
"success": False,
|
||||
"error": error_message,
|
||||
"blocking_level": ("404" in error_codes) and "warning" or "error",
|
||||
}}
|
||||
if not response.get("error"):
|
||||
json_dump = json.dumps(response.get("data"))
|
||||
json_name = "%s_einvoice.json" % (invoice.name.replace("/", "_"))
|
||||
attachment = self.env["ir.attachment"].create({
|
||||
"name": json_name,
|
||||
"raw": json_dump.encode(),
|
||||
"res_model": "account.move",
|
||||
"res_id": invoice.id,
|
||||
"mimetype": "application/json",
|
||||
})
|
||||
return {invoice: {"success": True, "attachment": attachment}}
|
||||
|
||||
def _l10n_in_edi_cancel_invoice(self, invoice):
|
||||
l10n_in_edi_response_json = invoice._get_l10n_in_edi_response_json()
|
||||
cancel_json = {
|
||||
"Irn": l10n_in_edi_response_json.get("Irn"),
|
||||
"CnlRsn": invoice.l10n_in_edi_cancel_reason,
|
||||
"CnlRem": invoice.l10n_in_edi_cancel_remarks,
|
||||
}
|
||||
response = self._l10n_in_edi_cancel(invoice.company_id, cancel_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(invoice.company_id)
|
||||
if not authenticate_response.get("error"):
|
||||
error = []
|
||||
response = self._l10n_in_edi_cancel(invoice.company_id, cancel_json)
|
||||
if response.get("error"):
|
||||
error = response["error"]
|
||||
error_codes = [e.get("code") for e in error]
|
||||
if "9999" in error_codes:
|
||||
response = {}
|
||||
error = []
|
||||
odoobot = self.env.ref("base.partner_root")
|
||||
invoice.message_post(author_id=odoobot.id, body=_(
|
||||
"Somehow this invoice had been cancelled to government before." \
|
||||
"<br/>Normally, this should not happen too often" \
|
||||
"<br/>Just verify by logging into government website " \
|
||||
"<a href='https://einvoice1.gst.gov.in'>here<a>."
|
||||
))
|
||||
if "no-credit" in error_codes:
|
||||
return {invoice: {
|
||||
"success": False,
|
||||
"error": self._l10n_in_edi_get_iap_buy_credits_message(invoice.company_id),
|
||||
"blocking_level": "error",
|
||||
}}
|
||||
if error:
|
||||
error_message = "<br/>".join(["[%s] %s" % (e.get("code"), html_escape(e.get("message"))) for e in error])
|
||||
return {invoice: {
|
||||
"success": False,
|
||||
"error": error_message,
|
||||
"blocking_level": ("404" in error_codes) and "warning" or "error",
|
||||
}}
|
||||
if not response.get("error"):
|
||||
json_dump = json.dumps(response.get("data", {}))
|
||||
json_name = "%s_cancel_einvoice.json" % (invoice.name.replace("/", "_"))
|
||||
attachment = False
|
||||
if json_dump:
|
||||
attachment = self.env["ir.attachment"].create({
|
||||
"name": json_name,
|
||||
"raw": json_dump.encode(),
|
||||
"res_model": "account.move",
|
||||
"res_id": invoice.id,
|
||||
"mimetype": "application/json",
|
||||
})
|
||||
return {invoice: {"success": True, "attachment": attachment}}
|
||||
|
||||
def _l10n_in_validate_partner(self, partner, is_company=False):
|
||||
self.ensure_one()
|
||||
message = []
|
||||
if not re.match("^.{3,100}$", partner.street or ""):
|
||||
message.append(_("- Street required min 3 and max 100 characters"))
|
||||
if partner.street2 and not re.match("^.{3,100}$", partner.street2):
|
||||
message.append(_("- Street2 should be min 3 and max 100 characters"))
|
||||
if not re.match("^.{3,100}$", partner.city or ""):
|
||||
message.append(_("- City required min 3 and max 100 characters"))
|
||||
if partner.country_id.code == "IN" and not re.match("^.{3,50}$", partner.state_id.name or ""):
|
||||
message.append(_("- State required min 3 and max 50 characters"))
|
||||
if (
|
||||
partner.country_id.code == "IN"
|
||||
and not re.match(r"^(?!0+$)([0-9]{2})$", partner.state_id.l10n_in_tin or "")
|
||||
):
|
||||
message.append(_("- State TIN Number must be exactly 2 digits."))
|
||||
if partner.country_id.code == "IN" and not re.match("^[0-9]{6,}$", partner.zip or ""):
|
||||
message.append(_("- Zip code required 6 digits"))
|
||||
if partner.phone and not re.match("^[0-9]{10,12}$",
|
||||
self._l10n_in_edi_extract_digits(partner.phone)
|
||||
):
|
||||
message.append(_("- Mobile number should be minimum 10 or maximum 12 digits"))
|
||||
if partner.email and (
|
||||
not re.match(r"^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$", partner.email)
|
||||
or not re.match("^.{6,100}$", partner.email)
|
||||
):
|
||||
message.append(_("- Email address should be valid and not more then 100 characters"))
|
||||
if message:
|
||||
message.insert(0, "%s" %(partner.display_name))
|
||||
return message
|
||||
|
||||
def _get_l10n_in_edi_saler_buyer_party(self, move):
|
||||
return {
|
||||
"seller_details": move.company_id.partner_id,
|
||||
"dispatch_details": move._l10n_in_get_warehouse_address() or move.company_id.partner_id,
|
||||
"buyer_details": move.partner_id,
|
||||
"ship_to_details": move.partner_shipping_id or move.partner_id,
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _get_l10n_in_edi_partner_details(self, partner, set_vat=True, set_phone_and_email=True,
|
||||
is_overseas=False, pos_state_id=False):
|
||||
"""
|
||||
Create the dictionary based partner details
|
||||
if set_vat is true then, vat(GSTIN) and legal name(LglNm) is added
|
||||
if set_phone_and_email is true then phone and email is add
|
||||
if set_pos is true then state code from partner or passed state_id is added as POS(place of supply)
|
||||
if is_overseas is true then pin is 999999 and GSTIN(vat) is URP and Stcd is .
|
||||
if pos_state_id is passed then we use set POS
|
||||
"""
|
||||
zip_digits = self._l10n_in_edi_extract_digits(partner.zip)
|
||||
partner_details = {
|
||||
"Addr1": partner.street or "",
|
||||
"Loc": partner.city or "",
|
||||
"Pin": zip_digits and int(zip_digits) or "",
|
||||
"Stcd": partner.state_id.l10n_in_tin or "",
|
||||
}
|
||||
if partner.street2:
|
||||
partner_details.update({"Addr2": partner.street2})
|
||||
if set_phone_and_email:
|
||||
if partner.email:
|
||||
partner_details.update({"Em": partner.email})
|
||||
if partner.phone:
|
||||
partner_details.update({"Ph": self._l10n_in_edi_extract_digits(partner.phone)})
|
||||
if pos_state_id:
|
||||
partner_details.update({"POS": pos_state_id.l10n_in_tin or ""})
|
||||
if set_vat:
|
||||
partner_details.update({
|
||||
"LglNm": partner.commercial_partner_id.name,
|
||||
"GSTIN": partner.vat or "URP",
|
||||
})
|
||||
else:
|
||||
partner_details.update({"Nm": partner.name or partner.commercial_partner_id.name})
|
||||
# For no country I would suppose it is India, so not sure this is super right
|
||||
if is_overseas and (not partner.country_id or partner.country_id.code != 'IN'):
|
||||
partner_details.update({
|
||||
"GSTIN": "URP",
|
||||
"Pin": 999999,
|
||||
"Stcd": "96",
|
||||
"POS": "96",
|
||||
})
|
||||
return partner_details
|
||||
|
||||
@api.model
|
||||
def _l10n_in_round_value(self, amount, precision_digits=2):
|
||||
"""
|
||||
This method is call for rounding.
|
||||
If anything is wrong with rounding then we quick fix in method
|
||||
"""
|
||||
value = round(amount, precision_digits)
|
||||
# avoid -0.0
|
||||
return value if value else 0.0
|
||||
|
||||
def _get_l10n_in_edi_line_details(self, index, line, line_tax_details):
|
||||
"""
|
||||
Create the dictionary with line details
|
||||
return {
|
||||
account.move.line('1'): {....},
|
||||
account.move.line('2'): {....},
|
||||
....
|
||||
}
|
||||
"""
|
||||
sign = line.move_id.is_inbound() and -1 or 1
|
||||
tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(line_tax_details.get("tax_details", {}))
|
||||
quantity = line.quantity
|
||||
full_discount_or_zero_quantity = line.discount == 100.00 or float_is_zero(quantity, 3)
|
||||
if full_discount_or_zero_quantity:
|
||||
unit_price_in_inr = line.currency_id._convert(
|
||||
line.price_unit,
|
||||
line.company_currency_id,
|
||||
line.company_id,
|
||||
line.date or fields.Date.context_today(self)
|
||||
)
|
||||
else:
|
||||
unit_price_in_inr = ((sign * line.balance) / (1 - (line.discount / 100))) / quantity
|
||||
|
||||
if unit_price_in_inr < 0 and quantity < 0:
|
||||
# If unit price and quantity both is negative then
|
||||
# We set unit price and quantity as positive because
|
||||
# government does not accept negative in qty or unit price
|
||||
unit_price_in_inr = unit_price_in_inr * -1
|
||||
quantity = quantity * -1
|
||||
return {
|
||||
"SlNo": str(index),
|
||||
"PrdDesc": line.name.replace("\n", ""),
|
||||
"IsServc": line.product_id.type == "service" and "Y" or "N",
|
||||
"HsnCd": self._l10n_in_edi_extract_digits(line.product_id.l10n_in_hsn_code),
|
||||
"Qty": self._l10n_in_round_value(quantity or 0.0, 3),
|
||||
"Unit": line.product_uom_id.l10n_in_code and line.product_uom_id.l10n_in_code.split("-")[0] or "OTH",
|
||||
# Unit price in company currency and tax excluded so its different then price_unit
|
||||
"UnitPrice": self._l10n_in_round_value(unit_price_in_inr, 3),
|
||||
# total amount is before discount
|
||||
"TotAmt": self._l10n_in_round_value(unit_price_in_inr * quantity),
|
||||
"Discount": self._l10n_in_round_value((unit_price_in_inr * quantity) * (line.discount / 100)),
|
||||
"AssAmt": self._l10n_in_round_value((sign * line.balance)),
|
||||
"GstRt": self._l10n_in_round_value(tax_details_by_code.get("igst_rate", 0.00) or (
|
||||
tax_details_by_code.get("cgst_rate", 0.00) + tax_details_by_code.get("sgst_rate", 0.00)), 3),
|
||||
"IgstAmt": self._l10n_in_round_value(tax_details_by_code.get("igst_amount", 0.00)),
|
||||
"CgstAmt": self._l10n_in_round_value(tax_details_by_code.get("cgst_amount", 0.00)),
|
||||
"SgstAmt": self._l10n_in_round_value(tax_details_by_code.get("sgst_amount", 0.00)),
|
||||
"CesRt": self._l10n_in_round_value(tax_details_by_code.get("cess_rate", 0.00), 3),
|
||||
"CesAmt": self._l10n_in_round_value(tax_details_by_code.get("cess_amount", 0.00)),
|
||||
"CesNonAdvlAmt": self._l10n_in_round_value(
|
||||
tax_details_by_code.get("cess_non_advol_amount", 0.00)),
|
||||
"StateCesRt": self._l10n_in_round_value(tax_details_by_code.get("state_cess_rate_amount", 0.00), 3),
|
||||
"StateCesAmt": self._l10n_in_round_value(tax_details_by_code.get("state_cess_amount", 0.00)),
|
||||
"StateCesNonAdvlAmt": self._l10n_in_round_value(
|
||||
tax_details_by_code.get("state_cess_non_advol_amount", 0.00)),
|
||||
"OthChrg": self._l10n_in_round_value(tax_details_by_code.get("other_amount", 0.00)),
|
||||
"TotItemVal": self._l10n_in_round_value(((sign * line.balance) + line_tax_details.get("tax_amount", 0.00))),
|
||||
}
|
||||
|
||||
def _l10n_in_edi_generate_invoice_json_managing_negative_lines(self, invoice, json_payload):
|
||||
"""Set negative lines against positive lines as discount with same HSN code and tax rate
|
||||
|
||||
With negative lines
|
||||
|
||||
product name | hsn code | unit price | qty | discount | total
|
||||
=============================================================
|
||||
product A | 123456 | 1000 | 1 | 100 | 900
|
||||
product B | 123456 | 1500 | 2 | 0 | 3000
|
||||
Discount | 123456 | -300 | 1 | 0 | -300
|
||||
|
||||
Converted to without negative lines
|
||||
|
||||
product name | hsn code | unit price | qty | discount | total
|
||||
=============================================================
|
||||
product A | 123456 | 1000 | 1 | 100 | 900
|
||||
product B | 123456 | 1500 | 2 | 300 | 2700
|
||||
|
||||
totally discounted lines are kept as 0, though
|
||||
"""
|
||||
def discount_group_key(line_vals):
|
||||
return "%s-%s"%(line_vals['HsnCd'], line_vals['GstRt'])
|
||||
|
||||
def put_discount_on(discount_line_vals, other_line_vals):
|
||||
discount = discount_line_vals['AssAmt'] * -1
|
||||
discount_to_allow = other_line_vals['AssAmt']
|
||||
if float_compare(discount_to_allow, discount, precision_rounding=invoice.currency_id.rounding) < 0:
|
||||
# Update discount line, needed when discount is more then max line, in short remaining_discount is not zero
|
||||
discount_line_vals.update({
|
||||
'AssAmt': self._l10n_in_round_value(discount_line_vals['AssAmt'] + other_line_vals['AssAmt']),
|
||||
'IgstAmt': self._l10n_in_round_value(discount_line_vals['IgstAmt'] + other_line_vals['IgstAmt']),
|
||||
'CgstAmt': self._l10n_in_round_value(discount_line_vals['CgstAmt'] + other_line_vals['CgstAmt']),
|
||||
'SgstAmt': self._l10n_in_round_value(discount_line_vals['SgstAmt'] + other_line_vals['SgstAmt']),
|
||||
'CesAmt': self._l10n_in_round_value(discount_line_vals['CesAmt'] + other_line_vals['CesAmt']),
|
||||
'CesNonAdvlAmt': self._l10n_in_round_value(discount_line_vals['CesNonAdvlAmt'] + other_line_vals['CesNonAdvlAmt']),
|
||||
'StateCesAmt': self._l10n_in_round_value(discount_line_vals['StateCesAmt'] + other_line_vals['StateCesAmt']),
|
||||
'StateCesNonAdvlAmt': self._l10n_in_round_value(discount_line_vals['StateCesNonAdvlAmt'] + other_line_vals['StateCesNonAdvlAmt']),
|
||||
'OthChrg': self._l10n_in_round_value(discount_line_vals['OthChrg'] + other_line_vals['OthChrg']),
|
||||
'TotItemVal': self._l10n_in_round_value(discount_line_vals['TotItemVal'] + other_line_vals['TotItemVal']),
|
||||
})
|
||||
other_line_vals.update({
|
||||
'Discount': self._l10n_in_round_value(other_line_vals['Discount'] + discount_to_allow),
|
||||
'AssAmt': 0.00,
|
||||
'IgstAmt': 0.00,
|
||||
'CgstAmt': 0.00,
|
||||
'SgstAmt': 0.00,
|
||||
'CesAmt': 0.00,
|
||||
'CesNonAdvlAmt': 0.00,
|
||||
'StateCesAmt': 0.00,
|
||||
'StateCesNonAdvlAmt': 0.00,
|
||||
'OthChrg': 0.00,
|
||||
'TotItemVal': 0.00,
|
||||
})
|
||||
return False
|
||||
other_line_vals.update({
|
||||
'Discount': self._l10n_in_round_value(other_line_vals['Discount'] + discount),
|
||||
'AssAmt': self._l10n_in_round_value(other_line_vals['AssAmt'] + discount_line_vals['AssAmt']),
|
||||
'IgstAmt': self._l10n_in_round_value(other_line_vals['IgstAmt'] + discount_line_vals['IgstAmt']),
|
||||
'CgstAmt': self._l10n_in_round_value(other_line_vals['CgstAmt'] + discount_line_vals['CgstAmt']),
|
||||
'SgstAmt': self._l10n_in_round_value(other_line_vals['SgstAmt'] + discount_line_vals['SgstAmt']),
|
||||
'CesAmt': self._l10n_in_round_value(other_line_vals['CesAmt'] + discount_line_vals['CesAmt']),
|
||||
'CesNonAdvlAmt': self._l10n_in_round_value(other_line_vals['CesNonAdvlAmt'] + discount_line_vals['CesNonAdvlAmt']),
|
||||
'StateCesAmt': self._l10n_in_round_value(other_line_vals['StateCesAmt'] + discount_line_vals['StateCesAmt']),
|
||||
'StateCesNonAdvlAmt': self._l10n_in_round_value(other_line_vals['StateCesNonAdvlAmt'] + discount_line_vals['StateCesNonAdvlAmt']),
|
||||
'OthChrg': self._l10n_in_round_value(other_line_vals['OthChrg'] + discount_line_vals['OthChrg']),
|
||||
'TotItemVal': self._l10n_in_round_value(other_line_vals['TotItemVal'] + discount_line_vals['TotItemVal']),
|
||||
})
|
||||
return True
|
||||
|
||||
discount_lines = []
|
||||
for discount_line in json_payload['ItemList'].copy(): #to be sure to not skip in the loop:
|
||||
if discount_line['AssAmt'] < 0:
|
||||
discount_lines.append(discount_line)
|
||||
json_payload['ItemList'].remove(discount_line)
|
||||
if not discount_lines:
|
||||
return json_payload
|
||||
|
||||
lines_grouped_and_sorted = defaultdict(list)
|
||||
for line in sorted(json_payload['ItemList'], key=lambda i: i['AssAmt'], reverse=True):
|
||||
lines_grouped_and_sorted[discount_group_key(line)].append(line)
|
||||
|
||||
for discount_line in discount_lines:
|
||||
apply_discount_on_lines = lines_grouped_and_sorted.get(discount_group_key(discount_line), [])
|
||||
for apply_discount_on in apply_discount_on_lines:
|
||||
if put_discount_on(discount_line, apply_discount_on):
|
||||
break
|
||||
return json_payload
|
||||
|
||||
def _l10n_in_edi_generate_invoice_json(self, invoice):
|
||||
tax_details = self._l10n_in_prepare_edi_tax_details(invoice)
|
||||
saler_buyer = self._get_l10n_in_edi_saler_buyer_party(invoice)
|
||||
tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(tax_details.get("tax_details", {}))
|
||||
is_intra_state = invoice.l10n_in_state_id == invoice.company_id.state_id
|
||||
is_overseas = invoice.l10n_in_gst_treatment == "overseas"
|
||||
lines = invoice.invoice_line_ids.filtered(lambda line: line.display_type not in ('line_note', 'line_section', 'rounding'))
|
||||
tax_details_per_record = tax_details.get("tax_details_per_record")
|
||||
sign = invoice.is_inbound() and -1 or 1
|
||||
rounding_amount = sum(line.balance for line in invoice.line_ids if line.display_type == 'rounding') * sign
|
||||
json_payload = {
|
||||
"Version": "1.1",
|
||||
"TranDtls": {
|
||||
"TaxSch": "GST",
|
||||
"SupTyp": self._l10n_in_get_supply_type(invoice, tax_details_by_code),
|
||||
"RegRev": tax_details_by_code.get("is_reverse_charge") and "Y" or "N",
|
||||
"IgstOnIntra": is_intra_state and tax_details_by_code.get("igst_amount") and "Y" or "N"},
|
||||
"DocDtls": {
|
||||
"Typ": invoice.move_type == "out_refund" and "CRN" or "INV",
|
||||
"No": invoice.name,
|
||||
"Dt": invoice.invoice_date.strftime("%d/%m/%Y")},
|
||||
"SellerDtls": self._get_l10n_in_edi_partner_details(saler_buyer.get("seller_details")),
|
||||
"BuyerDtls": self._get_l10n_in_edi_partner_details(
|
||||
saler_buyer.get("buyer_details"), pos_state_id=invoice.l10n_in_state_id, is_overseas=is_overseas),
|
||||
"ItemList": [
|
||||
self._get_l10n_in_edi_line_details(index, line, tax_details_per_record.get(line, {}))
|
||||
for index, line in enumerate(lines, start=1)
|
||||
],
|
||||
"ValDtls": {
|
||||
"AssVal": self._l10n_in_round_value(tax_details.get("base_amount")),
|
||||
"CgstVal": self._l10n_in_round_value(tax_details_by_code.get("cgst_amount", 0.00)),
|
||||
"SgstVal": self._l10n_in_round_value(tax_details_by_code.get("sgst_amount", 0.00)),
|
||||
"IgstVal": self._l10n_in_round_value(tax_details_by_code.get("igst_amount", 0.00)),
|
||||
"CesVal": self._l10n_in_round_value((
|
||||
tax_details_by_code.get("cess_amount", 0.00)
|
||||
+ tax_details_by_code.get("cess_non_advol_amount", 0.00)),
|
||||
),
|
||||
"StCesVal": self._l10n_in_round_value((
|
||||
tax_details_by_code.get("state_cess_amount", 0.00)
|
||||
+ tax_details_by_code.get("state_cess_non_advol_amount", 0.00)),
|
||||
),
|
||||
"RndOffAmt": self._l10n_in_round_value(
|
||||
rounding_amount),
|
||||
"TotInvVal": self._l10n_in_round_value(
|
||||
(tax_details.get("base_amount") + tax_details.get("tax_amount") + rounding_amount)),
|
||||
},
|
||||
}
|
||||
if invoice.company_currency_id != invoice.currency_id:
|
||||
json_payload["ValDtls"].update({
|
||||
"TotInvValFc": self._l10n_in_round_value(
|
||||
(tax_details.get("base_amount_currency") + tax_details.get("tax_amount_currency")))
|
||||
})
|
||||
if saler_buyer.get("seller_details") != saler_buyer.get("dispatch_details"):
|
||||
json_payload.update({
|
||||
"DispDtls": self._get_l10n_in_edi_partner_details(saler_buyer.get("dispatch_details"),
|
||||
set_vat=False, set_phone_and_email=False)
|
||||
})
|
||||
if saler_buyer.get("buyer_details") != saler_buyer.get("ship_to_details"):
|
||||
json_payload.update({
|
||||
"ShipDtls": self._get_l10n_in_edi_partner_details(saler_buyer.get("ship_to_details"), is_overseas=is_overseas)
|
||||
})
|
||||
if is_overseas:
|
||||
json_payload.update({
|
||||
"ExpDtls": {
|
||||
"RefClm": tax_details_by_code.get("igst_amount") and "Y" or "N",
|
||||
"ForCur": invoice.currency_id.name,
|
||||
"CntCode": saler_buyer.get("buyer_details").country_id.code or "",
|
||||
}
|
||||
})
|
||||
if invoice.l10n_in_shipping_bill_number:
|
||||
json_payload["ExpDtls"].update({
|
||||
"ShipBNo": invoice.l10n_in_shipping_bill_number,
|
||||
})
|
||||
if invoice.l10n_in_shipping_bill_date:
|
||||
json_payload["ExpDtls"].update({
|
||||
"ShipBDt": invoice.l10n_in_shipping_bill_date.strftime("%d/%m/%Y"),
|
||||
})
|
||||
if invoice.l10n_in_shipping_port_code_id:
|
||||
json_payload["ExpDtls"].update({
|
||||
"Port": invoice.l10n_in_shipping_port_code_id.code
|
||||
})
|
||||
if not invoice._l10n_in_edi_is_managing_invoice_negative_lines_allowed():
|
||||
return json_payload
|
||||
return self._l10n_in_edi_generate_invoice_json_managing_negative_lines(invoice, json_payload)
|
||||
|
||||
@api.model
|
||||
def _l10n_in_prepare_edi_tax_details(self, move, in_foreign=False, filter_invl_to_apply=None):
|
||||
def l10n_in_grouping_key_generator(base_line, tax_values):
|
||||
invl = base_line['record']
|
||||
tax = tax_values['tax_repartition_line'].tax_id
|
||||
tags = tax_values['tax_repartition_line'].tag_ids
|
||||
line_code = "other"
|
||||
if not invl.currency_id.is_zero(tax_values['tax_amount_currency']):
|
||||
if any(tag in tags for tag in self.env.ref("l10n_in.tax_tag_cess")):
|
||||
if tax.amount_type != "percent":
|
||||
line_code = "cess_non_advol"
|
||||
else:
|
||||
line_code = "cess"
|
||||
elif any(tag in tags for tag in self.env.ref("l10n_in.tax_tag_state_cess")):
|
||||
if tax.amount_type != "percent":
|
||||
line_code = "state_cess_non_advol"
|
||||
else:
|
||||
line_code = "state_cess"
|
||||
else:
|
||||
for gst in ["cgst", "sgst", "igst"]:
|
||||
if any(tag in tags for tag in self.env.ref("l10n_in.tax_tag_%s"%(gst))):
|
||||
line_code = gst
|
||||
return {
|
||||
"tax": tax,
|
||||
"base_product_id": invl.product_id,
|
||||
"tax_product_id": invl.product_id,
|
||||
"base_product_uom_id": invl.product_uom_id,
|
||||
"tax_product_uom_id": invl.product_uom_id,
|
||||
"line_code": line_code,
|
||||
}
|
||||
|
||||
def l10n_in_filter_to_apply(base_line, tax_values):
|
||||
if base_line['record'].display_type == 'rounding':
|
||||
return False
|
||||
return True
|
||||
|
||||
return move._prepare_edi_tax_details(
|
||||
filter_to_apply=l10n_in_filter_to_apply,
|
||||
grouping_key_generator=l10n_in_grouping_key_generator,
|
||||
filter_invl_to_apply=filter_invl_to_apply,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _get_l10n_in_tax_details_by_line_code(self, tax_details):
|
||||
l10n_in_tax_details = {}
|
||||
for tax_detail in tax_details.values():
|
||||
if tax_detail["tax"].l10n_in_reverse_charge:
|
||||
l10n_in_tax_details.setdefault("is_reverse_charge", True)
|
||||
l10n_in_tax_details.setdefault("%s_rate" % (tax_detail["line_code"]), tax_detail["tax"].amount)
|
||||
l10n_in_tax_details.setdefault("%s_amount" % (tax_detail["line_code"]), 0.00)
|
||||
l10n_in_tax_details.setdefault("%s_amount_currency" % (tax_detail["line_code"]), 0.00)
|
||||
l10n_in_tax_details["%s_amount" % (tax_detail["line_code"])] += tax_detail["tax_amount"]
|
||||
l10n_in_tax_details["%s_amount_currency" % (tax_detail["line_code"])] += tax_detail["tax_amount_currency"]
|
||||
return l10n_in_tax_details
|
||||
|
||||
def _l10n_in_get_supply_type(self, move, tax_details_by_code):
|
||||
supply_type = "B2B"
|
||||
if move.l10n_in_gst_treatment in ("overseas", "special_economic_zone") and tax_details_by_code.get("igst_amount"):
|
||||
supply_type = move.l10n_in_gst_treatment == "overseas" and "EXPWP" or "SEZWP"
|
||||
elif move.l10n_in_gst_treatment in ("overseas", "special_economic_zone"):
|
||||
supply_type = move.l10n_in_gst_treatment == "overseas" and "EXPWOP" or "SEZWOP"
|
||||
elif move.l10n_in_gst_treatment == "deemed_export":
|
||||
supply_type = "DEXP"
|
||||
return supply_type
|
||||
|
||||
#================================ API methods ===========================
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_no_config_response(self):
|
||||
return {'error': [{
|
||||
'code': '0',
|
||||
'message': _(
|
||||
"Ensure GST Number set on company setting and API are Verified."
|
||||
)}
|
||||
]}
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_get_token(self, company):
|
||||
sudo_company = company.sudo()
|
||||
if sudo_company.l10n_in_edi_username and sudo_company._l10n_in_edi_token_is_valid():
|
||||
return sudo_company.l10n_in_edi_token
|
||||
elif sudo_company.l10n_in_edi_username and sudo_company.l10n_in_edi_password:
|
||||
self._l10n_in_edi_authenticate(company)
|
||||
return sudo_company.l10n_in_edi_token
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_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_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.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": "404",
|
||||
"message": _("Unable to connect to the online E-invoice service."
|
||||
"The web service may be temporary down. Please try again in a moment.")
|
||||
}]
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_authenticate(self, company):
|
||||
params = {"password": company.sudo().l10n_in_edi_password}
|
||||
response = self._l10n_in_edi_connect_to_server(company, url_path="/iap/l10n_in_edi/1/authenticate", params=params)
|
||||
# validity data-time in Indian standard time(UTC+05:30) so remove that gap and store in odoo
|
||||
if "data" in response:
|
||||
tz = pytz.timezone("Asia/Kolkata")
|
||||
local_time = tz.localize(fields.Datetime.to_datetime(response["data"]["TokenExpiry"]))
|
||||
utc_time = local_time.astimezone(pytz.utc)
|
||||
company.sudo().l10n_in_edi_token_validity = fields.Datetime.to_string(utc_time)
|
||||
company.sudo().l10n_in_edi_token = response["data"]["AuthToken"]
|
||||
return response
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_generate(self, company, json_payload):
|
||||
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", params=params)
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_get_irn_by_details(self, company, json_payload):
|
||||
token = self._l10n_in_edi_get_token(company)
|
||||
if not token:
|
||||
return self._l10n_in_edi_no_config_response()
|
||||
params = {
|
||||
"auth_token": token,
|
||||
}
|
||||
params.update(json_payload)
|
||||
return self._l10n_in_edi_connect_to_server(
|
||||
company,
|
||||
url_path="/iap/l10n_in_edi/1/getirnbydocdetails",
|
||||
params=params,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_cancel(self, company, json_payload):
|
||||
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/cancel", params=params)
|
||||
|
|
@ -1,79 +1,843 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import UserError
|
||||
from odoo import Command, _, api, fields, models
|
||||
from odoo.exceptions import AccessError, LockError, UserError
|
||||
from odoo.tools import float_is_zero, float_compare
|
||||
|
||||
from odoo.addons.l10n_in.models.account_invoice import EDI_CANCEL_REASON
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
try:
|
||||
import jwt
|
||||
except ImportError:
|
||||
_logger.warning("The 'jwt' library is not installed. Decoding for duplicate IRN e-invoices will be skipped.")
|
||||
jwt = None
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
l10n_in_edi_cancel_reason = fields.Selection(selection=[
|
||||
("1", "Duplicate"),
|
||||
("2", "Data Entry Mistake"),
|
||||
("3", "Order Cancelled"),
|
||||
("4", "Others"),
|
||||
], string="Cancel reason", copy=False)
|
||||
l10n_in_edi_cancel_remarks = fields.Char("Cancel remarks", copy=False)
|
||||
l10n_in_edi_show_cancel = fields.Boolean(compute="_compute_l10n_in_edi_show_cancel", string="E-invoice(IN) is sent?")
|
||||
# E-Invoice Fields
|
||||
l10n_in_edi_status = fields.Selection(
|
||||
string="India E-Invoice Status",
|
||||
selection=[
|
||||
('to_send', "To Send"),
|
||||
('sent', "Sent"),
|
||||
('cancelled', "Cancelled"),
|
||||
],
|
||||
copy=False,
|
||||
tracking=True,
|
||||
readonly=True,
|
||||
)
|
||||
l10n_in_edi_attachment_id = fields.Many2one(
|
||||
comodel_name='ir.attachment',
|
||||
string="E-Invoice(IN) Attachment",
|
||||
compute=lambda self: self._compute_linked_attachment_id(
|
||||
'l10n_in_edi_attachment_id',
|
||||
'l10n_in_edi_attachment_file'
|
||||
),
|
||||
depends=['l10n_in_edi_attachment_file']
|
||||
)
|
||||
l10n_in_edi_attachment_file = fields.Binary(
|
||||
string="E-Invoice(IN) File",
|
||||
attachment=True,
|
||||
copy=False
|
||||
)
|
||||
l10n_in_edi_cancel_reason = fields.Selection(
|
||||
selection=list(EDI_CANCEL_REASON.items()),
|
||||
string="E-Invoice(IN) Cancel Reason",
|
||||
copy=False
|
||||
)
|
||||
l10n_in_edi_cancel_remarks = fields.Char(
|
||||
string="E-Invoice(IN) Cancel Remarks",
|
||||
copy=False
|
||||
)
|
||||
l10n_in_edi_content = fields.Binary(
|
||||
compute="_compute_l10n_in_edi_content",
|
||||
string="E-Invoice(IN) Content"
|
||||
)
|
||||
l10n_in_edi_error = fields.Html(readonly=True, copy=False)
|
||||
|
||||
@api.depends('edi_document_ids')
|
||||
def _compute_l10n_in_edi_show_cancel(self):
|
||||
for invoice in self:
|
||||
invoice.l10n_in_edi_show_cancel = bool(invoice.edi_document_ids.filtered(
|
||||
lambda i: i.edi_format_id.code == "in_einvoice_1_03"
|
||||
and i.state in ("sent", "to_cancel", "cancelled")
|
||||
))
|
||||
|
||||
def action_retry_edi_documents_error(self):
|
||||
# E-Invoice compute
|
||||
def _compute_l10n_in_edi_content(self):
|
||||
for move in self:
|
||||
if move.country_code == 'IN':
|
||||
move.message_post(body=_(
|
||||
"Retrying EDI processing for the following documents: %(breakline)s %(edi_codes)s",
|
||||
breakline=Markup("<br/>"),
|
||||
edi_codes=Markup("<br/>").join(
|
||||
move.edi_document_ids
|
||||
.filtered(lambda doc: doc.blocking_level == "error")
|
||||
.mapped("edi_format_name")
|
||||
)
|
||||
))
|
||||
return super().action_retry_edi_documents_error()
|
||||
move.l10n_in_edi_content = (
|
||||
move.country_code == 'IN'
|
||||
and move.company_id.l10n_in_edi_feature
|
||||
and move.is_sale_document(include_receipts=True)
|
||||
and move.journal_id.type == 'sale'
|
||||
and base64.b64encode(
|
||||
json.dumps(move._l10n_in_edi_generate_invoice_json()).encode()
|
||||
)
|
||||
)
|
||||
|
||||
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"]
|
||||
def _compute_l10n_in_warning(self):
|
||||
super()._compute_l10n_in_warning()
|
||||
gsp_provider = self.env["ir.config_parameter"].sudo().get_param("l10n_in.gsp_provider", "tera")
|
||||
if gsp_provider != "tera":
|
||||
return
|
||||
indian_invoice = self.filtered(lambda m: m.country_code == 'IN' and m.move_type != 'entry' and
|
||||
m.l10n_in_edi_status in ('to_send', 'sent') and not m.l10n_in_edi_error
|
||||
)
|
||||
edi_error_message = _(
|
||||
"⚠️ Important Notice – GSP Deprecation \n"
|
||||
"The currently selected GSP (Tera Soft) will be deprecated soon.\n"
|
||||
"To ensure uninterrupted e-Invoice and E-way operations, please switch to BVM GSP as per the"
|
||||
)
|
||||
if not self.env.is_admin():
|
||||
edi_error_message += _(
|
||||
"\n\nYou must contact your system administrator to update the GSP."
|
||||
)
|
||||
for move in indian_invoice:
|
||||
l10n_in_warning = move.l10n_in_warning or {}
|
||||
l10n_in_warning['in_edi_gsp_deprecation'] = {
|
||||
'message': edi_error_message,
|
||||
'action_text': _("Documentation"),
|
||||
'action': {
|
||||
'name': _("Documentation"),
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': 'https://www.odoo.com/documentation/19.0/applications/finance/fiscal_localizations/india.html#gsp-configuration',
|
||||
}
|
||||
}
|
||||
move.l10n_in_warning = l10n_in_warning
|
||||
|
||||
# Action Methods
|
||||
def action_export_l10n_in_edi_content_json(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': f'/web/content/account.move/{self.id}/l10n_in_edi_content'
|
||||
}
|
||||
|
||||
def button_request_cancel(self):
|
||||
if self._l10n_in_edi_need_cancel_request():
|
||||
if self.l10n_in_edi_cancel_remarks and self.l10n_in_edi_cancel_reason:
|
||||
return self._l10n_in_edi_cancel_invoice()
|
||||
return self.env['l10n_in_edi.cancel'].with_context(
|
||||
default_move_id=self.id
|
||||
)._get_records_action(name=_("Cancel E-Invoice"), target='new')
|
||||
elif self.l10n_in_edi_status == 'sent':
|
||||
self.message_post(
|
||||
body=_(
|
||||
"Force cancelled %(invoice)s by %(username)s",
|
||||
invoice=self.name, username=self.env.user.name
|
||||
)
|
||||
)
|
||||
self.button_cancel()
|
||||
self.write({
|
||||
'l10n_in_edi_status': 'cancelled',
|
||||
'l10n_in_edi_error': False,
|
||||
})
|
||||
return True
|
||||
return super().button_request_cancel()
|
||||
|
||||
def action_l10n_in_edi_force_cancel(self):
|
||||
self.with_context(l10n_in_edi_force_cancel=True).button_request_cancel()
|
||||
|
||||
def button_draft(self):
|
||||
for move in self:
|
||||
send_l10n_in_edi = move.edi_document_ids.filtered(lambda doc: doc.edi_format_id.code == "in_einvoice_1_03")
|
||||
# check submitted E-invoice does not have reason and remarks
|
||||
# because it's needed to cancel E-invoice
|
||||
if send_l10n_in_edi 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-invoice set cancel reason and remarks at Other info tab in invoices: \n%s",
|
||||
("\n".join(reason_and_remarks_not_set.mapped("name"))),
|
||||
))
|
||||
return super().button_cancel_posted_moves()
|
||||
if move.l10n_in_edi_status == 'to_send':
|
||||
# Avoid resetting sent and cancelled invoices
|
||||
move.l10n_in_edi_status = False
|
||||
if move.l10n_in_edi_error:
|
||||
move.l10n_in_edi_error = False
|
||||
return super().button_draft()
|
||||
|
||||
# Business Methods
|
||||
def _post(self, soft=True):
|
||||
# EXTENDS 'account'
|
||||
res = super()._post(soft=soft)
|
||||
self.filtered(lambda m: m._l10n_in_check_einvoice_eligible()).l10n_in_edi_status = 'to_send'
|
||||
return res
|
||||
|
||||
def _l10n_in_edi_need_cancel_request(self):
|
||||
self.ensure_one()
|
||||
return (
|
||||
self.country_code == 'IN'
|
||||
and not self.env.context.get('l10n_in_edi_force_cancel')
|
||||
and self.is_sale_document()
|
||||
and self.l10n_in_edi_status == 'sent'
|
||||
)
|
||||
|
||||
def _need_cancel_request(self):
|
||||
# EXTENDS 'account'
|
||||
return super()._need_cancel_request() or self._l10n_in_edi_need_cancel_request()
|
||||
|
||||
# Indian E-invoice Business Methods
|
||||
def _l10n_in_check_einvoice_eligible(self):
|
||||
self.ensure_one()
|
||||
return (
|
||||
self.company_id.l10n_in_edi_feature
|
||||
and self.journal_id.type == 'sale'
|
||||
and any(
|
||||
line.display_type == 'product'
|
||||
and line.l10n_in_gstr_section in [
|
||||
'sale_b2b_rcm', 'sale_b2b_regular', 'sale_exp_wp', 'sale_exp_wop',
|
||||
'sale_sez_wp', 'sale_sez_wop', 'sale_deemed_export', 'sale_cdnr_rcm',
|
||||
'sale_cdnr_regular', 'sale_cdnr_deemed_export', 'sale_cdnr_sez_wp',
|
||||
'sale_cdnr_sez_wop', 'sale_cdnur_exp_wp', 'sale_cdnur_exp_wop',
|
||||
]
|
||||
for line in self.line_ids
|
||||
)
|
||||
)
|
||||
|
||||
def _get_l10n_in_edi_response_json(self):
|
||||
self.ensure_one()
|
||||
l10n_in_edi = self.edi_document_ids.filtered(lambda i: i.edi_format_id.code == "in_einvoice_1_03"
|
||||
and i.state in ("sent", "to_cancel"))
|
||||
if l10n_in_edi:
|
||||
return json.loads(l10n_in_edi.sudo().attachment_id.raw.decode("utf-8"))
|
||||
else:
|
||||
return {}
|
||||
if self.l10n_in_edi_attachment_id:
|
||||
return json.loads(self.l10n_in_edi_attachment_id.sudo().raw.decode("utf-8"))
|
||||
return {}
|
||||
|
||||
def _l10n_in_lock_invoice(self):
|
||||
try:
|
||||
self.lock_for_update()
|
||||
except LockError:
|
||||
raise UserError(_('This electronic document is being processed already.')) from None
|
||||
|
||||
def _l10n_in_edi_optional_field_validation(self, partner):
|
||||
"""
|
||||
Validates optional partner fields (e.g., email, phone, street2) for e-invoicing,
|
||||
which are not mandatory in the government API JSON schema. Returns error messages
|
||||
for posting in the chatter.
|
||||
"""
|
||||
message = []
|
||||
if partner.email and (
|
||||
not re.match(r"^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$", partner.email) or
|
||||
not re.match(r"^.{6,100}$", partner.email)
|
||||
):
|
||||
message.append(_("- Email: invalid or longer than 100 characters."))
|
||||
if partner.phone and not re.match(
|
||||
r"^[0-9]{10,12}$",
|
||||
partner.env['account.move']._l10n_in_extract_digits(partner.phone)
|
||||
):
|
||||
message.append(_("- Phone number: must be 10–12 digits."))
|
||||
if partner.street2 and not re.match(r"^.{3,100}$", partner.street2):
|
||||
message.append(_("- Street2: must be 3–100 characters."))
|
||||
return message
|
||||
|
||||
def _l10n_in_edi_send_invoice(self):
|
||||
self.ensure_one()
|
||||
if self.l10n_in_edi_error:
|
||||
# make sure to clear the error before sending again
|
||||
self.l10n_in_edi_error = False
|
||||
self.message_post(body=_(
|
||||
"Retrying to send your E-Invoice to government portal."
|
||||
))
|
||||
partners = set(self._get_l10n_in_seller_buyer_party().values())
|
||||
for partner in partners:
|
||||
if partner_validation := partner._l10n_in_edi_strict_error_validation():
|
||||
self.l10n_in_edi_error = Markup("<br>").join(partner_validation)
|
||||
return {'messages': partner_validation}
|
||||
self._l10n_in_lock_invoice()
|
||||
generate_json = self._l10n_in_edi_generate_invoice_json()
|
||||
response = self._l10n_in_edi_connect_to_server(
|
||||
url_end_point='generate',
|
||||
json_payload=generate_json
|
||||
)
|
||||
if error := response.get('error', {}):
|
||||
odoobot_id = self.env.ref('base.partner_root').id
|
||||
error_codes = [e.get("code") for e in error]
|
||||
if '2150' in error_codes:
|
||||
# Get IRN by details in case of IRN is already generated
|
||||
# this happens when timeout from the Government portal but IRN is generated
|
||||
response = self._l10n_in_edi_connect_to_server(
|
||||
url_end_point='getirnbydocdetails',
|
||||
params={
|
||||
"doc_type": (
|
||||
(self.move_type == "out_refund" and "CRN")
|
||||
or (self.debit_origin_id and "DBN")
|
||||
or "INV"
|
||||
),
|
||||
"doc_num": self.name,
|
||||
"doc_date": self.invoice_date and self.invoice_date.strftime("%d/%m/%Y"),
|
||||
}
|
||||
)
|
||||
mismatch_error = []
|
||||
decoded_response = {}
|
||||
if jwt:
|
||||
try:
|
||||
if data := response.get('data'):
|
||||
signinvoice = data['SignedInvoice']
|
||||
decoded_response = jwt.decode(signinvoice, options={'verify_signature': False})
|
||||
decoded_response = json.loads(decoded_response['data'])
|
||||
except (json.JSONDecodeError, jwt.exceptions.DecodeError) as e:
|
||||
_logger.warning("Failed to decode SignedInvoice JWT payload: %s", str(e))
|
||||
if decoded_response:
|
||||
received_gstin = decoded_response['BuyerDtls']['Gstin']
|
||||
expected_gstin = generate_json['BuyerDtls']['GSTIN']
|
||||
received_total_invoice_value = decoded_response['ValDtls']['TotInvVal']
|
||||
expected_total_invoice_value = generate_json['ValDtls']['TotInvVal']
|
||||
# Check for mismatch between decoded and expected e-invoice details:
|
||||
# - Buyer GSTIN must match
|
||||
# - Total Invoice Value must be within the allowed government tolerance range:
|
||||
# For example, if the expected invoice value is 100,
|
||||
# the valid range is from 99 (value - 1) to 101 (value + 1),
|
||||
# i.e., 99.00 < received value < 101.00
|
||||
if (received_gstin != expected_gstin or
|
||||
not expected_total_invoice_value - 1 < received_total_invoice_value < expected_total_invoice_value + 1
|
||||
):
|
||||
mismatch_error = [{
|
||||
'code': '2150',
|
||||
'message': _("Duplicate IRN found for this invoice, but the buyer details or invoice values do not match.")
|
||||
}]
|
||||
# Handle the result based on mismatch or response error
|
||||
if mismatch_error:
|
||||
error = mismatch_error
|
||||
elif not response.get("error"):
|
||||
error = []
|
||||
link = Markup(
|
||||
"<a href='https://einvoice1.gst.gov.in/Others/VSignedInvoice'>%s</a>"
|
||||
) % (_("here"))
|
||||
self.message_post(
|
||||
author_id=odoobot_id,
|
||||
body=_(
|
||||
"Somehow this invoice has been submited to government before."
|
||||
"%(br)sNormally, this should not happen too often"
|
||||
"%(br)sJust verify value of invoice by upload json to government website %(link)s.",
|
||||
br=Markup("<br/>"),
|
||||
link=link
|
||||
)
|
||||
)
|
||||
if (no_credit := 'no-credit' in error_codes) or error:
|
||||
msg = Markup("<br/>").join(
|
||||
["[%s] %s" % (e.get("code"), e.get("message")) for e in error]
|
||||
)
|
||||
is_warning = any(warning_code in error_codes for warning_code in ('404', 'timeout'))
|
||||
self.l10n_in_edi_error = (
|
||||
self._l10n_in_edi_get_iap_buy_credits_message()
|
||||
if no_credit else msg
|
||||
)
|
||||
# avoid return `l10n_in_edi_error` because as a html field
|
||||
# values are sanitized with `<p>` tag
|
||||
return {
|
||||
'messages': [msg],
|
||||
'is_warning': is_warning
|
||||
}
|
||||
data = response.get("data", {})
|
||||
json_dump = json.dumps(data)
|
||||
json_name = "%s_einvoice.json" % (self.name.replace("/", "_"))
|
||||
attachment = self.env["ir.attachment"].create({
|
||||
'name': json_name,
|
||||
'raw': json_dump.encode(),
|
||||
'res_model': self._name,
|
||||
'res_field': 'l10n_in_edi_attachment_file',
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/json',
|
||||
'company_id': self.company_id.id,
|
||||
})
|
||||
request_json_dump = json.dumps(generate_json, indent=4)
|
||||
request_json_name = "%s_request.json" % (self.name.replace("/", "_"))
|
||||
request_attachment = self.env["ir.attachment"].create({
|
||||
'name': request_json_name,
|
||||
'raw': request_json_dump.encode(),
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/json',
|
||||
'company_id': self.company_id.id,
|
||||
})
|
||||
self.l10n_in_edi_status = 'sent'
|
||||
message = []
|
||||
for partner in partners:
|
||||
if partner_validation := self._l10n_in_edi_optional_field_validation(partner):
|
||||
message.append(
|
||||
Markup("<strong><em>%s</em></strong><br>%s") % (partner.name, Markup("<br>").join(partner_validation))
|
||||
)
|
||||
message.append(self.env._("E-invoice submitted successfully."))
|
||||
if message:
|
||||
self.message_post(
|
||||
attachment_ids=[request_attachment.id, attachment.id],
|
||||
body=Markup("<strong>%s</strong><br>%s") % (_("Following:"), Markup("<br>").join(message))
|
||||
)
|
||||
|
||||
def _l10n_in_edi_cancel_invoice(self):
|
||||
if self.l10n_in_edi_error:
|
||||
# make sure to clear the error before cancelling again
|
||||
self.l10n_in_edi_error = False
|
||||
self.message_post(body=_(
|
||||
"Retrying to send cancellation request for E-Invoice to government portal."
|
||||
))
|
||||
self._l10n_in_lock_invoice()
|
||||
l10n_in_edi_response_json = self._get_l10n_in_edi_response_json()
|
||||
cancel_json = {
|
||||
"Irn": l10n_in_edi_response_json.get("Irn"),
|
||||
"CnlRsn": self.l10n_in_edi_cancel_reason,
|
||||
"CnlRem": self.l10n_in_edi_cancel_remarks,
|
||||
}
|
||||
response = self._l10n_in_edi_connect_to_server(url_end_point='cancel', json_payload=cancel_json)
|
||||
# Creating a lambda function so it fetches the odoobot id only when needed
|
||||
_get_odoobot_id = (
|
||||
lambda self: self.env.ref('base.partner_root').id
|
||||
)
|
||||
if error := response.get('error'):
|
||||
error_codes = [e.get('code') for e in error]
|
||||
if '9999' in error_codes:
|
||||
response = {}
|
||||
error = []
|
||||
link = Markup(
|
||||
"<a href='https://einvoice1.gst.gov.in/Others/VSignedInvoice'>%s</a>"
|
||||
) % (_("here"))
|
||||
self.message_post(
|
||||
author_id=_get_odoobot_id(self),
|
||||
body=_(
|
||||
"Somehow this invoice had been cancelled to government before."
|
||||
"%(br)sNormally, this should not happen too often"
|
||||
"%(br)sJust verify by logging into government website %(link)s",
|
||||
br=Markup("<br/>"),
|
||||
link=link
|
||||
)
|
||||
)
|
||||
if "no-credit" in error_codes:
|
||||
self.l10n_in_edi_error = self._l10n_in_edi_get_iap_buy_credits_message()
|
||||
return
|
||||
if error:
|
||||
self.l10n_in_edi_error = (
|
||||
Markup("<br/>").join(
|
||||
["[%s] %s" % (e.get("code"), e.get("message")) for e in error]
|
||||
)
|
||||
)
|
||||
if "error" not in response:
|
||||
json_dump = json.dumps(response.get('data', {}))
|
||||
json_name = "%s_cancel_einvoice.json" % (self.name.replace("/", "_"))
|
||||
if json_dump:
|
||||
attachment = self.env['ir.attachment'].create({
|
||||
'name': json_name,
|
||||
'raw': json_dump.encode(),
|
||||
'res_model': self._name,
|
||||
'res_field': 'l10n_in_edi_attachment_file',
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/json',
|
||||
})
|
||||
request_json_dump = json.dumps(cancel_json, indent=4)
|
||||
request_json_name = "%s_cancel_request.json" % (self.name.replace("/", "_"))
|
||||
request_attachment = self.env['ir.attachment'].create({
|
||||
'name': request_json_name,
|
||||
'raw': request_json_dump.encode(),
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'mimetype': 'application/json',
|
||||
})
|
||||
self.message_post(author_id=_get_odoobot_id(self), body=_(
|
||||
"E-Invoice has been cancelled successfully. "
|
||||
"Cancellation Reason: %(reason)s and Cancellation Remark: %(remark)s",
|
||||
reason=EDI_CANCEL_REASON[self.l10n_in_edi_cancel_reason],
|
||||
remark=self.l10n_in_edi_cancel_remarks
|
||||
), attachment_ids=[request_attachment.id, attachment.id] if attachment else [request_attachment.id])
|
||||
self.l10n_in_edi_status = 'cancelled'
|
||||
self.button_cancel()
|
||||
if self._can_commit():
|
||||
self.env.cr.commit()
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _l10n_in_edi_is_managing_invoice_negative_lines_allowed(self):
|
||||
""" Negative lines are not allowed by the Indian government making some features unavailable like sale_coupon
|
||||
or global discounts. This method allows odoo to distribute the negative discount lines to each others lines
|
||||
with same HSN code making such features available even for Indian people.
|
||||
:return: True if odoo needs to distribute the negative discount lines, False otherwise.
|
||||
def _get_l10n_in_edi_partner_details(
|
||||
self,
|
||||
partner,
|
||||
set_vat=True,
|
||||
set_phone_and_email=True,
|
||||
is_overseas=False,
|
||||
pos_state_id=False
|
||||
):
|
||||
"""
|
||||
param_name = 'l10n_in_edi.manage_invoice_negative_lines'
|
||||
return bool(self.env['ir.config_parameter'].sudo().get_param(param_name))
|
||||
Create the dictionary based partner details
|
||||
if set_vat is true then, vat(GSTIN) and legal name(LglNm) is added
|
||||
if set_phone_and_email is true then phone and email is add
|
||||
if set_pos is true then state code from partner
|
||||
or passed state_id is added as POS(place of supply)
|
||||
if is_overseas is true then pin is 999999 and GSTIN(vat) is URP and Stcd is .
|
||||
if pos_state_id is passed then we use set POS
|
||||
"""
|
||||
zip_digits = self._l10n_in_extract_digits(partner.zip)
|
||||
partner_details = {
|
||||
'Addr1': partner.street or '',
|
||||
'Loc': partner.city or '',
|
||||
'Pin': zip_digits and int(zip_digits) or '',
|
||||
'Stcd': partner.state_id.l10n_in_tin or '',
|
||||
}
|
||||
if partner.street2 and re.match(r"^.{3,100}$", partner.street2):
|
||||
partner_details['Addr2'] = partner.street2
|
||||
if set_phone_and_email:
|
||||
if (
|
||||
partner.email
|
||||
and re.match(r"^[a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+$", partner.email)
|
||||
and re.match(r"^.{6,100}$", partner.email)
|
||||
):
|
||||
partner_details['Em'] = partner.email
|
||||
if (
|
||||
partner.phone
|
||||
and re.match(r"^[0-9]{10,12}$", self._l10n_in_extract_digits(partner.phone))
|
||||
):
|
||||
partner_details['Ph'] = self._l10n_in_extract_digits(partner.phone)
|
||||
if pos_state_id:
|
||||
partner_details['POS'] = pos_state_id.l10n_in_tin or ''
|
||||
if set_vat:
|
||||
partner_details.update({
|
||||
'LglNm': partner.commercial_partner_id.name,
|
||||
'GSTIN': partner.vat or 'URP',
|
||||
})
|
||||
else:
|
||||
partner_details['Nm'] = partner.name
|
||||
# For no country I would suppose it is India, so not sure this is super right
|
||||
if is_overseas and (not partner.country_id or partner.country_id.code != 'IN'):
|
||||
partner_details.update({
|
||||
"GSTIN": "URP",
|
||||
"Pin": 999999,
|
||||
"Stcd": "96",
|
||||
"POS": "96",
|
||||
})
|
||||
return partner_details
|
||||
|
||||
def _get_l10n_in_edi_line_details(self, index, line, line_tax_details):
|
||||
"""
|
||||
Create the dictionary with line details
|
||||
"""
|
||||
sign = self.is_inbound() and -1 or 1
|
||||
tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(line_tax_details['tax_details'])
|
||||
quantity = line.quantity
|
||||
if line.discount == 100.00 or float_is_zero(quantity, 3):
|
||||
# Full discount or zero quantity
|
||||
unit_price_in_inr = line.currency_id._convert(
|
||||
line.price_unit,
|
||||
line.company_currency_id,
|
||||
line.company_id,
|
||||
line.date or fields.Date.context_today(self)
|
||||
)
|
||||
else:
|
||||
unit_price_in_inr = ((sign * line.balance) / (1 - (line.discount / 100))) / quantity
|
||||
|
||||
if unit_price_in_inr < 0 and quantity < 0:
|
||||
# If unit price and quantity both is negative then
|
||||
# We set unit price and quantity as positive because
|
||||
# government does not accept negative in qty or unit price
|
||||
unit_price_in_inr = -unit_price_in_inr
|
||||
quantity = -quantity
|
||||
in_round = self._l10n_in_round_value
|
||||
line_details = {
|
||||
'SlNo': str(index),
|
||||
'IsServc': self._l10n_in_is_service_hsn(line.l10n_in_hsn_code) and 'Y' or 'N',
|
||||
'HsnCd': self._l10n_in_extract_digits(line.l10n_in_hsn_code),
|
||||
'Qty': in_round(quantity or 0.0, 3),
|
||||
'Unit': (
|
||||
line.product_uom_id.l10n_in_code
|
||||
and line.product_uom_id.l10n_in_code.split('-')[0]
|
||||
or 'OTH'
|
||||
),
|
||||
# Unit price in company currency and tax excluded so its different then price_unit
|
||||
'UnitPrice': in_round(unit_price_in_inr, 3),
|
||||
# total amount is before discount
|
||||
'TotAmt': in_round(unit_price_in_inr * quantity),
|
||||
'Discount': in_round((unit_price_in_inr * quantity) * (line.discount / 100)),
|
||||
'AssAmt': in_round(sign * line.balance),
|
||||
'GstRt': in_round(
|
||||
(tax_details_by_code.get('igst_rate', 0.00)
|
||||
or (tax_details_by_code.get('cgst_rate', 0.00) + tax_details_by_code.get('sgst_rate', 0.00))),
|
||||
3
|
||||
),
|
||||
'IgstAmt': in_round(tax_details_by_code.get('igst_amount', 0.00)),
|
||||
'CgstAmt': in_round(tax_details_by_code.get('cgst_amount', 0.00)),
|
||||
'SgstAmt': in_round(tax_details_by_code.get('sgst_amount', 0.00)),
|
||||
'CesRt': in_round(tax_details_by_code.get('cess_rate', 0.00), 3),
|
||||
'CesAmt': in_round(tax_details_by_code.get('cess_amount', 0.00)),
|
||||
'CesNonAdvlAmt': in_round(
|
||||
tax_details_by_code.get('cess_non_advol_amount', 0.00)
|
||||
),
|
||||
'StateCesRt': in_round(tax_details_by_code.get('state_cess_rate_amount', 0.00), 3),
|
||||
'StateCesAmt': in_round(tax_details_by_code.get('state_cess_amount', 0.00)),
|
||||
'StateCesNonAdvlAmt': in_round(
|
||||
tax_details_by_code.get('state_cess_non_advol_amount', 0.00)
|
||||
),
|
||||
'OthChrg': in_round(tax_details_by_code.get('other_amount', 0.00)),
|
||||
'TotItemVal': in_round((sign * line.balance) + line_tax_details.get('tax_amount', 0.00)),
|
||||
}
|
||||
if line.name:
|
||||
line_details['PrdDesc'] = line.name.replace("\n", "")[:300]
|
||||
return line_details
|
||||
|
||||
def _l10n_in_edi_generate_invoice_json_managing_negative_lines(self, json_payload):
|
||||
"""Set negative lines against positive lines as discount with same HSN code and tax rate
|
||||
With negative lines
|
||||
product name | hsn code | unit price | qty | discount | total
|
||||
=============================================================
|
||||
product A | 123456 | 1000 | 1 | 100 | 900
|
||||
product B | 123456 | 1500 | 2 | 0 | 3000
|
||||
Discount | 123456 | -300 | 1 | 0 | -300
|
||||
Converted to without negative lines
|
||||
product name | hsn code | unit price | qty | discount | total
|
||||
=============================================================
|
||||
product A | 123456 | 1000 | 1 | 100 | 900
|
||||
product B | 123456 | 1500 | 2 | 300 | 2700
|
||||
totally discounted lines are kept as 0, though
|
||||
"""
|
||||
def discount_group_key(line_vals):
|
||||
return "%s-%s" % (line_vals['HsnCd'], line_vals['GstRt'])
|
||||
|
||||
def put_discount_on(discount_line_vals, other_line_vals):
|
||||
discount = -discount_line_vals['AssAmt']
|
||||
discount_to_allow = other_line_vals['AssAmt']
|
||||
in_round = self._l10n_in_round_value
|
||||
amount_keys = (
|
||||
'AssAmt', 'IgstAmt', 'CgstAmt', 'SgstAmt', 'CesAmt',
|
||||
'CesNonAdvlAmt', 'StateCesAmt', 'StateCesNonAdvlAmt',
|
||||
'OthChrg', 'TotItemVal'
|
||||
)
|
||||
if float_compare(discount_to_allow, discount, precision_rounding=self.currency_id.rounding) < 0:
|
||||
# Update discount line, needed when discount is more then max line, in short remaining_discount is not zero
|
||||
discount_line_vals.update({
|
||||
key: in_round(discount_line_vals[key] + other_line_vals[key])
|
||||
for key in amount_keys
|
||||
})
|
||||
other_line_vals['Discount'] = in_round(other_line_vals['Discount'] + discount_to_allow)
|
||||
other_line_vals.update(dict.fromkeys(amount_keys, 0.00))
|
||||
return False
|
||||
other_line_vals['Discount'] = in_round(other_line_vals['Discount'] + discount)
|
||||
other_line_vals.update({
|
||||
key: in_round(other_line_vals[key] + discount_line_vals[key])
|
||||
for key in amount_keys
|
||||
})
|
||||
return True
|
||||
|
||||
discount_lines = []
|
||||
for discount_line in json_payload['ItemList'].copy(): #to be sure to not skip in the loop:
|
||||
if discount_line['AssAmt'] < 0:
|
||||
discount_lines.append(discount_line)
|
||||
json_payload['ItemList'].remove(discount_line)
|
||||
if not discount_lines:
|
||||
return json_payload
|
||||
self.message_post(
|
||||
author_id=self.env.ref('base.partner_root').id,
|
||||
body=_("Negative lines will be decreased from positive invoice lines having the same taxes and HSN code")
|
||||
)
|
||||
|
||||
lines_grouped_and_sorted = defaultdict(list)
|
||||
for line in sorted(json_payload['ItemList'], key=lambda i: i['AssAmt'], reverse=True):
|
||||
lines_grouped_and_sorted[discount_group_key(line)].append(line)
|
||||
|
||||
for discount_line in discount_lines:
|
||||
for apply_discount_on in lines_grouped_and_sorted[discount_group_key(discount_line)]:
|
||||
if put_discount_on(discount_line, apply_discount_on):
|
||||
break
|
||||
return json_payload
|
||||
|
||||
def _l10n_in_edi_generate_invoice_json(self):
|
||||
self.ensure_one()
|
||||
tax_details = self._l10n_in_prepare_tax_details()
|
||||
seller_buyer = self._get_l10n_in_seller_buyer_party()
|
||||
tax_details_by_code = self._get_l10n_in_tax_details_by_line_code(tax_details['tax_details'])
|
||||
is_intra_state = self.l10n_in_state_id == self.company_id.state_id
|
||||
is_overseas = self.l10n_in_gst_treatment == "overseas"
|
||||
line_ids = []
|
||||
global_discount_line_ids = []
|
||||
grouping_lines = self.invoice_line_ids.grouped(
|
||||
lambda l: l.display_type == 'product' and (l._l10n_in_is_global_discount() and 'global_discount' or 'lines')
|
||||
)
|
||||
default_line = self.env['account.move.line'].browse()
|
||||
lines = grouping_lines.get('lines', default_line)
|
||||
global_discount_line = grouping_lines.get('global_discount', default_line)
|
||||
tax_details_per_record = tax_details['tax_details_per_record']
|
||||
sign = self.is_inbound() and -1 or 1
|
||||
rounding_amount = sum(line.balance for line in self.line_ids if line.display_type == 'rounding') * sign
|
||||
global_discount_amount = sum(line.balance for line in global_discount_line) * -sign
|
||||
in_round = self._l10n_in_round_value
|
||||
json_payload = {
|
||||
"Version": "1.1",
|
||||
"TranDtls": {
|
||||
"TaxSch": "GST",
|
||||
"SupTyp": self._l10n_in_get_supply_type(tax_details_by_code.get('igst_amount')),
|
||||
"RegRev": tax_details_by_code.get('is_reverse_charge') and "Y" or "N",
|
||||
"IgstOnIntra": (
|
||||
# for Export SEZ LUT tax as per e-invoice api doc validation point 32
|
||||
# Export and SEZ must be treated as Inter state supply
|
||||
self.l10n_in_gst_treatment not in ('special_economic_zone', 'overseas')
|
||||
and is_intra_state
|
||||
and tax_details_by_code.get("igst_amount")
|
||||
and "Y" or "N"
|
||||
),
|
||||
},
|
||||
"DocDtls": {
|
||||
"Typ": (self.move_type == "out_refund" and "CRN") or (self.debit_origin_id and "DBN") or "INV",
|
||||
"No": self.name,
|
||||
"Dt": self.invoice_date and self.invoice_date.strftime("%d/%m/%Y")
|
||||
},
|
||||
"SellerDtls": self._get_l10n_in_edi_partner_details(seller_buyer['seller_details']),
|
||||
"BuyerDtls": self._get_l10n_in_edi_partner_details(
|
||||
seller_buyer['buyer_details'],
|
||||
pos_state_id=self.l10n_in_state_id,
|
||||
is_overseas=is_overseas
|
||||
),
|
||||
"ItemList": [
|
||||
self._get_l10n_in_edi_line_details(
|
||||
index,
|
||||
line,
|
||||
tax_details_per_record.get(line, {})
|
||||
)
|
||||
for index, line in enumerate(lines, start=1)
|
||||
],
|
||||
"ValDtls": {
|
||||
"AssVal": in_round(tax_details['base_amount']),
|
||||
"CgstVal": in_round(tax_details_by_code.get("cgst_amount", 0.00)),
|
||||
"SgstVal": in_round(tax_details_by_code.get("sgst_amount", 0.00)),
|
||||
"IgstVal": in_round(tax_details_by_code.get("igst_amount", 0.00)),
|
||||
"CesVal": in_round((
|
||||
tax_details_by_code.get("cess_amount", 0.00)
|
||||
+ tax_details_by_code.get("cess_non_advol_amount", 0.00)),
|
||||
),
|
||||
"StCesVal": in_round((
|
||||
tax_details_by_code.get("state_cess_amount", 0.00)
|
||||
+ tax_details_by_code.get("state_cess_non_advol_amount", 0.00)), # clean this up =p
|
||||
),
|
||||
"Discount": in_round(global_discount_amount),
|
||||
"RndOffAmt": in_round(rounding_amount),
|
||||
"TotInvVal": in_round(
|
||||
tax_details["base_amount"]
|
||||
+ tax_details["tax_amount"]
|
||||
+ rounding_amount
|
||||
- global_discount_amount
|
||||
),
|
||||
},
|
||||
}
|
||||
if self.company_currency_id != self.currency_id:
|
||||
json_payload["ValDtls"].update({
|
||||
"TotInvValFc": in_round(
|
||||
(tax_details.get("base_amount_currency") + tax_details.get("tax_amount_currency")))
|
||||
})
|
||||
if seller_buyer['seller_details'] != seller_buyer['dispatch_details']:
|
||||
json_payload['DispDtls'] = self._get_l10n_in_edi_partner_details(
|
||||
seller_buyer['dispatch_details'],
|
||||
set_vat=False,
|
||||
set_phone_and_email=False
|
||||
)
|
||||
if seller_buyer['buyer_details'] != seller_buyer['ship_to_details']:
|
||||
json_payload['ShipDtls'] = self._get_l10n_in_edi_partner_details(
|
||||
seller_buyer['ship_to_details'],
|
||||
is_overseas=is_overseas
|
||||
)
|
||||
if is_overseas:
|
||||
json_payload['ExpDtls'] = {
|
||||
'RefClm': tax_details_by_code.get('igst_amount') and 'Y' or 'N',
|
||||
'ForCur': self.currency_id.name,
|
||||
'CntCode': seller_buyer['buyer_details'].country_id.code or '',
|
||||
}
|
||||
if shipping_bill_no := self.l10n_in_shipping_bill_number:
|
||||
json_payload['ExpDtls']['ShipBNo'] = shipping_bill_no
|
||||
if shipping_bill_date := self.l10n_in_shipping_bill_date:
|
||||
json_payload['ExpDtls']['ShipBDt'] = shipping_bill_date.strftime("%d/%m/%Y")
|
||||
if shipping_port_code_id := self.l10n_in_shipping_port_code_id:
|
||||
json_payload['ExpDtls']['Port'] = shipping_port_code_id.code
|
||||
json_valdtls = json_payload['ValDtls']
|
||||
base_and_tax_amount = tax_details.get("base_amount") + tax_details.get("tax_amount")
|
||||
# For Export If with payment of Tax then we need to include Tax in Total Invoice Value
|
||||
if json_payload['TranDtls']['SupTyp'] == 'EXPWP' and json_valdtls['AssVal'] == base_and_tax_amount:
|
||||
json_payload["ValDtls"]["TotInvVal"] = self._l10n_in_round_value(sum([
|
||||
json_valdtls['TotInvVal'],
|
||||
json_valdtls['IgstVal'],
|
||||
json_valdtls['CgstVal'],
|
||||
json_valdtls['SgstVal'],
|
||||
json_valdtls['CesVal'],
|
||||
json_valdtls['StCesVal'],
|
||||
]))
|
||||
for line in json_payload["ItemList"]:
|
||||
line["TotItemVal"] = self._l10n_in_round_value(sum([
|
||||
line["TotItemVal"],
|
||||
line["IgstAmt"],
|
||||
line["CgstAmt"],
|
||||
line["SgstAmt"],
|
||||
line["CesAmt"],
|
||||
line["CesNonAdvlAmt"],
|
||||
line["StateCesAmt"],
|
||||
line["StateCesNonAdvlAmt"],
|
||||
]))
|
||||
return self._l10n_in_edi_generate_invoice_json_managing_negative_lines(json_payload)
|
||||
|
||||
def _l10n_in_get_supply_type(self, is_igst_amount):
|
||||
if self.l10n_in_gst_treatment in ("overseas", "special_economic_zone") and is_igst_amount:
|
||||
return {
|
||||
'overseas': 'EXPWP',
|
||||
'special_economic_zone': 'SEZWP',
|
||||
}[self.l10n_in_gst_treatment]
|
||||
return {
|
||||
'deemed_export': 'DEXP',
|
||||
'overseas': 'EXPWOP',
|
||||
'special_economic_zone': 'SEZWOP',
|
||||
}.get(self.l10n_in_gst_treatment, 'B2B')
|
||||
|
||||
# ================= Get Error =================
|
||||
def _l10n_in_check_einvoice_validation(self):
|
||||
alerts = {
|
||||
**self.company_id._l10n_in_check_einvoice_validation(),
|
||||
**(self.partner_id | self.partner_shipping_id)._l10n_in_check_einvoice_validation(),
|
||||
**self.invoice_line_ids._l10n_in_check_einvoice_validation(),
|
||||
}
|
||||
if invalid_records := self.filtered(lambda m: not re.match("^.{1,16}$", m.name)):
|
||||
alerts['l10n_in_edi_invalid_invoice_number'] = {
|
||||
'message': _("Invoice number should not be more than 16 characters"),
|
||||
'action_text': _("View Invoices"),
|
||||
'action': invalid_records._get_records_action(name=_("Check Invoices")),
|
||||
}
|
||||
return alerts
|
||||
|
||||
# ================================ API methods ===========================
|
||||
def _l10n_in_edi_connect_to_server(self, url_end_point, json_payload=False, params=False):
|
||||
"""
|
||||
url_end_point possible values (generate, getirnbydocdetails, generate_ewaybill_by_irn, get_ewaybill_by_irn, cancel)
|
||||
is used to get the EDI response from the server
|
||||
"""
|
||||
company = self.company_id
|
||||
token = company._l10n_in_edi_get_token()
|
||||
if not token:
|
||||
return {
|
||||
'error': [{
|
||||
'code': '0',
|
||||
'message': _(
|
||||
"Ensure GST Number set on company setting and API are Verified."
|
||||
)
|
||||
}]
|
||||
}
|
||||
default_params = {
|
||||
'auth_token': token,
|
||||
'username': company.sudo().l10n_in_edi_username,
|
||||
'gstin': company.vat,
|
||||
}
|
||||
if params:
|
||||
# To be used when generate_ewaybill_by_irn, get_ewaybill_by_irn
|
||||
params.update(default_params)
|
||||
else:
|
||||
params = {
|
||||
**default_params,
|
||||
'json_payload': json_payload
|
||||
}
|
||||
try:
|
||||
response = self.env['iap.account']._l10n_in_connect_to_server(
|
||||
company.sudo().l10n_in_edi_production_env,
|
||||
params,
|
||||
f"/iap/l10n_in_edi/1/{url_end_point}",
|
||||
'l10n_in_edi.endpoint'
|
||||
)
|
||||
except AccessError as e:
|
||||
_logger.warning("Connection error: %s", e.args[0])
|
||||
return {
|
||||
'error': [{
|
||||
'code': '404',
|
||||
'message': _(
|
||||
"Unable to connect to the online E-invoice service."
|
||||
"The web service may be temporary down. Please try again in a moment."
|
||||
)
|
||||
}]
|
||||
}
|
||||
if (error := response.get('error')) and '1005' in [e.get("code") for e in error]:
|
||||
# Invalid token error 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 = company._l10n_in_edi_authenticate()
|
||||
if not authenticate_response.get("error"):
|
||||
response = self._l10n_in_edi_connect_to_server(
|
||||
url_end_point=url_end_point,
|
||||
json_payload=json_payload,
|
||||
params=params
|
||||
)
|
||||
else:
|
||||
return authenticate_response
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
from odoo import models
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = 'account.move.line'
|
||||
|
||||
# E-Invoice Methods
|
||||
def _l10n_in_is_global_discount(self):
|
||||
self.ensure_one()
|
||||
return not self.tax_ids and self.price_subtotal < 0
|
||||
|
||||
def _l10n_in_check_einvoice_validation(self):
|
||||
_ = self.env._
|
||||
error_messages = {
|
||||
'invalid_hsn': _(
|
||||
"Missing or invalid HSN/SAC code: Ensure that invoice lines contain "
|
||||
"4, 6 or 8 digits"
|
||||
),
|
||||
'restrict_negative_discount_line': _("Negative discount is not allowed"),
|
||||
'tax_validation': _(
|
||||
"Set an appropriate GST tax on invoice lines "
|
||||
"(if it's zero rated or nil rated then apply it too)"
|
||||
),
|
||||
}
|
||||
|
||||
error_lines = {}
|
||||
for line in self:
|
||||
error_codes = []
|
||||
if line.display_type != 'product' or line._l10n_in_is_global_discount():
|
||||
continue
|
||||
if line._l10n_in_check_invalid_hsn_code():
|
||||
error_codes.append('invalid_hsn')
|
||||
if line.discount < 0:
|
||||
error_codes.append('restrict_negative_discount_line')
|
||||
if not any(tax.l10n_in_tax_type in ['gst', 'nil_rated', 'exempt', 'non_gst'] for tax in line.tax_ids.flatten_taxes_hierarchy()):
|
||||
error_codes.append('tax_validation')
|
||||
for code in error_codes:
|
||||
error_lines[code] = error_lines.get(code, self.env['account.move.line']) | line
|
||||
|
||||
return {
|
||||
f"l10n_in_edi_{error_code}": {
|
||||
'level': 'danger' if error_code == 'invalid_hsn' else 'warning',
|
||||
'message': error_messages[error_code],
|
||||
'action_text': _("View Invoice Line(s)"),
|
||||
# The context are set in view_move_line_tree_hsn_l10n_in
|
||||
# Please make sure to change, if any change in error codes
|
||||
'action': lines.with_context(**{
|
||||
error_code: True,
|
||||
'send_and_print': True
|
||||
})._get_records_action(
|
||||
name=_("Check Invoice Line(s)"),
|
||||
domain=[('id', 'in', lines.ids)],
|
||||
views=[(
|
||||
self.env.ref('l10n_in.view_move_line_tree_hsn_l10n_in').id,
|
||||
'list'
|
||||
)],
|
||||
),
|
||||
}
|
||||
for error_code, lines in error_lines.items()
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class AccountMoveSend(models.AbstractModel):
|
||||
_inherit = 'account.move.send'
|
||||
|
||||
@api.model
|
||||
def _is_in_edi_applicable(self, move):
|
||||
return (
|
||||
move._l10n_in_check_einvoice_eligible()
|
||||
and move.state == 'posted'
|
||||
and move.l10n_in_edi_status != 'sent'
|
||||
)
|
||||
|
||||
def _get_all_extra_edis(self) -> dict:
|
||||
# EXTENDS 'account'
|
||||
res = super()._get_all_extra_edis()
|
||||
res.update({
|
||||
'in_edi_send': {
|
||||
'label': self.env._("E-invoicing"),
|
||||
'is_applicable': self._is_in_edi_applicable,
|
||||
'help': self.env._(
|
||||
"Send the e-invoice json to the Indian Invoice Registration Portal (IRP)."
|
||||
)
|
||||
}
|
||||
})
|
||||
return res
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ALERTS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _get_alerts(self, moves, moves_data):
|
||||
# EXTENDS 'account'
|
||||
alerts = super()._get_alerts(moves, moves_data)
|
||||
if in_moves := moves.filtered(lambda m: 'in_edi_send' in moves_data[m]['extra_edis']):
|
||||
if in_alerts := in_moves._l10n_in_check_einvoice_validation():
|
||||
alerts.update(in_alerts)
|
||||
return alerts
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# SENDING METHODS
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
def _get_invoice_extra_attachments(self, invoice):
|
||||
# EXTENDS 'account'
|
||||
return super()._get_invoice_extra_attachments(invoice) + invoice.l10n_in_edi_attachment_id
|
||||
|
||||
def _call_web_service_before_invoice_pdf_render(self, invoices_data):
|
||||
# EXTENDS 'account'
|
||||
super()._call_web_service_before_invoice_pdf_render(invoices_data)
|
||||
for invoice, invoice_data in invoices_data.items():
|
||||
if 'in_edi_send' in invoice_data['extra_edis']:
|
||||
if error := invoice._l10n_in_edi_send_invoice():
|
||||
invoice_data['error'] = {
|
||||
'error_title': self.env._(
|
||||
"Error when sending the invoice to government:"
|
||||
),
|
||||
'errors': error['messages'],
|
||||
'retry': error.get('is_warning'),
|
||||
}
|
||||
elif invoice.invoice_pdf_report_id:
|
||||
invoice.write({'invoice_pdf_report_file': False})
|
||||
if self._can_commit():
|
||||
self.env.cr.commit()
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
from odoo import api, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class IrAttachment(models.Model):
|
||||
_inherit = 'ir.attachment'
|
||||
|
||||
@api.ondelete(at_uninstall=False)
|
||||
def _unlink_except_l10n_in_government_document(self):
|
||||
"""
|
||||
Prevents the deletion of attachments related to government-issued documents.
|
||||
"""
|
||||
if any(
|
||||
attachment.res_model == 'account.move'
|
||||
and attachment.mimetype == 'application/json'
|
||||
and attachment.res_field == 'l10n_in_edi_attachment_file'
|
||||
for attachment in self
|
||||
):
|
||||
raise UserError(self.env._(
|
||||
"You can't unlink an attachment that you received from the government"
|
||||
))
|
||||
|
|
@ -1,24 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
import pytz
|
||||
|
||||
from odoo import fields, models
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.exceptions import AccessError, ValidationError
|
||||
from stdnum.in_ import pan, gstin
|
||||
|
||||
|
||||
class ResCompany(models.Model):
|
||||
_inherit = "res.company"
|
||||
_inherit = 'res.company'
|
||||
|
||||
l10n_in_edi_username = fields.Char("E-invoice (IN) Username", groups="base.group_system")
|
||||
l10n_in_edi_password = fields.Char("E-invoice (IN) Password", groups="base.group_system")
|
||||
l10n_in_edi_token = fields.Char("E-invoice (IN) Token", groups="base.group_system")
|
||||
l10n_in_edi_token_validity = fields.Datetime("E-invoice (IN) Valid Until", groups="base.group_system")
|
||||
l10n_in_edi_production_env = fields.Boolean(
|
||||
string="E-invoice (IN) Is production OSE environment",
|
||||
help="Enable the use of production credentials",
|
||||
groups="base.group_system",
|
||||
# E-Invoice fields
|
||||
l10n_in_edi_feature = fields.Boolean(string="Indian E-Invoicing")
|
||||
l10n_in_edi_username = fields.Char(
|
||||
string="E-invoice (IN) Username",
|
||||
groups="base.group_system"
|
||||
)
|
||||
l10n_in_edi_password = fields.Char(
|
||||
string="E-invoice (IN) Password",
|
||||
groups="base.group_system"
|
||||
)
|
||||
l10n_in_edi_token = fields.Char(
|
||||
string="E-invoice (IN) Token",
|
||||
groups="base.group_system"
|
||||
)
|
||||
l10n_in_edi_token_validity = fields.Datetime(
|
||||
string="E-invoice (IN) Valid Until",
|
||||
groups="base.group_system"
|
||||
)
|
||||
|
||||
|
||||
# E-Invoice Business Methods
|
||||
|
||||
def _l10n_in_edi_token_is_valid(self):
|
||||
self.ensure_one()
|
||||
if self.l10n_in_edi_token and self.l10n_in_edi_token_validity > fields.Datetime.now():
|
||||
return True
|
||||
return self.l10n_in_edi_token and self.l10n_in_edi_token_validity > fields.Datetime.now()
|
||||
|
||||
def _l10n_in_edi_get_token(self):
|
||||
self_sudo = self.sudo()
|
||||
if self_sudo.l10n_in_edi_username and self_sudo._l10n_in_edi_token_is_valid():
|
||||
return self_sudo.l10n_in_edi_token
|
||||
elif self_sudo.l10n_in_edi_username and self_sudo.l10n_in_edi_password:
|
||||
self_sudo._l10n_in_edi_authenticate()
|
||||
return self_sudo.l10n_in_edi_token
|
||||
return False
|
||||
|
||||
def _l10n_in_edi_authenticate(self):
|
||||
self_sudo = self.sudo()
|
||||
params = {
|
||||
"username": self_sudo.l10n_in_edi_username,
|
||||
"password": self_sudo.l10n_in_edi_password,
|
||||
"gstin": self_sudo.vat,
|
||||
}
|
||||
try:
|
||||
response = self.env['iap.account']._l10n_in_connect_to_server(
|
||||
self_sudo.l10n_in_edi_production_env,
|
||||
params,
|
||||
"/iap/l10n_in_edi/1/authenticate",
|
||||
"l10n_in_edi.endpoint"
|
||||
)
|
||||
except AccessError as e:
|
||||
return {
|
||||
"error": [{
|
||||
"code": "404",
|
||||
"message": _(
|
||||
"Unable to connect to the online E-invoice service. "
|
||||
"The web service may be temporary down. Please try again in a moment."
|
||||
)
|
||||
}]
|
||||
}
|
||||
# validity data-time in Indian standard time(UTC+05:30) convert IST to UTC
|
||||
if data := response.get('data'):
|
||||
tz = pytz.timezone("Asia/Kolkata")
|
||||
local_time = tz.localize(fields.Datetime.to_datetime(data["TokenExpiry"]))
|
||||
utc_time = local_time.astimezone(pytz.utc)
|
||||
self_sudo.write({
|
||||
'l10n_in_edi_token_validity': fields.Datetime.to_string(utc_time),
|
||||
'l10n_in_edi_token': data['AuthToken'],
|
||||
})
|
||||
return response
|
||||
|
||||
def _l10n_in_check_einvoice_validation(self):
|
||||
checks = {
|
||||
'company_address_missing': {
|
||||
'fields': ('street', 'zip', 'city', 'state_id', 'country_id',),
|
||||
'message': _("Companies should have a complete address, verify their Street, City, State, Country and Zip code."),
|
||||
},
|
||||
}
|
||||
return {
|
||||
f"l10n_in_edi_{key}": {
|
||||
'message': check['message'],
|
||||
'action_text': (
|
||||
_("View Companies") if len(invalid_records) > 1
|
||||
else _("View %s", invalid_records.name)
|
||||
),
|
||||
'action': invalid_records._get_records_action(name=_("Check Company Data")),
|
||||
}
|
||||
for key, check in checks.items()
|
||||
if (invalid_records := self.filtered(lambda record: any(not record[field] for field in check['fields'])))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,52 @@
|
|||
# -*- 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, RedirectWarning
|
||||
from odoo import fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = "res.config.settings"
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
l10n_in_edi_username = fields.Char("Indian EDI username", related="company_id.l10n_in_edi_username", readonly=False)
|
||||
l10n_in_edi_password = fields.Char("Indian EDI password", related="company_id.l10n_in_edi_password", readonly=False)
|
||||
l10n_in_edi_production_env = fields.Boolean(
|
||||
string="Indian EDI Testing Environment",
|
||||
related="company_id.l10n_in_edi_production_env",
|
||||
# E-Invoice
|
||||
l10n_in_edi_feature = fields.Boolean(
|
||||
related='company_id.l10n_in_edi_feature',
|
||||
readonly=False
|
||||
)
|
||||
l10n_in_edi_username = fields.Char(
|
||||
"Indian EDI username",
|
||||
related="company_id.l10n_in_edi_username",
|
||||
readonly=False
|
||||
)
|
||||
l10n_in_edi_password = fields.Char(
|
||||
"Indian EDI password",
|
||||
related="company_id.l10n_in_edi_password",
|
||||
readonly=False
|
||||
)
|
||||
|
||||
def l10n_in_check_gst_number(self):
|
||||
if not self.company_id.vat:
|
||||
action = {
|
||||
"view_mode": "form",
|
||||
"res_model": "res.company",
|
||||
"type": "ir.actions.act_window",
|
||||
"res_id" : self.company_id.id,
|
||||
"views": [[self.env.ref("base.view_company_form").id, "form"]],
|
||||
}
|
||||
raise RedirectWarning(_("Please enter a GST number in company."), action, _('Go to Company'))
|
||||
|
||||
# E-Invoice Methods
|
||||
def l10n_in_edi_test(self):
|
||||
self.l10n_in_check_gst_number()
|
||||
response = self.env['account.edi.format']._l10n_in_edi_authenticate(self.company_id)
|
||||
self._l10n_in_check_gst_number()
|
||||
response = self.company_id._l10n_in_edi_authenticate()
|
||||
_ = self.env._
|
||||
if response.get('error'):
|
||||
raise UserError("\n".join(["[%s] %s" % (e.get('code'), (e.get('message'))) for e in response['error']]))
|
||||
elif not self.company_id.sudo()._l10n_in_edi_token_is_valid():
|
||||
raise UserError(_("Incorrect username or password, or the GST number on company does not match."))
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'sticky': False,
|
||||
'message': _("API credentials validated successfully"),
|
||||
}
|
||||
}
|
||||
|
||||
def l10n_in_edi_buy_iap(self):
|
||||
if not self.l10n_in_edi_production_env:
|
||||
raise UserError(_("You must enable production environment to buy credits"))
|
||||
return {
|
||||
'type': 'ir.actions.act_url',
|
||||
'url': self.env["iap.account"].get_credits_url(service_name="l10n_in_edi", base_url=''),
|
||||
'target': '_new'
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'params': {
|
||||
'type': 'info',
|
||||
'sticky': False,
|
||||
'message': _("API credentials validated successfully"),
|
||||
}
|
||||
}
|
||||
|
||||
def _l10n_in_gsp_provider_changed(self):
|
||||
"""
|
||||
This change should effect all Indian companies so we search for them and
|
||||
Invalidate existing tokens if GSP provider changed
|
||||
"""
|
||||
super()._l10n_in_gsp_provider_changed()
|
||||
self.env['res.company'].sudo().search([('account_fiscal_country_id.code', '=', 'IN')]).write({
|
||||
'l10n_in_edi_token': False,
|
||||
'l10n_in_edi_token_validity': False,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import re
|
||||
|
||||
from odoo import _, models
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
# E-Invoice Validation
|
||||
def _l10n_in_edi_strict_error_validation(self):
|
||||
"""
|
||||
This method is used to check the strict validation of the partner data
|
||||
as per government API json schema (https://einv-apisandbox.nic.in/version1.03/generate-irn.html#requestSampleJSON)
|
||||
In case of any error, it will return the error message
|
||||
Note - We stimulate as error message from API, so that user can understand the error
|
||||
Also restrict unwanted request to government servers and avoid getting black listed
|
||||
"""
|
||||
message = []
|
||||
if not re.match("^.{3,100}$", self.street or ""):
|
||||
message.append(_("- Street required min 3 and max 100 characters"))
|
||||
if not re.match("^.{3,100}$", self.city or ""):
|
||||
message.append(_("- City required min 3 and max 100 characters"))
|
||||
if self.country_id.code == "IN" and not re.match("^.{3,50}$", self.state_id.name or ""):
|
||||
message.append(_("- State required min 3 and max 50 characters"))
|
||||
if self.country_id.code == "IN" and not re.match("^([1-9][0-9]{5})$", self.zip or ""):
|
||||
message.append(_("- ZIP code required 6 digits ranging from 100000 to 999999"))
|
||||
if (
|
||||
self.country_id.code == "IN"
|
||||
and not re.match(r"^(?!0+$)([0-9]{2})$", self.state_id.l10n_in_tin or "")
|
||||
):
|
||||
message.append(_("- State TIN Number must be exactly 2 digits."))
|
||||
if message:
|
||||
message.insert(0, self.display_name)
|
||||
return message
|
||||
|
||||
def _l10n_in_check_einvoice_validation(self):
|
||||
checks = {
|
||||
'partner_address_missing': {
|
||||
'fields': ('street', 'zip', 'city', 'state_id', 'country_id',),
|
||||
'message': _(
|
||||
"Partners should have a complete address, verify their Street, City, State, Country and Zip code."
|
||||
),
|
||||
},
|
||||
}
|
||||
return {
|
||||
f"l10n_in_edi_{key}": {
|
||||
'message': check['message'],
|
||||
'action_text': (
|
||||
_("View Partners") if len(invalid_records) > 1
|
||||
else _("View %s", invalid_records.name)
|
||||
),
|
||||
'action': invalid_records._get_records_action(name=_("Check Partner Data")),
|
||||
}
|
||||
for key, check in checks.items()
|
||||
if (invalid_records := self.filtered(lambda record: any(not record[field] for field in check['fields'])))
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_l10n_in_edi_cancel,access_l10n_in_edi_cancel,model_l10n_in_edi_cancel,account.group_account_invoice,1,1,1,0
|
||||
|
|
|
@ -1,4 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_edi_json
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -5,10 +5,86 @@
|
|||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- E-Invoice -->
|
||||
<xpath expr="//group[@name='sale_info_group']" position="inside">
|
||||
<field name="l10n_in_edi_show_cancel" invisible="1"/>
|
||||
<field name="l10n_in_edi_cancel_reason" attrs="{'invisible': ['|', '|', ('country_code', '!=', 'IN'), ('state', '!=', 'posted'), ('l10n_in_edi_show_cancel', '!=', True)]}"/>
|
||||
<field name="l10n_in_edi_cancel_remarks" attrs="{'invisible': ['|', '|', ('country_code', '!=', 'IN'), ('state', '!=', 'posted'), ('l10n_in_edi_show_cancel', '!=', True)]}"/>
|
||||
<field name="l10n_in_edi_cancel_reason"
|
||||
string="EDI Cancel Reason"
|
||||
invisible="not l10n_in_edi_cancel_reason"
|
||||
readonly="1"/>
|
||||
<field name="l10n_in_edi_cancel_remarks"
|
||||
string="EDI Cancel Remarks"
|
||||
invisible="not l10n_in_edi_cancel_remarks"
|
||||
readonly="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//header" position="after">
|
||||
<div class="alert alert-danger" role="alert"
|
||||
invisible="not l10n_in_edi_error">
|
||||
<div class="o_row">
|
||||
<field name="l10n_in_edi_error"/>
|
||||
<button name="action_l10n_in_edi_force_cancel"
|
||||
type="object"
|
||||
class="oe_link oe_inline"
|
||||
confirm="Are you sure you want to cancel this invoice without waiting for the EDI document to be canceled?"
|
||||
invisible="l10n_in_edi_status != 'sent'"
|
||||
string="Force Cancel"/>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//button[@name='button_draft']" position="after">
|
||||
<button name="action_export_l10n_in_edi_content_json"
|
||||
type="object"
|
||||
class="btn-secondary"
|
||||
invisible="not l10n_in_edi_error"
|
||||
groups="base.group_no_one"
|
||||
string="Download EDI JSON"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='journal_div']" position="after">
|
||||
<field name="l10n_in_edi_status" invisible="not l10n_in_edi_status or state == 'draft'"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_out_invoice_tree_inherit_l10n_in_edi" model="ir.ui.view">
|
||||
<field name="name">out.invoice.list.inherit.l10n_in_edi</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_out_invoice_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="status_in_payment" position="before">
|
||||
<field name="l10n_in_edi_status"
|
||||
optional="hide"
|
||||
widget="badge"
|
||||
decoration-success="l10n_in_edi_status == 'sent'"
|
||||
decoration-info="l10n_in_edi_status == 'to_send'"
|
||||
decoration-danger="l10n_in_edi_status == 'cancelled'"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_out_credit_note_tree_inherit_l10n_in_edi" model="ir.ui.view">
|
||||
<field name="name">out.credit.note.list.inherit.l10n_in_edi</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_out_credit_note_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="status_in_payment" position="before">
|
||||
<field name="l10n_in_edi_status"
|
||||
optional="hide"
|
||||
widget="badge"
|
||||
decoration-success="l10n_in_edi_status == 'sent'"
|
||||
decoration-info="l10n_in_edi_status == 'to_send'"
|
||||
decoration-danger="l10n_in_edi_status == 'cancelled'"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record id="l10n_in_edi_inherit_account_move_search_view" model="ir.ui.view">
|
||||
<field name="name">l10n.in.edi.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="//filter[@name='cancel']" position="after">
|
||||
<separator/>
|
||||
<filter name="l10n_in_edi_to_send" string="Indian E-Invoices To Send" domain="[('l10n_in_edi_status', '=', 'to_send')]"/>
|
||||
<filter name="l10n_in_edi_error_move" string="Indian E-Invoices In Error" domain="[('l10n_in_edi_error', '!=', False)]"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='status']" position="after">
|
||||
<filter name="groupby_l10n_in_edi_status" string="Indian E-invoice status" context="{'group_by' : 'l10n_in_edi_status'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
|||
|
|
@ -3,26 +3,21 @@
|
|||
<template id="l10n_in_einvoice_report_invoice_document_inherit" inherit_id="account.report_invoice_document">
|
||||
<xpath expr="//div[@id='informations']" position="inside">
|
||||
<t t-set="l10n_in_einvoice_json" t-value="o._get_l10n_in_edi_response_json()"/>
|
||||
<div class="col-auto col-3 mw-100 mb-2" t-if="l10n_in_einvoice_json" name="ack_no">
|
||||
<strong>Acknowledgement:</strong>
|
||||
<p class="m-0" t-out="l10n_in_einvoice_json['AckNo']"/>
|
||||
<p t-out="l10n_in_einvoice_json['AckDt']"/>
|
||||
<div class="col" t-if="l10n_in_einvoice_json" name="ack_no">
|
||||
<strong>Acknowledgement</strong>
|
||||
<div t-out="l10n_in_einvoice_json['AckNo']"/>
|
||||
<div t-out="l10n_in_einvoice_json['AckDt']"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@id='total']/div[1]" position="attributes">
|
||||
<attribute name="t-attf-class">{{('col-6' if report_type != 'html' else 'col-sm-6 col-md-5 ms-auto') if l10n_in_einvoice_json else ''}}
|
||||
{{(('col-6' if report_type != 'html' else 'col-sm-7 col-md-6') + ' ms-auto') if not l10n_in_einvoice_json else ''}}</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[@id='total']/div[1]" position="before">
|
||||
<xpath expr="//div[@id='right-elements']" position="after">
|
||||
<t t-set="l10n_in_einvoice_json" t-value="o._get_l10n_in_edi_response_json()"/>
|
||||
<div t-attf-class="#{'col-6' if report_type != 'html' else 'col-sm-7 col-md-6 ms-auto'} row" t-if="l10n_in_einvoice_json">
|
||||
<div class="col-7 me-2">
|
||||
<div t-attf-class="#{'col-5' if report_type != 'html' else 'ms-auto'} row avoid-page-break-inside" t-if="l10n_in_einvoice_json">
|
||||
<div class="col-7 me-2" t-attf-style="#{'' if report_type != 'html' else 'padding: 0 !important;'}">
|
||||
<strong>IRN:</strong>
|
||||
<span t-esc="l10n_in_einvoice_json['Irn']"/>
|
||||
</div>
|
||||
<div class="col-3 mt-1">
|
||||
<img t-att-src="'/report/barcode/?barcode_type=%s&value=%s&width=%s&height=%s' %('QR', l10n_in_einvoice_json['SignedQRCode'], 500, 500)" style="max-height: 155px"/>
|
||||
<img t-att-src="'/report/barcode/?barcode_type=%s&value=%s&width=%s&height=%s&quiet=%s' %('QR', l10n_in_einvoice_json['SignedQRCode'], 500, 500, 0)" style="max-height: 155px"/>
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
|
|
|
|||
|
|
@ -5,40 +5,24 @@
|
|||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<div data-key="account" position="inside">
|
||||
<h2 attrs="{'invisible': [('country_code', '!=', 'IN')]}">Indian Electronic Invoicing</h2>
|
||||
<div class='row mt16 o_settings_container' name="l10n_in_edi_iap" attrs="{'invisible': [('country_code', '!=', 'IN')]}">
|
||||
<div class="col-12 col-lg-6 o_setting_box" id="gsp_setting">
|
||||
<div class="o_setting_right_pane">
|
||||
<span class="o_form_label">Setup E-invoice</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">
|
||||
Check the <a href="https://www.odoo.com/documentation/16.0/applications/finance/fiscal_localizations/india.html">documentation</a> to get credentials
|
||||
</div>
|
||||
<div class="content-group">
|
||||
<div class="mt16 row">
|
||||
<label for="l10n_in_edi_username" string="Username" class="col-3 col-lg-3 o_light_label"/>
|
||||
<field name="l10n_in_edi_username" nolabel="1"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="l10n_in_edi_password" string="Password" class="col-3 col-lg-3 o_light_label" />
|
||||
<field name="l10n_in_edi_password" password="True" nolabel="1"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="l10n_in_edi_production_env" string="Production Environment" class="col-3 col-lg-3 o_light_label"/>
|
||||
<field name="l10n_in_edi_production_env" nolabel="1"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class='mt8'>
|
||||
<button name="l10n_in_edi_test" icon="fa-arrow-right" type="object" string="Verify Username and Password" class="btn-link"/>
|
||||
</div>
|
||||
<div class='mt8'>
|
||||
<button name="l10n_in_edi_buy_iap" title="Costs 1 credit per transaction. Free 200 credits will be available for the first time." icon="fa-arrow-right" type="object" string="Buy credits" class="btn-link"/>
|
||||
</div>
|
||||
</div>
|
||||
<xpath expr="//field[@name='module_l10n_in_edi']" position="replace">
|
||||
<field name="l10n_in_edi_feature"/>
|
||||
</xpath>
|
||||
<xpath expr="//setting[@name='electronic_invoices_in']/div[hasclass('content-group')]" position="replace">
|
||||
<div class="content-group" invisible="not l10n_in_edi_feature">
|
||||
<div class="mt16 row">
|
||||
<label for="l10n_in_edi_username" string="Username" class="col-3 col-lg-3 o_light_label"/>
|
||||
<field name="l10n_in_edi_username" nolabel="1"/>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label for="l10n_in_edi_password" string="Password" class="col-3 col-lg-3 o_light_label"/>
|
||||
<field name="l10n_in_edi_password" password="True" nolabel="1"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='mt8' invisible="not l10n_in_edi_feature">
|
||||
<button name="l10n_in_edi_test" icon="oi-arrow-right" type="object" string="Verify Username and Password" class="btn-link"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from . import l10n_in_edi_cancel
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import fields, models
|
||||
|
||||
from odoo.addons.l10n_in.models.account_invoice import EDI_CANCEL_REASON
|
||||
|
||||
|
||||
class L10n_In_EdiCancel(models.TransientModel):
|
||||
_name = 'l10n_in_edi.cancel'
|
||||
|
||||
_description = "Cancel E-Invoice"
|
||||
|
||||
move_id = fields.Many2one('account.move', string="Invoice", required=True)
|
||||
cancel_reason = fields.Selection(
|
||||
selection=list(EDI_CANCEL_REASON.items()),
|
||||
string="Cancel Reason",
|
||||
required=True
|
||||
)
|
||||
cancel_remarks = fields.Char("Cancel Remarks", required=True)
|
||||
|
||||
def cancel_l10n_in_edi_move(self):
|
||||
self.move_id.write({
|
||||
'l10n_in_edi_cancel_reason': self.cancel_reason,
|
||||
'l10n_in_edi_cancel_remarks': self.cancel_remarks,
|
||||
})
|
||||
self.move_id._l10n_in_edi_cancel_invoice()
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="view_ewaybill_cancel_form" model="ir.ui.view">
|
||||
<field name="name">l10n_in_edi.cancel.form</field>
|
||||
<field name="model">l10n_in_edi.cancel</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<group>
|
||||
<group>
|
||||
<field name="cancel_reason"/>
|
||||
<field name="cancel_remarks"/>
|
||||
</group>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Cancel E-Invoice"
|
||||
name="cancel_l10n_in_edi_move"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
data-hotkey="c"/>
|
||||
<button string="Discard"
|
||||
class="btn-secondary"
|
||||
special="cancel"
|
||||
data-hotkey="x"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
[project]
|
||||
name = "odoo-bringout-oca-ocb-l10n_in_edi"
|
||||
version = "16.0.0"
|
||||
description = "Indian - E-invoicing - Odoo addon"
|
||||
description = "Indian - E-invoicing -
|
||||
Odoo addon
|
||||
"
|
||||
authors = [
|
||||
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
|
||||
]
|
||||
dependencies = [
|
||||
"odoo-bringout-oca-ocb-account_edi>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-l10n_in>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-iap>=16.0.0",
|
||||
"odoo-bringout-oca-ocb-account_edi>=19.0.0",
|
||||
"odoo-bringout-oca-ocb-l10n_in>=19.0.0",
|
||||
"requests>=2.25.1"
|
||||
]
|
||||
readme = "README.md"
|
||||
|
|
@ -18,7 +19,7 @@ classifiers = [
|
|||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Office/Business",
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue