diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js index 6336db16ebc..f54bce8aac7 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js @@ -1,6 +1,8 @@ {% include "erpnext/regional/india/taxes.js" %} +{% include "erpnext/regional/india/e_invoice/einvoice.js" %} erpnext.setup_auto_gst_taxation('Sales Invoice'); +erpnext.setup_einvoice_actions('Sales Invoice') frappe.ui.form.on("Sales Invoice", { setup: function(frm) { diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js index d9d6634c397..f01325d80bd 100644 --- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js +++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js @@ -36,4 +36,139 @@ frappe.listview_settings['Sales Invoice'].onload = function (list_view) { }; list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false); + + const generate_irns = () => { + const docnames = list_view.get_checked_items(true); + if (docnames && docnames.length) { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices', + args: { docnames }, + freeze: true, + freeze_message: __('Generating E-Invoices...') + }); + } else { + frappe.msgprint({ + message: __('Please select at least one sales invoice to generate IRN'), + title: __('No Invoice Selected'), + indicator: 'red' + }); + } + }; + + const cancel_irns = () => { + const docnames = list_view.get_checked_items(true); + + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irns', + args: { + doctype: list_view.doctype, + docnames, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + freeze_message: __('Cancelling E-Invoices...'), + }); + d.hide(); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + let einvoicing_enabled = false; + frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => { + einvoicing_enabled = enabled; + }); + + list_view.$result.on("change", "input[type=checkbox]", () => { + if (einvoicing_enabled) { + const docnames = list_view.get_checked_items(true); + // show/hide e-invoicing actions when no sales invoices are checked + if (docnames && docnames.length) { + // prevent adding actions twice if e-invoicing action group already exists + if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) { + list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing')); + list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing')); + } + } else { + list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing')); + list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing')); + } + } + }); + + frappe.realtime.on("bulk_einvoice_generation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices generated successfully', [invoices.length]), + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to generate IRNs for following ${failures.length} sales invoices: + + `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Generation Complete'), + indicator: 'orange' + }); + } + }); + + frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => { + const { failures, user, invoices } = data; + + if (invoices.length != failures.length) { + frappe.msgprint({ + message: __('{0} e-invoices cancelled successfully', [invoices.length]), + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + + if (failures && failures.length && user == frappe.session.user) { + let message = ` + Failed to cancel IRNs for following ${failures.length} sales invoices: + + `; + frappe.msgprint({ + message: message, + title: __('Bulk E-Invoice Cancellation Complete'), + indicator: 'orange' + }); + } + }); }; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index f04e7eacb93..bc443581e45 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -294,6 +294,8 @@ class SalesInvoice(SellingController): def before_cancel(self): self.check_if_consolidated_invoice() + + super(SalesInvoice, self).before_cancel() self.update_time_sheet(None) def on_cancel(self): diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 55e38530600..941061f2a22 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -2100,6 +2100,54 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(data['billLists'][0]['actualFromStateCode'],7) self.assertEqual(data['billLists'][0]['fromStateCode'],27) + def test_einvoice_submission_without_irn(self): + # init + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 1 + einvoice_settings.applicable_from = nowdate() + einvoice_settings.append('credentials', { + 'company': '_Test Company', + 'gstin': '27AAECE4835E1ZR', + 'username': 'test', + 'password': 'test' + }) + einvoice_settings.save() + + country = frappe.flags.country + frappe.flags.country = 'India' + + si = make_sales_invoice_for_ewaybill() + self.assertRaises(frappe.ValidationError, si.submit) + + si.irn = 'test_irn' + si.submit() + + # reset + einvoice_settings = frappe.get_doc('E Invoice Settings') + einvoice_settings.enable = 0 + frappe.flags.country = country + + def test_einvoice_json(self): + from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals + + si = get_sales_invoice_for_e_invoice() + si.discount_amount = 100 + si.save() + + einvoice = make_einvoice(si) + self.assertTrue(einvoice['EwbDtls']) + validate_totals(einvoice) + + si.apply_discount_on = 'Net Total' + si.save() + einvoice = make_einvoice(si) + validate_totals(einvoice) + + [d.set('included_in_print_rate', 1) for d in si.taxes] + si.save() + einvoice = make_einvoice(si) + validate_totals(einvoice) + def test_item_tax_net_range(self): item = create_item("T Shirt") diff --git a/erpnext/patches/v14_0/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py similarity index 100% rename from erpnext/patches/v14_0/__init__.py rename to erpnext/accounts/print_format/gst_e_invoice/__init__.py diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html new file mode 100644 index 00000000000..7643eca7635 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html @@ -0,0 +1,162 @@ +{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%} +{%- set einvoice = json.loads(doc.signed_einvoice) -%} + +
+
+ {% if letter_head and not no_letterhead %} +
{{ letter_head }}
+ {% endif %} + +
+ {% if print_settings.repeat_header_footer %} + + {% endif %} +
1. Transaction Details
+
+
+
+
+
{{ einvoice.Irn }}
+
+
+
+
{{ einvoice.AckNo }}
+
+
+
+
{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}
+
+
+
+
{{ einvoice.TranDtls.SupTyp }}
+
+
+
+
{{ einvoice.DocDtls.Typ }}
+
+
+
+
{{ einvoice.DocDtls.No }}
+
+
+
+ +
+
+
2. Party Details
+
+ {%- set seller = einvoice.SellerDtls -%} +
+
Seller
+

{{ seller.Gstin }}

+

{{ seller.LglNm }}

+

{{ seller.Addr1 }}

+ {%- if seller.Addr2 -%}

{{ seller.Addr2 }}

{% endif %} +

{{ seller.Loc }}

+

{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}

+ + {%- if einvoice.ShipDtls -%} + {%- set shipping = einvoice.ShipDtls -%} +
Shipping
+

{{ shipping.Gstin }}

+

{{ shipping.LglNm }}

+

{{ shipping.Addr1 }}

+ {%- if shipping.Addr2 -%}

{{ shipping.Addr2 }}

{% endif %} +

{{ shipping.Loc }}

+

{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}

+ {% endif %} +
+ {%- set buyer = einvoice.BuyerDtls -%} +
+
Buyer
+

{{ buyer.Gstin }}

+

{{ buyer.LglNm }}

+

{{ buyer.Addr1 }}

+ {%- if buyer.Addr2 -%}

{{ buyer.Addr2 }}

{% endif %} +

{{ buyer.Loc }}

+

{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}

+
+
+
+
3. Item Details
+ + + + + + + + + + + + + + + + + + {% for item in einvoice.ItemList %} + + + + + + + + + + + + + + {% endfor %} + +
Sr. No.ItemHSN CodeQtyUOMRateDiscountTaxable AmountTax RateOther ChargesTotal
{{ item.SlNo }}{{ item.PrdDesc }}{{ item.HsnCd }}{{ item.Qty }}{{ item.Unit }}{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}{{ item.GstRt + item.CesRt }} %{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}
+
+
+
4. Value Details
+ + + + + + + + + + + + + + + + + {%- set value_details = einvoice.ValDtls -%} + + + + + + + + + + + + + +
Taxable AmountCGSTSGSTIGSTCESSState CESSDiscountOther ChargesRound OffTotal Value
{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}{{ frappe.utils.fmt_money(0, None, "INR") }}{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}
+
+
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json new file mode 100644 index 00000000000..1001199a092 --- /dev/null +++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json @@ -0,0 +1,24 @@ +{ + "align_labels_right": 1, + "creation": "2020-10-10 18:01:21.032914", + "custom_format": 0, + "default_print_language": "en-US", + "disabled": 1, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "", + "idx": 0, + "line_breaks": 1, + "modified": "2020-10-23 19:54:40.634936", + "modified_by": "Administrator", + "module": "Accounts", + "name": "GST E-Invoice", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 1, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4775f56a01b..bab16a494d6 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -167,9 +167,14 @@ class AccountsController(TransactionBase): validate_regional(self) + validate_einvoice_fields(self) + if self.doctype != 'Material Request': apply_pricing_rule_on_transaction(self) + def before_cancel(self): + validate_einvoice_fields(self) + def on_trash(self): # delete sl and gl entries on deletion of transaction if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'): @@ -2151,3 +2156,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil @erpnext.allow_regional def validate_regional(doc): pass + +@erpnext.allow_regional +def validate_einvoice_fields(doc): + pass diff --git a/erpnext/hooks.py b/erpnext/hooks.py index d172da3f1fa..04f5793836f 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -443,6 +443,7 @@ regional_overrides = { 'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts', 'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption', 'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period', + 'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields', 'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount', 'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code' }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9f7e733af42..41cb4835660 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -203,6 +203,7 @@ execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation") erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020 erpnext.patches.v12_0.create_itc_reversal_custom_fields +execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings") erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020 erpnext.patches.v12_0.add_taxjar_integration_field @@ -223,6 +224,7 @@ erpnext.patches.v13_0.set_youtube_video_id erpnext.patches.v13_0.set_app_name erpnext.patches.v13_0.print_uom_after_quantity_patch erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account +erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 erpnext.patches.v13_0.updates_for_multi_currency_payroll erpnext.patches.v13_0.update_reason_for_resignation_in_employee execute:frappe.delete_doc("Report", "Quoted Item Comparison") @@ -241,12 +243,15 @@ erpnext.patches.v13_0.update_payment_terms_outstanding erpnext.patches.v12_0.add_state_code_for_ladakh erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes -erpnext.patches.v13_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.update_vehicle_no_reqd_condition +erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17 +erpnext.patches.v12_0.add_einvoice_summary_report_permissions erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae erpnext.patches.v13_0.setup_uae_vat_fields execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext') +erpnext.patches.v12_0.add_company_link_to_einvoice_settings erpnext.patches.v12_0.create_taxable_value_field erpnext.patches.v12_0.add_gst_category_in_delivery_note erpnext.patches.v12_0.purchase_receipt_status @@ -255,6 +260,7 @@ erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022 erpnext.patches.v13_0.update_shipment_status erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting +erpnext.patches.v12_0.add_ewaybill_validity_field erpnext.patches.v13_0.germany_make_custom_fields erpnext.patches.v13_0.germany_fill_debtor_creditor_number erpnext.patches.v13_0.set_pos_closing_as_failed @@ -271,6 +277,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry erpnext.patches.v13_0.update_subscription_status_in_memberships erpnext.patches.v13_0.update_amt_in_work_order_required_items +erpnext.patches.v12_0.show_einvoice_irn_cancelled_field erpnext.patches.v13_0.delete_orphaned_tables erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16 erpnext.patches.v13_0.update_tds_check_field #3 @@ -327,10 +334,10 @@ erpnext.patches.v13_0.delete_bank_reconciliation_detail [post_model_sync] erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template -erpnext.patches.v14_0.delete_einvoicing_doctypes erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v14_0.delete_hub_doctypes erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022 erpnext.patches.v14_0.delete_agriculture_doctypes erpnext.patches.v14_0.rearrange_company_fields erpnext.patches.v14_0.update_leave_notification_template +erpnext.patches.v14_0.restore_einvoice_fields \ No newline at end of file diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py new file mode 100644 index 00000000000..e498b673fbf --- /dev/null +++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company or not frappe.db.count('E Invoice User'): + return + + frappe.reload_doc("regional", "doctype", "e_invoice_user") + for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']): + company_name = frappe.db.sql(""" + select dl.link_name from `tabAddress` a, `tabDynamic Link` dl + where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company' + """, (creds.get('gstin'))) + if company_name and len(company_name) > 0: + frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0]) diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py new file mode 100644 index 00000000000..aeff9ca8413 --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_status_field.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals + +import json + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + # move hidden einvoice fields to a different section + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), + + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + + if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'): + frappe.db.sql(''' + UPDATE `tabSales Invoice` SET einvoice_status = 'Pending' + WHERE + posting_date >= '2021-04-01' + AND ifnull(irn, '') = '' + AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '') + AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export') + ''') + + # set appropriate statuses + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated' + WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''') + + frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled' + WHERE ifnull(irn_cancelled, 0) = 1''') + + # set correct acknowledgement in e-invoices + einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice']) + + if einvoices: + for inv in einvoices: + signed_einvoice = inv.get('signed_einvoice') + if signed_einvoice: + signed_einvoice = json.loads(signed_einvoice) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False) + frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False) diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py new file mode 100644 index 00000000000..e837786138f --- /dev/null +++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py @@ -0,0 +1,20 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + if frappe.db.exists('Report', 'E-Invoice Summary') and \ + not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')): + frappe.get_doc(dict( + doctype='Custom Role', + report='E-Invoice Summary', + roles= [ + dict(role='Accounts User'), + dict(role='Accounts Manager') + ] + )).insert() diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py new file mode 100644 index 00000000000..247140d21d0 --- /dev/null +++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py @@ -0,0 +1,18 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill') + ] + } + create_custom_fields(custom_fields, update=True) diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py new file mode 100644 index 00000000000..c17666add18 --- /dev/null +++ b/erpnext/patches/v12_0/setup_einvoice_fields.py @@ -0,0 +1,59 @@ +from __future__ import unicode_literals + +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +from erpnext.regional.india.setup import add_permissions, add_print_formats + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + frappe.reload_doc("custom", "doctype", "custom_field") + frappe.reload_doc("regional", "doctype", "e_invoice_settings") + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1) + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + add_print_formats() + + einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)' + t = { + 'mode_of_transport': [{'default': None}], + 'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}], + 'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}], + 'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}], + 'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}], + 'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}], + 'ewaybill': [ + {'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'}, + {'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'} + ] + } + + for field, conditions in t.items(): + for c in conditions: + [(prop, value)] = c.items() + frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value) diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py new file mode 100644 index 00000000000..3f90a03020f --- /dev/null +++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py @@ -0,0 +1,14 @@ +from __future__ import unicode_literals + +import frappe + + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'}) + if irn_cancelled_field: + frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn') + frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0) diff --git a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py b/erpnext/patches/v13_0/einvoicing_deprecation_warning.py deleted file mode 100644 index e123a55f5ab..00000000000 --- a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py +++ /dev/null @@ -1,9 +0,0 @@ -import click - - -def execute(): - click.secho( - "Indian E-Invoicing integration is moved to a separate app and will be removed from ERPNext in version-14.\n" - "Please install the app to continue using the integration: https://github.com/frappe/erpnext_gst_compliance", - fg="yellow", - ) diff --git a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py b/erpnext/patches/v14_0/delete_einvoicing_doctypes.py deleted file mode 100644 index a3a8149be32..00000000000 --- a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py +++ /dev/null @@ -1,10 +0,0 @@ -import frappe - - -def execute(): - frappe.delete_doc('DocType', 'E Invoice Settings', ignore_missing=True) - frappe.delete_doc('DocType', 'E Invoice User', ignore_missing=True) - frappe.delete_doc('Report', 'E-Invoice Summary', ignore_missing=True) - frappe.delete_doc('Print Format', 'GST E-Invoice', ignore_missing=True) - frappe.delete_doc('Custom Field', 'Sales Invoice-eway_bill_cancelled', ignore_missing=True) - frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True) diff --git a/erpnext/patches/v14_0/restore_einvoice_fields.py b/erpnext/patches/v14_0/restore_einvoice_fields.py new file mode 100644 index 00000000000..c4431fb9dbe --- /dev/null +++ b/erpnext/patches/v14_0/restore_einvoice_fields.py @@ -0,0 +1,24 @@ +import frappe +from frappe.custom.doctype.custom_field.custom_field import create_custom_fields + +from erpnext.regional.india.setup import add_permissions, add_print_formats + + +def execute(): + # restores back the 2 custom fields that was deleted while removing e-invoicing from v14 + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + custom_fields = { + 'Sales Invoice': [ + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + ] + } + create_custom_fields(custom_fields, update=True) + add_permissions() + add_print_formats() diff --git a/erpnext/regional/doctype/e_invoice_request_log/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js new file mode 100644 index 00000000000..7b7ba964e5e --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('E Invoice Request Log', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json new file mode 100644 index 00000000000..3034370feac --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json @@ -0,0 +1,102 @@ +{ + "actions": [], + "autoname": "EINV-REQ-.#####", + "creation": "2020-12-08 12:54:08.175992", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user", + "url", + "headers", + "response", + "column_break_7", + "timestamp", + "reference_invoice", + "data" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User" + }, + { + "fieldname": "reference_invoice", + "fieldtype": "Data", + "label": "Reference Invoice" + }, + { + "fieldname": "headers", + "fieldtype": "Code", + "label": "Headers", + "options": "JSON" + }, + { + "fieldname": "data", + "fieldtype": "Code", + "label": "Data", + "options": "JSON" + }, + { + "default": "Now", + "fieldname": "timestamp", + "fieldtype": "Datetime", + "label": "Timestamp" + }, + { + "fieldname": "response", + "fieldtype": "Code", + "label": "Response", + "options": "JSON" + }, + { + "fieldname": "url", + "fieldtype": "Data", + "label": "URL" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-01-13 12:06:57.253111", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Request Log", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py new file mode 100644 index 00000000000..38fe3089412 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class EInvoiceRequestLog(Document): + pass diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py new file mode 100644 index 00000000000..091cc88e454 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + + +class TestEInvoiceRequestLog(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/e_invoice_settings/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js new file mode 100644 index 00000000000..54e488610df --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js @@ -0,0 +1,11 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('E Invoice Settings', { + refresh(frm) { + const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing'; + frm.dashboard.set_headline( + __("Read {0} for more information on E Invoicing features.", [`documentation`]) + ); + } +}); diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json new file mode 100644 index 00000000000..68ed3391d04 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json @@ -0,0 +1,73 @@ +{ + "actions": [], + "creation": "2020-09-24 16:23:16.235722", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable", + "section_break_2", + "sandbox_mode", + "applicable_from", + "credentials", + "auth_token", + "token_expiry" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "depends_on": "enable", + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "auth_token", + "fieldtype": "Data", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "token_expiry", + "fieldtype": "Datetime", + "hidden": 1, + "read_only": 1 + }, + { + "fieldname": "credentials", + "fieldtype": "Table", + "label": "Credentials", + "mandatory_depends_on": "enable", + "options": "E Invoice User" + }, + { + "default": "0", + "fieldname": "sandbox_mode", + "fieldtype": "Check", + "label": "Sandbox Mode" + }, + { + "fieldname": "applicable_from", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Applicable From", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "issingle": 1, + "links": [], + "modified": "2021-03-30 12:26:25.538294", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice Settings", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py new file mode 100644 index 00000000000..70ec2ed0679 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt +from __future__ import unicode_literals + +import frappe +from frappe import _ +from frappe.model.document import Document + + +class EInvoiceSettings(Document): + def validate(self): + if self.enable and not self.credentials: + frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.')) diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py new file mode 100644 index 00000000000..10770deb0ee --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + + +class TestEInvoiceSettings(unittest.TestCase): + pass diff --git a/erpnext/regional/doctype/e_invoice_user/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json new file mode 100644 index 00000000000..a65b1ca7ca8 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2020-12-22 15:02:46.229474", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "gstin", + "username", + "password" + ], + "fields": [ + { + "fieldname": "gstin", + "fieldtype": "Data", + "in_list_view": 1, + "label": "GSTIN", + "reqd": 1 + }, + { + "fieldname": "username", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Username", + "reqd": 1 + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Password", + "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-03-22 12:16:56.365616", + "modified_by": "Administrator", + "module": "Regional", + "name": "E Invoice User", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py new file mode 100644 index 00000000000..a0fe399f110 --- /dev/null +++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +# import frappe +from frappe.model.document import Document + + +class EInvoiceUser(Document): + pass diff --git a/erpnext/regional/india/e_invoice/__init__.py b/erpnext/regional/india/e_invoice/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json new file mode 100644 index 00000000000..78e56518dff --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_item_template.json @@ -0,0 +1,31 @@ +{{ + "SlNo": "{item.sr_no}", + "PrdDesc": "{item.description}", + "IsServc": "{item.is_service_item}", + "HsnCd": "{item.gst_hsn_code}", + "Barcde": "{item.barcode}", + "Unit": "{item.uom}", + "Qty": "{item.qty}", + "FreeQty": "{item.free_qty}", + "UnitPrice": "{item.unit_rate}", + "TotAmt": "{item.gross_amount}", + "Discount": "{item.discount_amount}", + "AssAmt": "{item.taxable_value}", + "PrdSlNo": "{item.serial_no}", + "GstRt": "{item.tax_rate}", + "IgstAmt": "{item.igst_amount}", + "CgstAmt": "{item.cgst_amount}", + "SgstAmt": "{item.sgst_amount}", + "CesRt": "{item.cess_rate}", + "CesAmt": "{item.cess_amount}", + "CesNonAdvlAmt": "{item.cess_nadv_amount}", + "StateCesRt": "{item.state_cess_rate}", + "StateCesAmt": "{item.state_cess_amount}", + "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}", + "OthChrg": "{item.other_charges}", + "TotItemVal": "{item.total_value}", + "BchDtls": {{ + "Nm": "{item.batch_no}", + "ExpDt": "{item.batch_expiry_date}" + }} +}} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json new file mode 100644 index 00000000000..60f490d6166 --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_template.json @@ -0,0 +1,110 @@ +{{ + "Version": "1.1", + "TranDtls": {{ + "TaxSch": "{transaction_details.tax_scheme}", + "SupTyp": "{transaction_details.supply_type}", + "RegRev": "{transaction_details.reverse_charge}", + "EcmGstin": "{transaction_details.ecom_gstin}", + "IgstOnIntra": "{transaction_details.igst_on_intra}" + }}, + "DocDtls": {{ + "Typ": "{doc_details.invoice_type}", + "No": "{doc_details.invoice_name}", + "Dt": "{doc_details.invoice_date}" + }}, + "SellerDtls": {{ + "Gstin": "{seller_details.gstin}", + "LglNm": "{seller_details.legal_name}", + "TrdNm": "{seller_details.trade_name}", + "Loc": "{seller_details.location}", + "Pin": "{seller_details.pincode}", + "Stcd": "{seller_details.state_code}", + "Addr1": "{seller_details.address_line1}", + "Addr2": "{seller_details.address_line2}", + "Ph": "{seller_details.phone}", + "Em": "{seller_details.email}" + }}, + "BuyerDtls": {{ + "Gstin": "{buyer_details.gstin}", + "LglNm": "{buyer_details.legal_name}", + "TrdNm": "{buyer_details.trade_name}", + "Addr1": "{buyer_details.address_line1}", + "Addr2": "{buyer_details.address_line2}", + "Loc": "{buyer_details.location}", + "Pin": "{buyer_details.pincode}", + "Stcd": "{buyer_details.state_code}", + "Ph": "{buyer_details.phone}", + "Em": "{buyer_details.email}", + "Pos": "{buyer_details.place_of_supply}" + }}, + "DispDtls": {{ + "Nm": "{dispatch_details.company_name}", + "Addr1": "{dispatch_details.address_line1}", + "Addr2": "{dispatch_details.address_line2}", + "Loc": "{dispatch_details.location}", + "Pin": "{dispatch_details.pincode}", + "Stcd": "{dispatch_details.state_code}" + }}, + "ShipDtls": {{ + "Gstin": "{shipping_details.gstin}", + "LglNm": "{shipping_details.legal_name}", + "TrdNm": "{shipping_details.trader_name}", + "Addr1": "{shipping_details.address_line1}", + "Addr2": "{shipping_details.address_line2}", + "Loc": "{shipping_details.location}", + "Pin": "{shipping_details.pincode}", + "Stcd": "{shipping_details.state_code}" + }}, + "ItemList": [ + {item_list} + ], + "ValDtls": {{ + "AssVal": "{invoice_value_details.base_total}", + "CgstVal": "{invoice_value_details.total_cgst_amt}", + "SgstVal": "{invoice_value_details.total_sgst_amt}", + "IgstVal": "{invoice_value_details.total_igst_amt}", + "CesVal": "{invoice_value_details.total_cess_amt}", + "Discount": "{invoice_value_details.invoice_discount_amt}", + "RndOffAmt": "{invoice_value_details.round_off}", + "OthChrg": "{invoice_value_details.total_other_charges}", + "TotInvVal": "{invoice_value_details.base_grand_total}", + "TotInvValFc": "{invoice_value_details.grand_total}" + }}, + "PayDtls": {{ + "Nm": "{payment_details.payee_name}", + "AccDet": "{payment_details.account_no}", + "Mode": "{payment_details.mode_of_payment}", + "FinInsBr": "{payment_details.ifsc_code}", + "PayTerm": "{payment_details.terms}", + "PaidAmt": "{payment_details.paid_amount}", + "PaymtDue": "{payment_details.outstanding_amount}" + }}, + "RefDtls": {{ + "DocPerdDtls": {{ + "InvStDt": "{period_details.start_date}", + "InvEndDt": "{period_details.end_date}" + }}, + "PrecDocDtls": [{{ + "InvNo": "{prev_doc_details.invoice_name}", + "InvDt": "{prev_doc_details.invoice_date}" + }}] + }}, + "ExpDtls": {{ + "ShipBNo": "{export_details.bill_no}", + "ShipBDt": "{export_details.bill_date}", + "Port": "{export_details.port}", + "ForCur": "{export_details.foreign_curr_code}", + "CntCode": "{export_details.country_code}", + "ExpDuty": "{export_details.export_duty}" + }}, + "EwbDtls": {{ + "TransId": "{eway_bill_details.gstin}", + "TransName": "{eway_bill_details.name}", + "TransMode": "{eway_bill_details.mode_of_transport}", + "Distance": "{eway_bill_details.distance}", + "TransDocNo": "{eway_bill_details.document_name}", + "TransDocDt": "{eway_bill_details.document_date}", + "VehNo": "{eway_bill_details.vehicle_no}", + "VehType": "{eway_bill_details.vehicle_type}" + }} +}} \ No newline at end of file diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json new file mode 100644 index 00000000000..f4a3542a60e --- /dev/null +++ b/erpnext/regional/india/e_invoice/einv_validation.json @@ -0,0 +1,957 @@ +{ + "Version": { + "type": "string", + "minLength": 1, + "maxLength": 6, + "description": "Version of the schema" + }, + "Irn": { + "type": "string", + "minLength": 64, + "maxLength": 64, + "description": "Invoice Reference Number" + }, + "TranDtls": { + "type": "object", + "properties": { + "TaxSch": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["GST"], + "description": "GST- Goods and Services Tax Scheme" + }, + "SupTyp": { + "type": "string", + "minLength": 3, + "maxLength": 10, + "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"], + "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export" + }, + "RegRev": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Y- whether the tax liability is payable under reverse charge" + }, + "EcmGstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "E-Commerce GSTIN", + "validationMsg": "E-Commerce GSTIN is invalid" + }, + "IgstOnIntra": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Y- indicates the supply is intra state but chargeable to IGST" + } + }, + "required": ["TaxSch", "SupTyp"] + }, + "DocDtls": { + "type": "object", + "properties": { + "Typ": { + "type": "string", + "minLength": 3, + "maxLength": 3, + "enum": ["INV", "CRN", "DBN"], + "description": "Document Type" + }, + "No": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$", + "description": "Document Number", + "validationMsg": "Document Number should not be starting with 0, / and -" + }, + "Dt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Document Date" + } + }, + "required": ["Typ", "No", "Dt"] + }, + "SellerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "pattern": "([0-9]{2}[0-9A-Z]{13})", + "description": "Supplier GSTIN", + "validationMsg": "Company GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Tradename" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 50, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Supplier State Code" + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12, + "description": "Phone" + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100, + "description": "Email-Id" + } + }, + "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "BuyerDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "minLength": 3, + "maxLength": 15, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Buyer GSTIN", + "validationMsg": "Customer GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Trade Name" + }, + "Pos": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Place of Supply State code" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "Buyer State Code" + }, + "Ph": { + "type": "string", + "minLength": 6, + "maxLength": 12, + "description": "Phone" + }, + "Em": { + "type": "string", + "minLength": 6, + "maxLength": 100, + "description": "Email-Id" + } + }, + "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"] + }, + "DispDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Dispatch Address Name" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "State Code" + } + }, + "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ShipDtls": { + "type": "object", + "properties": { + "Gstin": { + "type": "string", + "maxLength": 15, + "minLength": 3, + "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$", + "description": "Shipping Address GSTIN", + "validationMsg": "Shipping Address GSTIN is invalid" + }, + "LglNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Legal Name" + }, + "TrdNm": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Trade Name" + }, + "Addr1": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Address Line 1" + }, + "Addr2": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Address Line 2" + }, + "Loc": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Location" + }, + "Pin": { + "type": "number", + "minimum": 100000, + "maximum": 999999, + "description": "Pincode" + }, + "Stcd": { + "type": "string", + "minLength": 1, + "maxLength": 2, + "description": "State Code" + } + }, + "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"] + }, + "ItemList": { + "type": "Array", + "properties": { + "SlNo": { + "type": "string", + "minLength": 1, + "maxLength": 6, + "description": "Serial No. of Item" + }, + "PrdDesc": { + "type": "string", + "minLength": 3, + "maxLength": 300, + "description": "Item Name" + }, + "IsServc": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["Y", "N"], + "description": "Is Service Item" + }, + "HsnCd": { + "type": "string", + "minLength": 4, + "maxLength": 8, + "description": "HSN Code" + }, + "Barcde": { + "type": "string", + "minLength": 3, + "maxLength": 30, + "description": "Barcode" + }, + "Qty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999, + "description": "Quantity" + }, + "FreeQty": { + "type": "number", + "minimum": 0, + "maximum": 9999999999.999, + "description": "Free Quantity" + }, + "Unit": { + "type": "string", + "minLength": 3, + "maxLength": 8, + "description": "UOM" + }, + "UnitPrice": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.999, + "description": "Rate" + }, + "TotAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Gross Amount" + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Discount" + }, + "PreTaxVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Pre tax value" + }, + "AssAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Taxable Value" + }, + "GstRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "GST Rate" + }, + "IgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "IGST Amount" + }, + "CgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "CGST Amount" + }, + "SgstAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "SGST Amount" + }, + "CesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "Cess Rate" + }, + "CesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Cess Amount (Advalorem)" + }, + "CesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Cess Amount (Non-Advalorem)" + }, + "StateCesRt": { + "type": "number", + "minimum": 0, + "maximum": 999.999, + "description": "State CESS Rate" + }, + "StateCesAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "State CESS Amount" + }, + "StateCesNonAdvlAmt": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "State CESS Amount (Non Advalorem)" + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Other Charges" + }, + "TotItemVal": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Total Item Value" + }, + "OrdLineRef": { + "type": "string", + "minLength": 1, + "maxLength": 50, + "description": "Order line reference" + }, + "OrgCntry": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Origin Country" + }, + "PrdSlNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Serial number" + }, + "BchDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 3, + "maxLength": 20, + "description": "Batch number" + }, + "ExpDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Batch Expiry Date" + }, + "WrDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Warranty Date" + } + }, + "required": ["Nm"] + }, + "AttribDtls": { + "type": "Array", + "Attribute": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Attribute name of the item" + }, + "Val": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Attribute value of the item" + } + } + } + } + }, + "required": [ + "SlNo", + "IsServc", + "HsnCd", + "UnitPrice", + "TotAmt", + "AssAmt", + "GstRt", + "TotItemVal" + ] + }, + "ValDtls": { + "type": "object", + "properties": { + "AssVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total Assessable value of all items" + }, + "CgstVal": { + "type": "number", + "maximum": 99999999999999.99, + "minimum": 0, + "description": "Total CGST value of all items" + }, + "SgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total SGST value of all items" + }, + "IgstVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total IGST value of all items" + }, + "CesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total CESS value of all items" + }, + "StCesVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Total State CESS value of all items" + }, + "Discount": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Invoice Discount" + }, + "OthChrg": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Other Charges" + }, + "RndOffAmt": { + "type": "number", + "minimum": -99.99, + "maximum": 99.99, + "description": "Rounded off Amount" + }, + "TotInvVal": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Final Invoice Value " + }, + "TotInvValFc": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Final Invoice value in Foreign Currency" + } + }, + "required": ["AssVal", "TotInvVal"] + }, + "PayDtls": { + "type": "object", + "properties": { + "Nm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Payee Name" + }, + "AccDet": { + "type": "string", + "minLength": 1, + "maxLength": 18, + "description": "Bank Account Number of Payee" + }, + "Mode": { + "type": "string", + "minLength": 1, + "maxLength": 18, + "description": "Mode of Payment" + }, + "FinInsBr": { + "type": "string", + "minLength": 1, + "maxLength": 11, + "description": "Branch or IFSC code" + }, + "PayTerm": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Terms of Payment" + }, + "PayInstr": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Payment Instruction" + }, + "CrTrn": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Credit Transfer" + }, + "DirDr": { + "type": "string", + "minLength": 1, + "maxLength": 100, + "description": "Direct Debit" + }, + "CrDay": { + "type": "number", + "minimum": 0, + "maximum": 9999, + "description": "Credit Days" + }, + "PaidAmt": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Advance Amount" + }, + "PaymtDue": { + "type": "number", + "minimum": 0, + "maximum": 99999999999999.99, + "description": "Outstanding Amount" + } + } + }, + "RefDtls": { + "type": "object", + "properties": { + "InvRm": { + "type": "string", + "maxLength": 100, + "minLength": 3, + "pattern": "^[0-9A-Za-z/-]{3,100}$", + "description": "Remarks/Note" + }, + "DocPerdDtls": { + "type": "object", + "properties": { + "InvStDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period Start Date" + }, + "InvEndDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Invoice Period End Date" + } + }, + "required": ["InvStDt ", "InvEndDt "] + }, + "PrecDocDtls": { + "type": "object", + "properties": { + "InvNo": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$", + "description": "Reference of Original Invoice" + }, + "InvDt": { + "type": "string", + "maxLength": 10, + "minLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of Orginal Invoice" + }, + "OthRefNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Other Reference" + } + } + }, + "required": ["InvNo", "InvDt"], + "ContrDtls": { + "type": "object", + "properties": { + "RecAdvRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Receipt Advice No." + }, + "RecAdvDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Date of receipt advice" + }, + "TendRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Lot/Batch Reference No." + }, + "ContrRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Contract Reference Number" + }, + "ExtRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Any other reference" + }, + "ProjRefr": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "pattern": "^([0-9A-Za-z/-]){1,20}$", + "description": "Project Reference Number" + }, + "PORefr": { + "type": "string", + "minLength": 1, + "maxLength": 16, + "pattern": "^([0-9A-Za-z/-]){1,16}$", + "description": "PO Reference Number" + }, + "PORefDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "PO Reference date" + } + } + } + } + }, + "AddlDocDtls": { + "type": "Array", + "properties": { + "Url": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Supporting document URL" + }, + "Docs": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Supporting document in Base64 Format" + }, + "Info": { + "type": "string", + "minLength": 3, + "maxLength": 1000, + "description": "Any additional information" + } + } + }, + + "ExpDtls": { + "type": "object", + "properties": { + "ShipBNo": { + "type": "string", + "minLength": 1, + "maxLength": 20, + "description": "Shipping Bill No." + }, + "ShipBDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Shipping Bill Date" + }, + "Port": { + "type": "string", + "minLength": 2, + "maxLength": 10, + "pattern": "^[0-9A-Za-z]{2,10}$", + "description": "Port Code. Refer the master" + }, + "RefClm": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "description": "Claiming Refund. Y/N" + }, + "ForCur": { + "type": "string", + "minLength": 3, + "maxLength": 16, + "description": "Additional Currency Code. Refer the master" + }, + "CntCode": { + "type": "string", + "minLength": 2, + "maxLength": 2, + "description": "Country Code. Refer the master" + }, + "ExpDuty": { + "type": "number", + "minimum": 0, + "maximum": 999999999999.99, + "description": "Export Duty" + } + } + }, + "EwbDtls": { + "type": "object", + "properties": { + "TransId": { + "type": "string", + "minLength": 15, + "maxLength": 15, + "description": "Transporter GSTIN" + }, + "TransName": { + "type": "string", + "minLength": 3, + "maxLength": 100, + "description": "Transporter Name" + }, + "TransMode": { + "type": "string", + "maxLength": 1, + "minLength": 1, + "enum": ["1", "2", "3", "4"], + "description": "Mode of Transport" + }, + "Distance": { + "type": "number", + "minimum": 1, + "maximum": 9999, + "description": "Distance" + }, + "TransDocNo": { + "type": "string", + "minLength": 1, + "maxLength": 15, + "pattern": "^([0-9A-Z/-]){1,15}$", + "description": "Tranport Document Number", + "validationMsg": "Transport Receipt No is invalid" + }, + "TransDocDt": { + "type": "string", + "minLength": 10, + "maxLength": 10, + "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]", + "description": "Transport Document Date" + }, + "VehNo": { + "type": "string", + "minLength": 4, + "maxLength": 20, + "description": "Vehicle Number" + }, + "VehType": { + "type": "string", + "minLength": 1, + "maxLength": 1, + "enum": ["O", "R"], + "description": "Vehicle Type" + } + }, + "required": ["Distance"] + }, + "required": [ + "Version", + "TranDtls", + "DocDtls", + "SellerDtls", + "BuyerDtls", + "ItemList", + "ValDtls" + ] +} diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js new file mode 100644 index 00000000000..348f0c6feed --- /dev/null +++ b/erpnext/regional/india/e_invoice/einvoice.js @@ -0,0 +1,292 @@ +erpnext.setup_einvoice_actions = (doctype) => { + frappe.ui.form.on(doctype, { + async refresh(frm) { + if (frm.doc.docstatus == 2) return; + + const res = await frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility', + args: { doc: frm.doc } + }); + const invoice_eligible = res.message; + + if (!invoice_eligible) return; + + const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; + + const add_custom_button = (label, action) => { + if (!frm.custom_buttons[label]) { + frm.add_custom_button(label, action, __('E Invoicing')); + } + }; + + if (!irn && !__unsaved) { + const action = () => { + if (frm.doc.__unsaved) { + frappe.throw(__('Please save the document to generate IRN.')); + } + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.get_einvoice', + args: { doctype, docname: name }, + freeze: true, + callback: (res) => { + const einvoice = res.message; + show_einvoice_preview(frm, einvoice); + } + }); + }; + + add_custom_button(__("Generate IRN"), action); + } + + if (irn && !irn_cancelled && !ewaybill) { + const fields = [ + { + "label": "Reason", + "fieldname": "reason", + "fieldtype": "Select", + "reqd": 1, + "default": "1-Duplicate", + "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] + }, + { + "label": "Remark", + "fieldname": "remark", + "fieldtype": "Data", + "reqd": 1 + } + ]; + const action = () => { + const d = new frappe.ui.Dialog({ + title: __("Cancel IRN"), + fields: fields, + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_irn', + args: { + doctype, + docname: name, + irn: irn, + reason: data.reason.split('-')[0], + remark: data.remark + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + add_custom_button(__("Cancel IRN"), action); + } + + if (irn && !irn_cancelled && !ewaybill) { + const action = () => { + const d = new frappe.ui.Dialog({ + title: __('Generate E-Way Bill'), + size: "large", + fields: get_ewaybill_fields(frm), + primary_action: function() { + const data = d.get_values(); + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill', + args: { + doctype, + docname: name, + irn, + ...data + }, + freeze: true, + callback: () => frm.reload_doc() || d.hide(), + error: () => d.hide() + }); + }, + primary_action_label: __('Submit') + }); + d.show(); + }; + + add_custom_button(__("Generate E-Way Bill"), action); + } + + if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { + const action = () => { + let message = __('Cancellation of e-way bill is currently not supported.') + ' '; + message += '

'; + message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.'); + + const dialog = frappe.msgprint({ + title: __('Update E-Way Bill Cancelled Status?'), + message: message, + indicator: 'orange', + primary_action: { + action: function() { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', + args: { doctype, docname: name }, + freeze: true, + callback: () => frm.reload_doc() || dialog.hide() + }); + } + }, + primary_action_label: __('Yes') + }); + }; + add_custom_button(__("Cancel E-Way Bill"), action); + } + } + }); +}; + +const get_ewaybill_fields = (frm) => { + return [ + { + 'fieldname': 'transporter', + 'label': 'Transporter', + 'fieldtype': 'Link', + 'options': 'Supplier', + 'default': frm.doc.transporter + }, + { + 'fieldname': 'gst_transporter_id', + 'label': 'GST Transporter ID', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.gst_transporter_id', + 'default': frm.doc.gst_transporter_id + }, + { + 'fieldname': 'driver', + 'label': 'Driver', + 'fieldtype': 'Link', + 'options': 'Driver', + 'default': frm.doc.driver + }, + { + 'fieldname': 'lr_no', + 'label': 'Transport Receipt No', + 'fieldtype': 'Data', + 'default': frm.doc.lr_no + }, + { + 'fieldname': 'vehicle_no', + 'label': 'Vehicle No', + 'fieldtype': 'Data', + 'default': frm.doc.vehicle_no + }, + { + 'fieldname': 'distance', + 'label': 'Distance (in km)', + 'fieldtype': 'Float', + 'default': frm.doc.distance + }, + { + 'fieldname': 'transporter_col_break', + 'fieldtype': 'Column Break', + }, + { + 'fieldname': 'transporter_name', + 'label': 'Transporter Name', + 'fieldtype': 'Data', + 'fetch_from': 'transporter.name', + 'read_only': 1, + 'default': frm.doc.transporter_name + }, + { + 'fieldname': 'mode_of_transport', + 'label': 'Mode of Transport', + 'fieldtype': 'Select', + 'options': `\nRoad\nAir\nRail\nShip`, + 'default': frm.doc.mode_of_transport + }, + { + 'fieldname': 'driver_name', + 'label': 'Driver Name', + 'fieldtype': 'Data', + 'fetch_from': 'driver.full_name', + 'read_only': 1, + 'default': frm.doc.driver_name + }, + { + 'fieldname': 'lr_date', + 'label': 'Transport Receipt Date', + 'fieldtype': 'Date', + 'default': frm.doc.lr_date + }, + { + 'fieldname': 'gst_vehicle_type', + 'label': 'GST Vehicle Type', + 'fieldtype': 'Select', + 'options': `Regular\nOver Dimensional Cargo (ODC)`, + 'depends_on': 'eval:(doc.mode_of_transport === "Road")', + 'default': frm.doc.gst_vehicle_type + } + ]; +}; + +const request_irn_generation = (frm) => { + frappe.call({ + method: 'erpnext.regional.india.e_invoice.utils.generate_irn', + args: { doctype: frm.doc.doctype, docname: frm.doc.name }, + freeze: true, + callback: () => frm.reload_doc() + }); +}; + +const get_preview_dialog = (frm, action) => { + const dialog = new frappe.ui.Dialog({ + title: __("Preview"), + size: "large", + fields: [ + { + "label": "Preview", + "fieldname": "preview_html", + "fieldtype": "HTML" + } + ], + primary_action: () => action(frm) || dialog.hide(), + primary_action_label: __('Generate IRN') + }); + return dialog; +}; + +const show_einvoice_preview = (frm, einvoice) => { + const preview_dialog = get_preview_dialog(frm, request_irn_generation); + + // initialize e-invoice fields + einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate(); + frm.doc.signed_einvoice = JSON.stringify(einvoice); + + // initialize preview wrapper + const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper; + $preview_wrapper.html( + `
+ +
+
` + ); + + frappe.call({ + method: "frappe.www.printview.get_html_and_style", + args: { + doc: frm.doc, + print_format: "GST E-Invoice", + no_letterhead: 1 + }, + callback: function (r) { + if (!r.exc) { + $preview_wrapper.find(".print-format").html(r.message.html); + const style = ` + .print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; } + .print-preview { min-height: 0px; } + .modal-dialog { width: 720px; }`; + + frappe.dom.set_style(style, "custom-print-style"); + preview_dialog.show(); + } + } + }); +}; diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 4b9942121aa..074bd527e2c 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -60,7 +60,7 @@ def create_hsn_codes(data, code_field): def add_custom_roles_for_reports(): for report_name in ('GST Sales Register', 'GST Purchase Register', - 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'): + 'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'): if not frappe.db.get_value('Custom Role', dict(report=report_name)): frappe.get_doc(dict( @@ -99,7 +99,7 @@ def add_custom_roles_for_reports(): )).insert() def add_permissions(): - for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'): + for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'): add_permission(doctype, 'All', 0) for role in ('Accounts Manager', 'Accounts User', 'System Manager'): add_permission(doctype, role, 0) @@ -115,9 +115,11 @@ def add_permissions(): def add_print_formats(): frappe.reload_doc("regional", "print_format", "gst_tax_invoice") frappe.reload_doc("accounts", "print_format", "gst_pos_invoice") + frappe.reload_doc("accounts", "print_format", "GST E-Invoice") frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0) frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0) + frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0) def make_property_setters(patch=False): # GST rules do not allow for an invoice no. bigger than 16 characters @@ -453,7 +455,7 @@ def get_custom_fields(): 'fieldname': 'ewaybill', 'label': 'E-Way Bill No.', 'fieldtype': 'Data', - 'depends_on': 'eval:(doc.docstatus === 1)', + 'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)', 'allow_on_submit': 1, 'insert_after': 'tax_id', 'translatable': 0, @@ -481,6 +483,46 @@ def get_custom_fields(): fetch_from='customer_address.gstin', print_hide=1, read_only=1) ] + si_einvoice_fields = [ + dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1, + depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'), + + dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'), + + dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1, + depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'), + + dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1, + depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'), + + dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type', + print_hide=1, hidden=1), + + dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section', + no_copy=1, print_hide=1), + + dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1), + + dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', + no_copy=1, print_hide=1), + + dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code', + no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image', + options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1), + + dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON', + hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1) + ] + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', @@ -493,7 +535,7 @@ def get_custom_fields(): 'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields, 'Purchase Order': purchase_invoice_gst_fields, 'Purchase Receipt': purchase_invoice_gst_fields, - 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields, + 'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields, 'POS Invoice': sales_invoice_gst_fields, 'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category, 'Payment Entry': payment_entry_fields, diff --git a/erpnext/regional/report/e_invoice_summary/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js new file mode 100644 index 00000000000..4713217d83c --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js @@ -0,0 +1,55 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["E-Invoice Summary"] = { + "filters": [ + { + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "fieldname": "company", + "label": __("Company"), + "default": frappe.defaults.get_user_default("Company"), + }, + { + "fieldtype": "Link", + "options": "Customer", + "fieldname": "customer", + "label": __("Customer") + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "from_date", + "label": __("From Date"), + "default": frappe.datetime.add_months(frappe.datetime.get_today(), -1), + }, + { + "fieldtype": "Date", + "reqd": 1, + "fieldname": "to_date", + "label": __("To Date"), + "default": frappe.datetime.get_today(), + }, + { + "fieldtype": "Select", + "fieldname": "status", + "label": __("Status"), + "options": "\nPending\nGenerated\nCancelled\nFailed" + } + ], + + "formatter": function (value, row, column, data, default_formatter) { + value = default_formatter(value, row, column, data); + + if (column.fieldname == "einvoice_status" && value) { + if (value == 'Pending') value = `${value}`; + else if (value == 'Generated') value = `${value}`; + else if (value == 'Cancelled') value = `${value}`; + else if (value == 'Failed') value = `${value}`; + } + + return value; + } +}; diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json new file mode 100644 index 00000000000..d0000ad50df --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json @@ -0,0 +1,28 @@ +{ + "add_total_row": 0, + "columns": [], + "creation": "2021-03-12 11:23:37.312294", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "json": "{}", + "letter_head": "Logo", + "modified": "2021-03-13 12:36:48.689413", + "modified_by": "Administrator", + "module": "Regional", + "name": "E-Invoice Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Sales Invoice", + "report_name": "E-Invoice Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Administrator" + } + ] +} \ No newline at end of file diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py new file mode 100644 index 00000000000..5cbb4553f40 --- /dev/null +++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py @@ -0,0 +1,112 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +import frappe +from frappe import _ + + +def execute(filters=None): + validate_filters(filters) + + columns = get_columns() + data = get_data(filters) + + return columns, data + +def validate_filters(filters=None): + if filters is None: + filters = {} + filters = frappe._dict(filters) + + if not filters.company: + frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter')) + if filters.company: + # validate if company has e-invoicing enabled + pass + if not filters.from_date or not filters.to_date: + frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter')) + if filters.from_date > filters.to_date: + frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter')) + +def get_data(filters=None): + if filters is None: + filters = {} + query_filters = { + 'posting_date': ['between', [filters.from_date, filters.to_date]], + 'einvoice_status': ['is', 'set'], + 'company': filters.company + } + if filters.customer: + query_filters['customer'] = filters.customer + if filters.status: + query_filters['einvoice_status'] = filters.status + + data = frappe.get_all( + 'Sales Invoice', + filters=query_filters, + fields=[d.get('fieldname') for d in get_columns()] + ) + + return data + +def get_columns(): + return [ + { + "fieldtype": "Date", + "fieldname": "posting_date", + "label": _("Posting Date"), + "width": 0 + }, + { + "fieldtype": "Link", + "fieldname": "name", + "label": _("Sales Invoice"), + "options": "Sales Invoice", + "width": 140 + }, + { + "fieldtype": "Data", + "fieldname": "einvoice_status", + "label": _("Status"), + "width": 100 + }, + { + "fieldtype": "Link", + "fieldname": "customer", + "options": "Customer", + "label": _("Customer") + }, + { + "fieldtype": "Check", + "fieldname": "is_return", + "label": _("Is Return"), + "width": 85 + }, + { + "fieldtype": "Data", + "fieldname": "ack_no", + "label": "Ack. No.", + "width": 145 + }, + { + "fieldtype": "Data", + "fieldname": "ack_date", + "label": "Ack. Date", + "width": 165 + }, + { + "fieldtype": "Data", + "fieldname": "irn", + "label": _("IRN No."), + "width": 250 + }, + { + "fieldtype": "Currency", + "options": "Company:company:default_currency", + "fieldname": "base_grand_total", + "label": _("Grand Total"), + "width": 120 + } + ]