feat: GST E Invoicing (#23455)
* feat: init e-invoice settings * feat: read public key file * feat: rsa encryption with public key * feat: save token and sek from auth request * chore: handle error response * feat: AES decryption of SEK with appkey * feat: decrypt json data with SEK * feat: make e invoice from erpnext sales invoice * feat: generate IRN * feat: decode signed json and QR code * chore: validations * feat: cancel IRN * feat: complete e-invoice schema * chore: move e-invoice settings to regional * chore: split einvoice settings and operations * chore: rename schema to template & js cleanup * feat: make IRN field on regional setup * feat: Generate & Cancel IRN from Sales Invoice * chore: minor fixes * fix: item discount * chore: show irn cancelled check after cancellation * fix: hide cancel irn dialog on error * fix: public key is required on validate * fix: cannot find attached key file * fix: validation if e invoicing is disabled * fix: do not show generate irn for invalid supply type * fix: update irn_cancelled after cancelling irn * chore: show irn field for proper gst_category * feat: e-way bill details in e-invoice * fix: save e-way bill no on irn generation * chore: no copy on e invoice custom fields * feat: cancel e-way bill before cancelling IRN * feat: manual download / upload json * chore: group e-invoicing actions * fix: fn name * chore: save signed invoice and qrcode after uplaoding irn * fix: fetch token if not valid * chore: move einvoicing stuff to seperate folder * feat: QRCode Image and E-Invoice Print Format * fix: bug * fix: invalid syntax * chore: code cleanup * chore: clean up e invoice actions * fix: download & upload e-invoice * fix: print format * fix: validations * fix: add permissions on regional setup * feat: add patch * fix: validate document name * fix: return date * fix: credit note einvoice * fix: validations * fix: error logging * fix: e_invoice module not found * fix: add missing package * fix: rename e_invoice_utils.py * fix: einvoice field validation * fix: patch * fix: invoice totals calculation * fix: other charges calculation * chore: improve document name validation message * fix: qr code image string * feat: initialize GSP connector * chore: remove unwanted fields * fix: qr code generation * feat: fetch and cache GSTIN details * feat: generate & cancel IRN * feat: cancel eway bill * chore: remove unwanted fuctions * chore: clean up einvoice actions * fix: attach qrcode on irn generation * fix: generate & cancel IRN * fix: show/hide eway bill fields * fix: valiations * feat: generate eway bill from IRN * chore: remove unwanted imports * chore: error logging * feat: header & footer in GST E Invoice * chore: remove test pincode * fix: invalid syntax * feat: cess non advolem on einvoice item * chore: remove fetch token from e invocie settings * fix: imports * fix: error handling * feat: update timeline on einvoice actions * fix: qrcode image size * fix: exclude intra company transactions * fix: eway bill test * fix: ewaybill mandatory conditions * chore: add tests * fix: returning condition * feat: log e-invocing requests * chore: add ack date and ack no field for print formats * fix: sider issues * feat: show e-invoice preview before IRN generation * fix: use as_list for error message * fix: minor ux issues * fix: dialog is undefined * fix: error handling * feat: add docs link to e invoice settings * feat: multiple gstins for e invoicing * fix: uncomment test condition * fix: remove test pincode * fix: cannot cancel irn without submitting sales invoice * chore: code cleanup * fix: sider issues * fix: e invoice request log permissions Co-authored-by: Nabin Hait <nabinhait@gmail.com>
This commit is contained in:
parent
4e9361f74b
commit
6d74f5b59b
@ -1,6 +1,8 @@
|
|||||||
{% include "erpnext/regional/india/taxes.js" %}
|
{% include "erpnext/regional/india/taxes.js" %}
|
||||||
|
{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
|
||||||
|
|
||||||
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
erpnext.setup_auto_gst_taxation('Sales Invoice');
|
||||||
|
erpnext.setup_einvoice_actions('Sales Invoice')
|
||||||
|
|
||||||
frappe.ui.form.on("Sales Invoice", {
|
frappe.ui.form.on("Sales Invoice", {
|
||||||
setup: function(frm) {
|
setup: function(frm) {
|
||||||
|
@ -232,9 +232,9 @@ class SalesInvoice(SellingController):
|
|||||||
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
frappe.throw(_("At least one mode of payment is required for POS invoice."))
|
||||||
|
|
||||||
def before_cancel(self):
|
def before_cancel(self):
|
||||||
|
super(SalesInvoice, self).before_cancel()
|
||||||
self.update_time_sheet(None)
|
self.update_time_sheet(None)
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
super(SalesInvoice, self).on_cancel()
|
super(SalesInvoice, self).on_cancel()
|
||||||
|
|
||||||
|
@ -1825,93 +1825,7 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
# check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
|
||||||
|
|
||||||
def test_eway_bill_json(self):
|
def test_eway_bill_json(self):
|
||||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
si = make_sales_invoice_for_ewaybill()
|
||||||
address = frappe.get_doc({
|
|
||||||
"address_line1": "_Test Address Line 1",
|
|
||||||
"address_title": "_Test Address for Eway bill",
|
|
||||||
"address_type": "Billing",
|
|
||||||
"city": "_Test City",
|
|
||||||
"state": "Test State",
|
|
||||||
"country": "India",
|
|
||||||
"doctype": "Address",
|
|
||||||
"is_primary_address": 1,
|
|
||||||
"phone": "+91 0000000000",
|
|
||||||
"gstin": "27AAECE4835E1ZR",
|
|
||||||
"gst_state": "Maharashtra",
|
|
||||||
"gst_state_number": "27",
|
|
||||||
"pincode": "401108"
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
address.append("links", {
|
|
||||||
"link_doctype": "Company",
|
|
||||||
"link_name": "_Test Company"
|
|
||||||
})
|
|
||||||
|
|
||||||
address.save()
|
|
||||||
|
|
||||||
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
|
||||||
address = frappe.get_doc({
|
|
||||||
"address_line1": "_Test Address Line 1",
|
|
||||||
"address_title": "_Test Customer-Address for Eway bill",
|
|
||||||
"address_type": "Shipping",
|
|
||||||
"city": "_Test City",
|
|
||||||
"state": "Test State",
|
|
||||||
"country": "India",
|
|
||||||
"doctype": "Address",
|
|
||||||
"is_primary_address": 1,
|
|
||||||
"phone": "+91 0000000000",
|
|
||||||
"gst_state": "Maharashtra",
|
|
||||||
"gst_state_number": "27",
|
|
||||||
"pincode": "410038"
|
|
||||||
}).insert()
|
|
||||||
|
|
||||||
address.append("links", {
|
|
||||||
"link_doctype": "Customer",
|
|
||||||
"link_name": "_Test Customer"
|
|
||||||
})
|
|
||||||
|
|
||||||
address.save()
|
|
||||||
|
|
||||||
gst_settings = frappe.get_doc("GST Settings")
|
|
||||||
|
|
||||||
gst_account = frappe.get_all(
|
|
||||||
"GST Account",
|
|
||||||
fields=["cgst_account", "sgst_account", "igst_account"],
|
|
||||||
filters = {"company": "_Test Company"})
|
|
||||||
|
|
||||||
if not gst_account:
|
|
||||||
gst_settings.append("gst_accounts", {
|
|
||||||
"company": "_Test Company",
|
|
||||||
"cgst_account": "CGST - _TC",
|
|
||||||
"sgst_account": "SGST - _TC",
|
|
||||||
"igst_account": "IGST - _TC",
|
|
||||||
})
|
|
||||||
|
|
||||||
gst_settings.save()
|
|
||||||
|
|
||||||
si = create_sales_invoice(do_not_save =1, rate = '60000')
|
|
||||||
|
|
||||||
si.distance = 2000
|
|
||||||
si.company_address = "_Test Address for Eway bill-Billing"
|
|
||||||
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
|
||||||
si.vehicle_no = "KA12KA1234"
|
|
||||||
si.gst_category = "Registered Regular"
|
|
||||||
|
|
||||||
si.append("taxes", {
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": "CGST - _TC",
|
|
||||||
"cost_center": "Main - _TC",
|
|
||||||
"description": "CGST @ 9.0",
|
|
||||||
"rate": 9
|
|
||||||
})
|
|
||||||
|
|
||||||
si.append("taxes", {
|
|
||||||
"charge_type": "On Net Total",
|
|
||||||
"account_head": "SGST - _TC",
|
|
||||||
"cost_center": "Main - _TC",
|
|
||||||
"description": "SGST @ 9.0",
|
|
||||||
"rate": 9
|
|
||||||
})
|
|
||||||
|
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@ -1927,6 +1841,187 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
|
||||||
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
|
||||||
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
|
||||||
|
|
||||||
|
def test_einvoice_submission_without_irn(self):
|
||||||
|
# init
|
||||||
|
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||||
|
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
|
||||||
|
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||||
|
frappe.flags.country = country
|
||||||
|
|
||||||
|
def test_einvoice_json(self):
|
||||||
|
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||||
|
|
||||||
|
customer_gstin = '27AACCM7806M1Z3'
|
||||||
|
customer_gstin_dtls = {
|
||||||
|
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
|
||||||
|
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
|
||||||
|
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
||||||
|
}
|
||||||
|
company_gstin = '27AAECE4835E1ZR'
|
||||||
|
company_gstin_dtls = {
|
||||||
|
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
|
||||||
|
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
|
||||||
|
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
||||||
|
}
|
||||||
|
# set cache gstin details to avoid fetching details which will require connection to GSP servers
|
||||||
|
frappe.local.gstin_cache = {}
|
||||||
|
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
|
||||||
|
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
|
||||||
|
|
||||||
|
si = make_sales_invoice_for_ewaybill()
|
||||||
|
si.naming_series = 'INV-2020-.#####'
|
||||||
|
si.items = []
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 2,
|
||||||
|
"rate": 100,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
si.append("items", {
|
||||||
|
"item_code": "_Test Item 2",
|
||||||
|
"uom": "Nos",
|
||||||
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
|
"qty": 4,
|
||||||
|
"rate": 150,
|
||||||
|
"income_account": "Sales - _TC",
|
||||||
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
})
|
||||||
|
si.save()
|
||||||
|
|
||||||
|
einvoice = make_einvoice(si)
|
||||||
|
|
||||||
|
total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']])
|
||||||
|
total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']])
|
||||||
|
|
||||||
|
self.assertEqual(einvoice['Version'], '1.1')
|
||||||
|
self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value)
|
||||||
|
self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
|
||||||
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
|
||||||
|
def make_sales_invoice_for_ewaybill():
|
||||||
|
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"address_line1": "_Test Address Line 1",
|
||||||
|
"address_title": "_Test Address for Eway bill",
|
||||||
|
"address_type": "Billing",
|
||||||
|
"city": "_Test City",
|
||||||
|
"state": "Test State",
|
||||||
|
"country": "India",
|
||||||
|
"doctype": "Address",
|
||||||
|
"is_primary_address": 1,
|
||||||
|
"phone": "+910000000000",
|
||||||
|
"gstin": "27AAECE4835E1ZR",
|
||||||
|
"gst_state": "Maharashtra",
|
||||||
|
"gst_state_number": "27",
|
||||||
|
"pincode": "401108"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
address.append("links", {
|
||||||
|
"link_doctype": "Company",
|
||||||
|
"link_name": "_Test Company"
|
||||||
|
})
|
||||||
|
|
||||||
|
address.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
|
||||||
|
address = frappe.get_doc({
|
||||||
|
"address_line1": "_Test Address Line 1",
|
||||||
|
"address_title": "_Test Customer-Address for Eway bill",
|
||||||
|
"address_type": "Shipping",
|
||||||
|
"city": "_Test City",
|
||||||
|
"state": "Test State",
|
||||||
|
"country": "India",
|
||||||
|
"doctype": "Address",
|
||||||
|
"is_primary_address": 1,
|
||||||
|
"phone": "+910000000000",
|
||||||
|
"gstin": "27AACCM7806M1Z3",
|
||||||
|
"gst_state": "Maharashtra",
|
||||||
|
"gst_state_number": "27",
|
||||||
|
"pincode": "410038"
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
address.append("links", {
|
||||||
|
"link_doctype": "Customer",
|
||||||
|
"link_name": "_Test Customer"
|
||||||
|
})
|
||||||
|
|
||||||
|
address.save()
|
||||||
|
|
||||||
|
if not frappe.db.exists('Supplier', '_Test Transporter'):
|
||||||
|
frappe.get_doc({
|
||||||
|
"doctype": "Supplier",
|
||||||
|
"supplier_name": "_Test Transporter",
|
||||||
|
"country": "India",
|
||||||
|
"supplier_group": "_Test Supplier Group",
|
||||||
|
"supplier_type": "Company",
|
||||||
|
"is_transporter": 1
|
||||||
|
}).insert()
|
||||||
|
|
||||||
|
gst_settings = frappe.get_doc("GST Settings")
|
||||||
|
|
||||||
|
gst_account = frappe.get_all(
|
||||||
|
"GST Account",
|
||||||
|
fields=["cgst_account", "sgst_account", "igst_account"],
|
||||||
|
filters = {"company": "_Test Company"})
|
||||||
|
|
||||||
|
if not gst_account:
|
||||||
|
gst_settings.append("gst_accounts", {
|
||||||
|
"company": "_Test Company",
|
||||||
|
"cgst_account": "CGST - _TC",
|
||||||
|
"sgst_account": "SGST - _TC",
|
||||||
|
"igst_account": "IGST - _TC",
|
||||||
|
})
|
||||||
|
|
||||||
|
gst_settings.save()
|
||||||
|
|
||||||
|
si = create_sales_invoice(do_not_save =1, rate = '60000')
|
||||||
|
|
||||||
|
si.distance = 2000
|
||||||
|
si.company_address = "_Test Address for Eway bill-Billing"
|
||||||
|
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
|
||||||
|
si.vehicle_no = "KA12KA1234"
|
||||||
|
si.gst_category = "Registered Regular"
|
||||||
|
si.mode_of_transport = 'Road'
|
||||||
|
si.transporter = '_Test Transporter'
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "CGST - _TC",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"description": "CGST @ 9.0",
|
||||||
|
"rate": 9
|
||||||
|
})
|
||||||
|
|
||||||
|
si.append("taxes", {
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"account_head": "SGST - _TC",
|
||||||
|
"cost_center": "Main - _TC",
|
||||||
|
"description": "SGST @ 9.0",
|
||||||
|
"rate": 9
|
||||||
|
})
|
||||||
|
|
||||||
|
return si
|
||||||
|
|
||||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||||
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
|
||||||
|
162
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
162
erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
Normal file
@ -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) -%}
|
||||||
|
|
||||||
|
<div class="page-break">
|
||||||
|
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||||
|
{% if letter_head and not no_letterhead %}
|
||||||
|
<div class="letter-head">{{ letter_head }}</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="print-heading">
|
||||||
|
<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if print_settings.repeat_header_footer %}
|
||||||
|
<div id="footer-html" class="visible-pdf">
|
||||||
|
{% if not no_letterhead and footer %}
|
||||||
|
<div class="letter-head-footer">
|
||||||
|
{{ footer }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-center small page-number visible-pdf">
|
||||||
|
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||||
|
<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
|
||||||
|
<div class="col-xs-8 column-break">
|
||||||
|
<div class="row data-field">
|
||||||
|
<div class="col-xs-4"><label>IRN</label></div>
|
||||||
|
<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row data-field">
|
||||||
|
<div class="col-xs-4"><label>Ack. No</label></div>
|
||||||
|
<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row data-field">
|
||||||
|
<div class="col-xs-4"><label>Ack. Date</label></div>
|
||||||
|
<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row data-field">
|
||||||
|
<div class="col-xs-4"><label>Category</label></div>
|
||||||
|
<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row data-field">
|
||||||
|
<div class="col-xs-4"><label>Document Type</label></div>
|
||||||
|
<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row data-field">
|
||||||
|
<div class="col-xs-4"><label>Document No</label></div>
|
||||||
|
<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-4 column-break">
|
||||||
|
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
|
||||||
|
<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
|
||||||
|
{%- set seller = einvoice.SellerDtls -%}
|
||||||
|
<div class="col-xs-6 column-break">
|
||||||
|
<h5 style="margin-bottom: 5px;">Seller</h5>
|
||||||
|
<p>{{ seller.Gstin }}</p>
|
||||||
|
<p>{{ seller.LglNm }}</p>
|
||||||
|
<p>{{ seller.Addr1 }}</p>
|
||||||
|
{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
|
||||||
|
<p>{{ seller.Loc }}</p>
|
||||||
|
<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
|
||||||
|
|
||||||
|
{%- if einvoice.ShipDtls -%}
|
||||||
|
{%- set shipping = einvoice.ShipDtls -%}
|
||||||
|
<h5 style="margin-bottom: 5px;">Shipping</h5>
|
||||||
|
<p>{{ shipping.Gstin }}</p>
|
||||||
|
<p>{{ shipping.LglNm }}</p>
|
||||||
|
<p>{{ shipping.Addr1 }}</p>
|
||||||
|
{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
|
||||||
|
<p>{{ shipping.Loc }}</p>
|
||||||
|
<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{%- set buyer = einvoice.BuyerDtls -%}
|
||||||
|
<div class="col-xs-6 column-break">
|
||||||
|
<h5 style="margin-bottom: 5px;">Buyer</h5>
|
||||||
|
<p>{{ buyer.Gstin }}</p>
|
||||||
|
<p>{{ buyer.LglNm }}</p>
|
||||||
|
<p>{{ buyer.Addr1 }}</p>
|
||||||
|
{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
|
||||||
|
<p>{{ buyer.Loc }}</p>
|
||||||
|
<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="overflow-x: auto;">
|
||||||
|
<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left" style="width: 3%;">Sr. No.</th>
|
||||||
|
<th class="text-left">Item</th>
|
||||||
|
<th class="text-left" style="width: 10%;">HSN Code</th>
|
||||||
|
<th class="text-left" style="width: 5%;">Qty</th>
|
||||||
|
<th class="text-left" style="width: 5%;">UOM</th>
|
||||||
|
<th class="text-left">Rate</th>
|
||||||
|
<th class="text-left" style="width: 5%;">Discount</th>
|
||||||
|
<th class="text-left">Taxable Amount</th>
|
||||||
|
<th class="text-left" style="width: 7%;">Tax Rate</th>
|
||||||
|
<th class="text-left" style="width: 5%;">Other Charges</th>
|
||||||
|
<th class="text-left">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in einvoice.ItemList %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
|
||||||
|
<td class="text-left">{{ item.PrdDesc }}</td>
|
||||||
|
<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
|
||||||
|
<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
|
||||||
|
<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
|
||||||
|
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
|
||||||
|
<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
|
||||||
|
<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div style="overflow-x: auto;">
|
||||||
|
<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="text-left">Taxable Amount</th>
|
||||||
|
<th class="text-left">CGST</th>
|
||||||
|
<th class="text-left"">SGST</th>
|
||||||
|
<th class="text-left">IGST</th>
|
||||||
|
<th class="text-left">CESS</th>
|
||||||
|
<th class="text-left" style="width: 10%;">State CESS</th>
|
||||||
|
<th class="text-left">Discount</th>
|
||||||
|
<th class="text-left" style="width: 10%;">Other Charges</th>
|
||||||
|
<th class="text-left" style="width: 10%;">Round Off</th>
|
||||||
|
<th class="text-left">Total Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- set value_details = einvoice.ValDtls -%}
|
||||||
|
<tr>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
|
||||||
|
<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -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"
|
||||||
|
}
|
@ -110,8 +110,14 @@ class AccountsController(TransactionBase):
|
|||||||
self.set_inter_company_account()
|
self.set_inter_company_account()
|
||||||
|
|
||||||
validate_regional(self)
|
validate_regional(self)
|
||||||
|
|
||||||
|
validate_einvoice_fields(self)
|
||||||
|
|
||||||
if self.doctype != 'Material Request':
|
if self.doctype != 'Material Request':
|
||||||
apply_pricing_rule_on_transaction(self)
|
apply_pricing_rule_on_transaction(self)
|
||||||
|
|
||||||
|
def before_cancel(self):
|
||||||
|
validate_einvoice_fields(self)
|
||||||
|
|
||||||
def validate_deferred_start_and_end_date(self):
|
def validate_deferred_start_and_end_date(self):
|
||||||
for d in self.items:
|
for d in self.items:
|
||||||
@ -1518,3 +1524,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
|||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def validate_regional(doc):
|
def validate_regional(doc):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@erpnext.allow_regional
|
||||||
|
def validate_einvoice_fields(doc):
|
||||||
|
pass
|
||||||
|
@ -397,7 +397,8 @@ regional_overrides = {
|
|||||||
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
||||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
'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.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
|
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
||||||
|
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
|
||||||
},
|
},
|
||||||
'United Arab Emirates': {
|
'United Arab Emirates': {
|
||||||
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
|
||||||
|
@ -732,6 +732,7 @@ erpnext.patches.v13_0.set_youtube_video_id
|
|||||||
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
erpnext.patches.v13_0.print_uom_after_quantity_patch
|
||||||
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
|
||||||
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
|
||||||
|
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
|
||||||
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
erpnext.patches.v13_0.updates_for_multi_currency_payroll
|
||||||
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
erpnext.patches.v13_0.update_reason_for_resignation_in_employee
|
||||||
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
erpnext.patches.v13_0.update_custom_fields_for_shopify
|
||||||
|
55
erpnext/patches/v12_0/setup_einvoice_fields.py
Normal file
55
erpnext/patches/v12_0/setup_einvoice_fields.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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("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)
|
@ -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) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
});
|
@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"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": "Link",
|
||||||
|
"label": "Reference Invoice",
|
||||||
|
"options": "Sales 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": "2020-12-24 21:09:38.882866",
|
||||||
|
"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"
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- 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
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- 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
|
@ -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/user/manual/en/regional/india/setup-e-invoicing';
|
||||||
|
frm.dashboard.set_headline(
|
||||||
|
__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-09-24 16:23:16.235722",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"enable",
|
||||||
|
"section_break_2",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"issingle": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-22 15:34:57.280044",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "E Invoice Settings",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
@ -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.'))
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
# -*- 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
|
0
erpnext/regional/doctype/e_invoice_user/__init__.py
Normal file
0
erpnext/regional/doctype/e_invoice_user/__init__.py
Normal file
48
erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
Normal file
48
erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-12-22 15:02:46.229474",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"index_web_pages_for_search": 1,
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-12-22 15:10:53.466205",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Regional",
|
||||||
|
"name": "E Invoice User",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
10
erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
Normal file
10
erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- 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
|
0
erpnext/regional/india/e_invoice/__init__.py
Normal file
0
erpnext/regional/india/e_invoice/__init__.py
Normal file
31
erpnext/regional/india/e_invoice/einv_item_template.json
Normal file
31
erpnext/regional/india/e_invoice/einv_item_template.json
Normal file
@ -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}"
|
||||||
|
}}
|
||||||
|
}}
|
110
erpnext/regional/india/e_invoice/einv_template.json
Normal file
110
erpnext/regional/india/e_invoice/einv_template.json
Normal file
@ -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_net_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}"
|
||||||
|
}}
|
||||||
|
}}
|
956
erpnext/regional/india/e_invoice/einv_validation.json
Normal file
956
erpnext/regional/india/e_invoice/einv_validation.json
Normal file
@ -0,0 +1,956 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
305
erpnext/regional/india/e_invoice/einvoice.js
Normal file
305
erpnext/regional/india/e_invoice/einvoice.js
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
erpnext.setup_einvoice_actions = (doctype) => {
|
||||||
|
frappe.ui.form.on(doctype, {
|
||||||
|
refresh(frm) {
|
||||||
|
const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable");
|
||||||
|
const supply_type = frm.doc.gst_category;
|
||||||
|
const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type);
|
||||||
|
const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin;
|
||||||
|
|
||||||
|
if (!einvoicing_enabled || !valid_supply_type || company_transaction) 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 = () => {
|
||||||
|
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'),
|
||||||
|
wide: 1,
|
||||||
|
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 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 E-Way Bill'),
|
||||||
|
fields: fields,
|
||||||
|
primary_action: function() {
|
||||||
|
const data = d.get_values();
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
||||||
|
args: {
|
||||||
|
doctype,
|
||||||
|
docname: name,
|
||||||
|
eway_bill: ewaybill,
|
||||||
|
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 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',
|
||||||
|
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
|
||||||
|
'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"),
|
||||||
|
wide: 1,
|
||||||
|
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(
|
||||||
|
`<div>
|
||||||
|
<div class="print-preview">
|
||||||
|
<div class="print-format"></div>
|
||||||
|
</div>
|
||||||
|
<div class="page-break-message text-muted text-center text-medium margin-top"></div>
|
||||||
|
</div>`
|
||||||
|
);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
772
erpnext/regional/india/e_invoice/utils.py
Normal file
772
erpnext/regional/india/e_invoice/utils.py
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
# -*- 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 os
|
||||||
|
import re
|
||||||
|
import jwt
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
import frappe
|
||||||
|
import traceback
|
||||||
|
from frappe import _, bold
|
||||||
|
from pyqrcode import create as qrcreate
|
||||||
|
from frappe.integrations.utils import make_post_request, make_get_request
|
||||||
|
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
|
||||||
|
from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
|
||||||
|
|
||||||
|
def validate_einvoice_fields(doc):
|
||||||
|
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
|
||||||
|
invalid_doctype = doc.doctype not in ['Sales Invoice']
|
||||||
|
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
|
||||||
|
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
|
||||||
|
|
||||||
|
if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
|
||||||
|
|
||||||
|
if doc.docstatus == 0 and doc._action == 'save':
|
||||||
|
if doc.irn:
|
||||||
|
frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
|
||||||
|
if len(doc.name) > 16:
|
||||||
|
raise_document_name_too_long_error()
|
||||||
|
|
||||||
|
elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
|
||||||
|
frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
|
||||||
|
|
||||||
|
elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
|
||||||
|
frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
|
||||||
|
|
||||||
|
def raise_document_name_too_long_error():
|
||||||
|
title = _('Document ID Too Long')
|
||||||
|
msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
|
||||||
|
msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
|
||||||
|
msg += '<br><br>'
|
||||||
|
msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
|
||||||
|
bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
|
||||||
|
)
|
||||||
|
msg += _('Please account for ammended documents too. ')
|
||||||
|
frappe.throw(msg, title=title)
|
||||||
|
|
||||||
|
def read_json(name):
|
||||||
|
file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
return cstr(f.read())
|
||||||
|
|
||||||
|
def get_transaction_details(invoice):
|
||||||
|
supply_type = ''
|
||||||
|
if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
|
||||||
|
elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
|
||||||
|
elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
|
||||||
|
elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
|
||||||
|
|
||||||
|
if not supply_type:
|
||||||
|
rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
|
||||||
|
frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
|
||||||
|
title=_('Invalid Supply Type'))
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
tax_scheme='GST',
|
||||||
|
supply_type=supply_type,
|
||||||
|
reverse_charge=invoice.reverse_charge
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_doc_details(invoice):
|
||||||
|
invoice_type = 'CRN' if invoice.is_return else 'INV'
|
||||||
|
|
||||||
|
invoice_name = invoice.name
|
||||||
|
invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
invoice_type=invoice_type,
|
||||||
|
invoice_name=invoice_name,
|
||||||
|
invoice_date=invoice_date
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_party_details(address_name):
|
||||||
|
address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
||||||
|
gstin = address.get('gstin')
|
||||||
|
|
||||||
|
gstin_details = get_gstin_details(gstin)
|
||||||
|
legal_name = gstin_details.get('LegalName')
|
||||||
|
location = gstin_details.get('AddrLoc') or address.get('city')
|
||||||
|
state_code = gstin_details.get('StateCode')
|
||||||
|
pincode = gstin_details.get('AddrPncd')
|
||||||
|
address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
|
||||||
|
address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
|
||||||
|
email_id = address.get('email_id')
|
||||||
|
phone = address.get('phone')
|
||||||
|
# get last 10 digit
|
||||||
|
phone = phone.replace(" ", "")[-10:] if phone else ''
|
||||||
|
|
||||||
|
if state_code == 97:
|
||||||
|
# according to einvoice standard
|
||||||
|
pincode = 999999
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
gstin=gstin, legal_name=legal_name, location=location,
|
||||||
|
pincode=pincode, state_code=state_code, address_line1=address_line1,
|
||||||
|
address_line2=address_line2, email=email_id, phone=phone
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_gstin_details(gstin):
|
||||||
|
if not hasattr(frappe.local, 'gstin_cache'):
|
||||||
|
frappe.local.gstin_cache = {}
|
||||||
|
|
||||||
|
key = gstin
|
||||||
|
details = frappe.local.gstin_cache.get(key)
|
||||||
|
if details:
|
||||||
|
return details
|
||||||
|
|
||||||
|
details = frappe.cache().hget('gstin_cache', key)
|
||||||
|
if details:
|
||||||
|
frappe.local.gstin_cache[key] = details
|
||||||
|
return details
|
||||||
|
|
||||||
|
if not details:
|
||||||
|
return GSPConnector.get_gstin_details(gstin)
|
||||||
|
|
||||||
|
def get_overseas_address_details(address_name):
|
||||||
|
address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
|
||||||
|
'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
|
||||||
|
)
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
gstin='URP', legal_name=address_title, address_line1=address_line1,
|
||||||
|
address_line2=address_line2, email=email_id, phone=phone,
|
||||||
|
pincode=999999, state_code=96, place_of_supply=96, location=city
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_item_list(invoice):
|
||||||
|
item_list = []
|
||||||
|
|
||||||
|
for d in invoice.items:
|
||||||
|
einvoice_item_schema = read_json('einv_item_template')
|
||||||
|
item = frappe._dict({})
|
||||||
|
item.update(d.as_dict())
|
||||||
|
|
||||||
|
item.sr_no = d.idx
|
||||||
|
item.qty = abs(item.qty)
|
||||||
|
item.description = d.item_name
|
||||||
|
item.taxable_value = abs(item.base_net_amount)
|
||||||
|
item.discount_amount = abs(item.discount_amount * item.qty)
|
||||||
|
item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate)
|
||||||
|
item.gross_amount = abs(item.unit_rate * item.qty)
|
||||||
|
|
||||||
|
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||||
|
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||||
|
item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
|
||||||
|
|
||||||
|
item = update_item_taxes(invoice, item)
|
||||||
|
|
||||||
|
item.total_value = abs(
|
||||||
|
item.taxable_value + item.igst_amount + item.sgst_amount +
|
||||||
|
item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
|
||||||
|
)
|
||||||
|
einv_item = einvoice_item_schema.format(item=item)
|
||||||
|
item_list.append(einv_item)
|
||||||
|
|
||||||
|
return ', '.join(item_list)
|
||||||
|
|
||||||
|
def update_item_taxes(invoice, item):
|
||||||
|
gst_accounts = get_gst_accounts(invoice.company)
|
||||||
|
gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
|
||||||
|
|
||||||
|
for attr in [
|
||||||
|
'tax_rate', 'cess_rate', 'cess_nadv_amount',
|
||||||
|
'cgst_amount', 'sgst_amount', 'igst_amount',
|
||||||
|
'cess_amount', 'cess_nadv_amount', 'other_charges'
|
||||||
|
]:
|
||||||
|
item[attr] = 0
|
||||||
|
|
||||||
|
for t in invoice.taxes:
|
||||||
|
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
|
||||||
|
if t.account_head in gst_accounts_list:
|
||||||
|
if t.account_head in gst_accounts.cess_account:
|
||||||
|
if t.charge_type == 'On Item Quantity':
|
||||||
|
item.cess_nadv_amount += abs(item_tax_detail[1])
|
||||||
|
else:
|
||||||
|
item.cess_rate += item_tax_detail[0]
|
||||||
|
item.cess_amount += abs(item_tax_detail[1])
|
||||||
|
elif t.account_head in gst_accounts.igst_account:
|
||||||
|
item.tax_rate += item_tax_detail[0]
|
||||||
|
item.igst_amount += abs(item_tax_detail[1])
|
||||||
|
elif t.account_head in gst_accounts.sgst_account:
|
||||||
|
item.tax_rate += item_tax_detail[0]
|
||||||
|
item.sgst_amount += abs(item_tax_detail[1])
|
||||||
|
elif t.account_head in gst_accounts.cgst_account:
|
||||||
|
item.tax_rate += item_tax_detail[0]
|
||||||
|
item.cgst_amount += abs(item_tax_detail[1])
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def get_invoice_value_details(invoice):
|
||||||
|
invoice_value_details = frappe._dict(dict())
|
||||||
|
invoice_value_details.base_net_total = abs(invoice.base_net_total)
|
||||||
|
invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0
|
||||||
|
# discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off
|
||||||
|
invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0)
|
||||||
|
disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total')
|
||||||
|
invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total)
|
||||||
|
invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total)
|
||||||
|
|
||||||
|
invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
|
||||||
|
|
||||||
|
return invoice_value_details
|
||||||
|
|
||||||
|
def update_invoice_taxes(invoice, invoice_value_details):
|
||||||
|
gst_accounts = get_gst_accounts(invoice.company)
|
||||||
|
gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
|
||||||
|
|
||||||
|
invoice_value_details.total_cgst_amt = 0
|
||||||
|
invoice_value_details.total_sgst_amt = 0
|
||||||
|
invoice_value_details.total_igst_amt = 0
|
||||||
|
invoice_value_details.total_cess_amt = 0
|
||||||
|
invoice_value_details.total_other_charges = 0
|
||||||
|
for t in invoice.taxes:
|
||||||
|
if t.account_head in gst_accounts_list:
|
||||||
|
if t.account_head in gst_accounts.cess_account:
|
||||||
|
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
elif t.account_head in gst_accounts.igst_account:
|
||||||
|
invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
elif t.account_head in gst_accounts.sgst_account:
|
||||||
|
invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
elif t.account_head in gst_accounts.cgst_account:
|
||||||
|
invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
else:
|
||||||
|
invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
|
||||||
|
|
||||||
|
return invoice_value_details
|
||||||
|
|
||||||
|
def get_payment_details(invoice):
|
||||||
|
payee_name = invoice.company
|
||||||
|
mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
|
||||||
|
paid_amount = invoice.base_paid_amount
|
||||||
|
outstanding_amount = invoice.outstanding_amount
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
payee_name=payee_name, mode_of_payment=mode_of_payment,
|
||||||
|
paid_amount=paid_amount, outstanding_amount=outstanding_amount
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_return_doc_reference(invoice):
|
||||||
|
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
|
||||||
|
return frappe._dict(dict(
|
||||||
|
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
|
||||||
|
))
|
||||||
|
|
||||||
|
def get_eway_bill_details(invoice):
|
||||||
|
if invoice.is_return:
|
||||||
|
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed'))
|
||||||
|
|
||||||
|
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
|
||||||
|
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
|
||||||
|
|
||||||
|
return frappe._dict(dict(
|
||||||
|
gstin=invoice.gst_transporter_id,
|
||||||
|
name=invoice.transporter_name,
|
||||||
|
mode_of_transport=mode_of_transport[invoice.mode_of_transport],
|
||||||
|
distance=invoice.distance or 0,
|
||||||
|
document_name=invoice.lr_no,
|
||||||
|
document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
|
||||||
|
vehicle_no=invoice.vehicle_no,
|
||||||
|
vehicle_type=vehicle_type[invoice.gst_vehicle_type]
|
||||||
|
))
|
||||||
|
|
||||||
|
def make_einvoice(invoice):
|
||||||
|
schema = read_json('einv_template')
|
||||||
|
|
||||||
|
transaction_details = get_transaction_details(invoice)
|
||||||
|
item_list = get_item_list(invoice)
|
||||||
|
doc_details = get_doc_details(invoice)
|
||||||
|
invoice_value_details = get_invoice_value_details(invoice)
|
||||||
|
seller_details = get_party_details(invoice.company_address)
|
||||||
|
|
||||||
|
if invoice.gst_category == 'Overseas':
|
||||||
|
buyer_details = get_overseas_address_details(invoice.customer_address)
|
||||||
|
else:
|
||||||
|
buyer_details = get_party_details(invoice.customer_address)
|
||||||
|
place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin
|
||||||
|
place_of_supply = place_of_supply[:2]
|
||||||
|
buyer_details.update(dict(place_of_supply=place_of_supply))
|
||||||
|
|
||||||
|
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
|
||||||
|
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
|
||||||
|
shipping_details = get_party_details(invoice.shipping_address_name)
|
||||||
|
|
||||||
|
if invoice.is_pos and invoice.base_paid_amount:
|
||||||
|
payment_details = get_payment_details(invoice)
|
||||||
|
|
||||||
|
if invoice.is_return and invoice.return_against:
|
||||||
|
prev_doc_details = get_return_doc_reference(invoice)
|
||||||
|
|
||||||
|
if invoice.transporter:
|
||||||
|
eway_bill_details = get_eway_bill_details(invoice)
|
||||||
|
|
||||||
|
# not yet implemented
|
||||||
|
dispatch_details = period_details = export_details = frappe._dict({})
|
||||||
|
|
||||||
|
einvoice = schema.format(
|
||||||
|
transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details,
|
||||||
|
seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
|
||||||
|
item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details,
|
||||||
|
period_details=period_details, prev_doc_details=prev_doc_details,
|
||||||
|
export_details=export_details, eway_bill_details=eway_bill_details
|
||||||
|
)
|
||||||
|
einvoice = json.loads(einvoice)
|
||||||
|
|
||||||
|
validations = json.loads(read_json('einv_validation'))
|
||||||
|
errors = validate_einvoice(validations, einvoice)
|
||||||
|
if errors:
|
||||||
|
message = "\n".join([
|
||||||
|
"E Invoice: ", json.dumps(einvoice, indent=4),
|
||||||
|
"-" * 50,
|
||||||
|
"Errors: ", json.dumps(errors, indent=4)
|
||||||
|
])
|
||||||
|
frappe.log_error(title="E Invoice Validation Failed", message=message)
|
||||||
|
frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1)
|
||||||
|
|
||||||
|
return einvoice
|
||||||
|
|
||||||
|
def validate_einvoice(validations, einvoice, errors=[]):
|
||||||
|
for fieldname, field_validation in validations.items():
|
||||||
|
value = einvoice.get(fieldname, None)
|
||||||
|
if not value or value == "None":
|
||||||
|
# remove keys with empty values
|
||||||
|
einvoice.pop(fieldname, None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
value_type = field_validation.get("type").lower()
|
||||||
|
if value_type in ['object', 'array']:
|
||||||
|
child_validations = field_validation.get('properties')
|
||||||
|
|
||||||
|
if isinstance(value, list):
|
||||||
|
for d in value:
|
||||||
|
validate_einvoice(child_validations, d, errors)
|
||||||
|
if not d:
|
||||||
|
# remove empty dicts
|
||||||
|
einvoice.pop(fieldname, None)
|
||||||
|
else:
|
||||||
|
validate_einvoice(child_validations, value, errors)
|
||||||
|
if not value:
|
||||||
|
# remove empty dicts
|
||||||
|
einvoice.pop(fieldname, None)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# convert to int or str
|
||||||
|
if value_type == 'string':
|
||||||
|
einvoice[fieldname] = str(value)
|
||||||
|
elif value_type == 'number':
|
||||||
|
is_integer = '.' not in str(field_validation.get('maximum'))
|
||||||
|
einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value)
|
||||||
|
value = einvoice[fieldname]
|
||||||
|
|
||||||
|
max_length = field_validation.get('maxLength')
|
||||||
|
minimum = flt(field_validation.get('minimum'))
|
||||||
|
maximum = flt(field_validation.get('maximum'))
|
||||||
|
pattern_str = field_validation.get('pattern')
|
||||||
|
pattern = re.compile(pattern_str or '')
|
||||||
|
|
||||||
|
label = field_validation.get('description') or fieldname
|
||||||
|
|
||||||
|
if value_type == 'string' and len(value) > max_length:
|
||||||
|
errors.append(_('{} should not exceed {} characters').format(label, max_length))
|
||||||
|
if value_type == 'number' and (value > maximum or value < minimum):
|
||||||
|
errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
|
||||||
|
if pattern_str and not pattern.match(value):
|
||||||
|
errors.append(field_validation.get('validationMsg'))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
class RequestFailed(Exception): pass
|
||||||
|
|
||||||
|
class GSPConnector():
|
||||||
|
def __init__(self, doctype=None, docname=None):
|
||||||
|
self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
|
||||||
|
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
|
||||||
|
self.credentials = self.get_credentials()
|
||||||
|
|
||||||
|
self.base_url = 'https://gsp.adaequare.com/'
|
||||||
|
self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token'
|
||||||
|
self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin'
|
||||||
|
self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice'
|
||||||
|
self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn'
|
||||||
|
self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel'
|
||||||
|
self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi'
|
||||||
|
self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill'
|
||||||
|
|
||||||
|
def get_credentials(self):
|
||||||
|
if self.invoice:
|
||||||
|
gstin = self.get_seller_gstin()
|
||||||
|
credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
|
||||||
|
else:
|
||||||
|
credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
|
||||||
|
return credentials
|
||||||
|
|
||||||
|
def get_seller_gstin(self):
|
||||||
|
gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
|
||||||
|
if not gstin:
|
||||||
|
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
|
||||||
|
return gstin
|
||||||
|
|
||||||
|
def get_auth_token(self):
|
||||||
|
if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
|
||||||
|
self.fetch_auth_token()
|
||||||
|
|
||||||
|
return self.e_invoice_settings.auth_token
|
||||||
|
|
||||||
|
def make_request(self, request_type, url, headers=None, data=None):
|
||||||
|
if request_type == 'post':
|
||||||
|
res = make_post_request(url, headers=headers, data=data)
|
||||||
|
else:
|
||||||
|
res = make_get_request(url, headers=headers, data=data)
|
||||||
|
|
||||||
|
self.log_request(url, headers, data, res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def log_request(self, url, headers, data, res):
|
||||||
|
headers.update({ 'password': self.credentials.password })
|
||||||
|
request_log = frappe.get_doc({
|
||||||
|
"doctype": "E Invoice Request Log",
|
||||||
|
"user": frappe.session.user,
|
||||||
|
"reference_invoice": self.invoice.name if self.invoice else None,
|
||||||
|
"url": url,
|
||||||
|
"headers": json.dumps(headers, indent=4) if headers else None,
|
||||||
|
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
|
||||||
|
"response": json.dumps(res, indent=4) if res else None
|
||||||
|
})
|
||||||
|
request_log.insert(ignore_permissions=True)
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
def fetch_auth_token(self):
|
||||||
|
headers = {
|
||||||
|
'gspappid': frappe.conf.einvoice_client_id,
|
||||||
|
'gspappsecret': frappe.conf.einvoice_client_secret
|
||||||
|
}
|
||||||
|
res = {}
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.authenticate_url, headers)
|
||||||
|
self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
||||||
|
self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
||||||
|
self.e_invoice_settings.save()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(res)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def get_headers(self):
|
||||||
|
return {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
'user_name': self.credentials.username,
|
||||||
|
'password': self.credentials.get_password(),
|
||||||
|
'gstin': self.credentials.gstin,
|
||||||
|
'authorization': self.get_auth_token(),
|
||||||
|
'requestid': str(base64.b64encode(os.urandom(18))),
|
||||||
|
}
|
||||||
|
|
||||||
|
def fetch_gstin_details(self, gstin):
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
params = '?gstin={gstin}'.format(gstin=gstin)
|
||||||
|
res = self.make_request('get', self.gstin_details_url + params, headers)
|
||||||
|
if res.get('success'):
|
||||||
|
return res.get('result')
|
||||||
|
else:
|
||||||
|
self.log_error(res)
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
self.raise_error()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error()
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_gstin_details(gstin):
|
||||||
|
'''fetch and cache GSTIN details'''
|
||||||
|
if not hasattr(frappe.local, 'gstin_cache'):
|
||||||
|
frappe.local.gstin_cache = {}
|
||||||
|
|
||||||
|
key = gstin
|
||||||
|
gsp_connector = GSPConnector()
|
||||||
|
details = gsp_connector.fetch_gstin_details(gstin)
|
||||||
|
|
||||||
|
frappe.local.gstin_cache[key] = details
|
||||||
|
frappe.cache().hset('gstin_cache', key, details)
|
||||||
|
return details
|
||||||
|
|
||||||
|
def generate_irn(self):
|
||||||
|
headers = self.get_headers()
|
||||||
|
einvoice = make_einvoice(self.invoice)
|
||||||
|
data = json.dumps(einvoice, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.generate_irn_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.set_einvoice_data(res.get('result'))
|
||||||
|
|
||||||
|
elif '2150' in res.get('message'):
|
||||||
|
# IRN already generated but not updated in invoice
|
||||||
|
# Extract the IRN from the response description and fetch irn details
|
||||||
|
irn = res.get('result')[0].get('Desc').get('Irn')
|
||||||
|
irn_details = self.get_irn_details(irn)
|
||||||
|
if irn_details:
|
||||||
|
self.set_einvoice_data(irn_details)
|
||||||
|
else:
|
||||||
|
raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
|
||||||
|
Contact ERPNext support to resolve the issue.')
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def get_irn_details(self, irn):
|
||||||
|
headers = self.get_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
params = '?irn={irn}'.format(irn=irn)
|
||||||
|
res = self.make_request('get', self.irn_details_url + params, headers)
|
||||||
|
if res.get('success'):
|
||||||
|
return res.get('result')
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error()
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def cancel_irn(self, irn, reason, remark):
|
||||||
|
headers = self.get_headers()
|
||||||
|
data = json.dumps({
|
||||||
|
'Irn': irn,
|
||||||
|
'Cnlrsn': reason,
|
||||||
|
'Cnlrem': remark
|
||||||
|
}, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.cancel_irn_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.invoice.irn_cancelled = 1
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('IRN Cancelled - {}').format(remark)
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def generate_eway_bill(self, **kwargs):
|
||||||
|
args = frappe._dict(kwargs)
|
||||||
|
|
||||||
|
headers = self.get_headers()
|
||||||
|
eway_bill_details = get_eway_bill_details(args)
|
||||||
|
data = json.dumps({
|
||||||
|
'Irn': args.irn,
|
||||||
|
'Distance': cint(eway_bill_details.distance),
|
||||||
|
'TransMode': eway_bill_details.mode_of_transport,
|
||||||
|
'TransId': eway_bill_details.gstin,
|
||||||
|
'TransName': eway_bill_details.transporter,
|
||||||
|
'TrnDocDt': eway_bill_details.document_date,
|
||||||
|
'TrnDocNo': eway_bill_details.document_name,
|
||||||
|
'VehNo': eway_bill_details.vehicle_no,
|
||||||
|
'VehType': eway_bill_details.vehicle_type
|
||||||
|
}, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.generate_ewaybill_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.invoice.ewaybill = res.get('result').get('EwbNo')
|
||||||
|
self.invoice.eway_bill_cancelled = 0
|
||||||
|
self.invoice.update(args)
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('E-Way Bill Generated')
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def cancel_eway_bill(self, eway_bill, reason, remark):
|
||||||
|
headers = self.get_headers()
|
||||||
|
data = json.dumps({
|
||||||
|
'ewbNo': eway_bill,
|
||||||
|
'cancelRsnCode': reason,
|
||||||
|
'cancelRmrk': remark
|
||||||
|
}, indent=4)
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
|
||||||
|
if res.get('success'):
|
||||||
|
self.invoice.ewaybill = ''
|
||||||
|
self.invoice.eway_bill_cancelled = 1
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('E-Way Bill Cancelled - {}').format(remark)
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise RequestFailed
|
||||||
|
|
||||||
|
except RequestFailed:
|
||||||
|
errors = self.sanitize_error_message(res.get('message'))
|
||||||
|
self.raise_error(errors=errors)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
self.log_error(data)
|
||||||
|
self.raise_error(True)
|
||||||
|
|
||||||
|
def sanitize_error_message(self, message):
|
||||||
|
'''
|
||||||
|
On validation errors, response message looks something like this:
|
||||||
|
message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
|
||||||
|
3095 : Supplier GSTIN is inactive'
|
||||||
|
we search for string between ':' to extract the error messages
|
||||||
|
errors = [
|
||||||
|
': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
|
||||||
|
': Test'
|
||||||
|
]
|
||||||
|
then we trim down the message by looping over errors
|
||||||
|
'''
|
||||||
|
errors = re.findall(': [^:]+', message)
|
||||||
|
for idx, e in enumerate(errors):
|
||||||
|
# remove colons
|
||||||
|
errors[idx] = errors[idx].replace(':', '').strip()
|
||||||
|
# if not last
|
||||||
|
if idx != len(errors) - 1:
|
||||||
|
# remove last 7 chars eg: ', 3095 '
|
||||||
|
errors[idx] = errors[idx][:-6]
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
def log_error(self, data={}):
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
data = json.loads(data)
|
||||||
|
|
||||||
|
seperator = "--" * 50
|
||||||
|
err_tb = traceback.format_exc()
|
||||||
|
err_msg = str(sys.exc_info()[1])
|
||||||
|
data = json.dumps(data, indent=4)
|
||||||
|
|
||||||
|
message = "\n".join([
|
||||||
|
"Error", err_msg, seperator,
|
||||||
|
"Data:", data, seperator,
|
||||||
|
"Exception:", err_tb
|
||||||
|
])
|
||||||
|
frappe.log_error(title=_('E Invoice Request Failed'), message=message)
|
||||||
|
|
||||||
|
def raise_error(self, raise_exception=False, errors=[]):
|
||||||
|
title = _('E Invoice Request Failed')
|
||||||
|
if errors:
|
||||||
|
frappe.throw(errors, title=title, as_list=1)
|
||||||
|
else:
|
||||||
|
link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
|
||||||
|
frappe.msgprint(
|
||||||
|
_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
|
||||||
|
title=title,
|
||||||
|
raise_exception=raise_exception,
|
||||||
|
indicator='red'
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_einvoice_data(self, res):
|
||||||
|
enc_signed_invoice = res.get('SignedInvoice')
|
||||||
|
dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data']
|
||||||
|
|
||||||
|
self.invoice.irn = res.get('Irn')
|
||||||
|
self.invoice.ewaybill = res.get('EwbNo')
|
||||||
|
self.invoice.signed_einvoice = dec_signed_invoice
|
||||||
|
self.invoice.signed_qr_code = res.get('SignedQRCode')
|
||||||
|
|
||||||
|
self.attach_qrcode_image()
|
||||||
|
|
||||||
|
self.invoice.flags.updater_reference = {
|
||||||
|
'doctype': self.invoice.doctype,
|
||||||
|
'docname': self.invoice.name,
|
||||||
|
'label': _('IRN Generated')
|
||||||
|
}
|
||||||
|
self.update_invoice()
|
||||||
|
|
||||||
|
def attach_qrcode_image(self):
|
||||||
|
qrcode = self.invoice.signed_qr_code
|
||||||
|
doctype = self.invoice.doctype
|
||||||
|
docname = self.invoice.name
|
||||||
|
|
||||||
|
_file = frappe.new_doc('File')
|
||||||
|
_file.update({
|
||||||
|
'file_name': f'QRCode_{docname}.png',
|
||||||
|
'attached_to_doctype': doctype,
|
||||||
|
'attached_to_name': docname,
|
||||||
|
'content': 'qrcode',
|
||||||
|
'is_private': 1
|
||||||
|
})
|
||||||
|
_file.insert()
|
||||||
|
frappe.db.commit()
|
||||||
|
url = qrcreate(qrcode, error='L')
|
||||||
|
abs_file_path = os.path.abspath(_file.get_full_path())
|
||||||
|
url.png(abs_file_path, scale=2, quiet_zone=1)
|
||||||
|
|
||||||
|
self.invoice.qrcode_image = _file.file_url
|
||||||
|
|
||||||
|
def update_invoice(self):
|
||||||
|
self.invoice.flags.ignore_validate_update_after_submit = True
|
||||||
|
self.invoice.flags.ignore_validate = True
|
||||||
|
self.invoice.save()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_einvoice(doctype, docname):
|
||||||
|
invoice = frappe.get_doc(doctype, docname)
|
||||||
|
return make_einvoice(invoice)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def generate_irn(doctype, docname):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.generate_irn()
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def cancel_irn(doctype, docname, irn, reason, remark):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.cancel_irn(irn, reason, remark)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def generate_eway_bill(doctype, docname, **kwargs):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.generate_eway_bill(**kwargs)
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
|
||||||
|
gsp_connector = GSPConnector(doctype, docname)
|
||||||
|
gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
@ -87,7 +87,7 @@ def add_custom_roles_for_reports():
|
|||||||
)).insert()
|
)).insert()
|
||||||
|
|
||||||
def add_permissions():
|
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)
|
add_permission(doctype, 'All', 0)
|
||||||
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
|
||||||
add_permission(doctype, role, 0)
|
add_permission(doctype, role, 0)
|
||||||
@ -103,9 +103,10 @@ def add_permissions():
|
|||||||
def add_print_formats():
|
def add_print_formats():
|
||||||
frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
|
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_pos_invoice")
|
||||||
|
frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
|
||||||
|
|
||||||
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
||||||
name in('GST POS Invoice', 'GST Tax Invoice') """)
|
name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """)
|
||||||
|
|
||||||
def make_custom_fields(update=True):
|
def make_custom_fields(update=True):
|
||||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||||
@ -351,7 +352,6 @@ def make_custom_fields(update=True):
|
|||||||
'label': 'Mode of Transport',
|
'label': 'Mode of Transport',
|
||||||
'fieldtype': 'Select',
|
'fieldtype': 'Select',
|
||||||
'options': '\nRoad\nAir\nRail\nShip',
|
'options': '\nRoad\nAir\nRail\nShip',
|
||||||
'default': 'Road',
|
|
||||||
'insert_after': 'transporter_name',
|
'insert_after': 'transporter_name',
|
||||||
'print_hide': 1,
|
'print_hide': 1,
|
||||||
'translatable': 0
|
'translatable': 0
|
||||||
@ -388,13 +388,34 @@ def make_custom_fields(update=True):
|
|||||||
'fieldname': 'ewaybill',
|
'fieldname': 'ewaybill',
|
||||||
'label': 'E-Way Bill No.',
|
'label': 'E-Way Bill No.',
|
||||||
'fieldtype': 'Data',
|
'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,
|
'allow_on_submit': 1,
|
||||||
'insert_after': 'tax_id',
|
'insert_after': 'tax_id',
|
||||||
'translatable': 0
|
'translatable': 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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='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)
|
||||||
|
]
|
||||||
|
|
||||||
custom_fields = {
|
custom_fields = {
|
||||||
'Address': [
|
'Address': [
|
||||||
dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
|
dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
|
||||||
@ -407,7 +428,7 @@ def make_custom_fields(update=True):
|
|||||||
'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_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 Order': purchase_invoice_gst_fields,
|
||||||
'Purchase Receipt': 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,
|
||||||
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
|
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
|
||||||
'Sales Order': sales_invoice_gst_fields,
|
'Sales Order': sales_invoice_gst_fields,
|
||||||
'Tax Category': inter_state_gst_field,
|
'Tax Category': inter_state_gst_field,
|
||||||
|
@ -12,3 +12,4 @@ taxjar==1.9.0
|
|||||||
tweepy==3.8.0
|
tweepy==3.8.0
|
||||||
Unidecode==1.1.1
|
Unidecode==1.1.1
|
||||||
WooCommerce==2.1.1
|
WooCommerce==2.1.1
|
||||||
|
pycryptodome==3.9.8
|
Loading…
x
Reference in New Issue
Block a user