fix: address validations & cancel eway bill dialog

This commit is contained in:
Saqib Ansari
2021-04-13 17:38:07 +05:30
parent 8e01c8aefb
commit 552330f744
2 changed files with 216 additions and 167 deletions

View File

@@ -1,13 +1,13 @@
erpnext.setup_einvoice_actions = (doctype) => { erpnext.setup_einvoice_actions = (doctype) => {
frappe.ui.form.on(doctype, { frappe.ui.form.on(doctype, {
async refresh(frm) { async refresh(frm) {
const { message } = await frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable"); const res = await frappe.call({
const einvoicing_enabled = cint(message.enable); method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
const supply_type = frm.doc.gst_category; args: { doc: frm.doc }
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; const invoice_eligible = res.message;
if (!einvoicing_enabled || !valid_supply_type || company_transaction) return; if (!invoice_eligible) return;
const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc; const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
@@ -110,45 +110,25 @@ erpnext.setup_einvoice_actions = (doctype) => {
} }
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) { 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 action = () => {
const d = new frappe.ui.Dialog({ let message = __('Cancellation of e-way bill is currently not supported. ');
title: __('Cancel E-Way Bill'), message += '<br><br>';
fields: fields, message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
frappe.msgprint({
title: __('Update E-Way Bill Cancelled Status?'),
message: message,
indicator: 'orange',
primary_action: function() { primary_action: function() {
const data = d.get_values();
frappe.call({ frappe.call({
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill', method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
args: { args: { doctype, docname: name },
doctype,
docname: name,
eway_bill: ewaybill,
reason: data.reason.split('-')[0],
remark: data.remark
},
freeze: true, freeze: true,
callback: () => frm.reload_doc() || d.hide(), callback: () => frm.reload_doc()
error: () => d.hide()
}); });
}, },
primary_action_label: __('Submit') primary_action_label: __('Yes')
}); });
d.show();
}; };
add_custom_button(__("Cancel E-Way Bill"), action); add_custom_button(__("Cancel E-Way Bill"), action);
} }

View File

@@ -5,6 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import os import os
import re import re
import six
import jwt import jwt
import sys import sys
import json import json
@@ -18,14 +19,35 @@ 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 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, get_link_to_form from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
def validate_einvoice_fields(doc): @frappe.whitelist()
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) def validate_eligibility(doc):
invalid_doctype = doc.doctype != 'Sales Invoice' if isinstance(doc, six.string_types):
doc = json.loads(doc)
invalid_doctype = doc.get('doctype') != 'Sales Invoice'
if invalid_doctype:
return False
einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
if not einvoicing_enabled:
return False
if getdate(doc.get('posting_date')) < getdate('2021-04-01'):
return False
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'] 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') company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
no_taxes_applied = not doc.get('taxes') no_taxes_applied = not doc.get('taxes')
if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied: if invalid_supply_type or company_transaction or no_taxes_applied:
return False
return True
def validate_einvoice_fields(doc):
invoice_eligible = validate_eligibility(doc)
if not invoice_eligible:
return return
if doc.docstatus == 0 and doc._action == 'save': if doc.docstatus == 0 and doc._action == 'save':
@@ -86,36 +108,36 @@ def get_doc_details(invoice):
invoice_date=invoice_date invoice_date=invoice_date
)) ))
def get_party_details(address_name, company_address=None, billing_address=None, shipping_address=None): def validate_address_fields(address, is_shipping_address):
d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0] if ((not address.gstin and not is_shipping_address)
or not address.city
if ((not d.gstin and not shipping_address) or not address.pincode
or not d.city or not address.address_title
or not d.pincode or not address.address_line1
or not d.address_title or not address.gst_state_number):
or not d.address_line1
or not d.gst_state_number):
frappe.throw( frappe.throw(
msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format( msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
get_link_to_form('Address', address_name)
),
title=_('Missing Address Fields') title=_('Missing Address Fields')
) )
if d.gst_state_number == 97: def get_party_details(address_name, is_shipping_address=False):
addr = frappe.get_doc('Address', address_name)
validate_address_fields(addr, is_shipping_address)
if addr.gst_state_number == 97:
# according to einvoice standard # according to einvoice standard
pincode = 999999 addr.pincode = 999999
party_address_details = frappe._dict(dict( party_address_details = frappe._dict(dict(
legal_name=d.address_title, legal_name=sanitize_for_json(addr.address_title),
location=d.city, pincode=d.pincode, location=sanitize_for_json(addr.city),
state_code=d.gst_state_number, pincode=addr.pincode, gstin=addr.gstin,
address_line1=d.address_line1, state_code=addr.gst_state_number,
address_line2=d.address_line2 address_line1=sanitize_for_json(addr.address_line1),
address_line2=sanitize_for_json(addr.address_line2)
)) ))
if d.gstin:
party_address_details.gstin = d.gstin
return party_address_details return party_address_details
@@ -205,9 +227,11 @@ def update_item_taxes(invoice, item):
item[attr] = 0 item[attr] = 0
for t in invoice.taxes: for t in invoice.taxes:
# this contains item wise tax rate & tax amount (incl. discount) is_applicable = t.tax_amount and t.account_head in gst_accounts_list
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) if is_applicable:
if t.account_head in gst_accounts_list: # this contains item wise tax rate & tax amount (incl. discount)
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
item_tax_rate = item_tax_detail[0] item_tax_rate = item_tax_detail[0]
# item tax amount excluding discount amount # item tax amount excluding discount amount
item_tax_amount = (item_tax_rate / 100) * item.taxable_value item_tax_amount = (item_tax_rate / 100) * item.taxable_value
@@ -303,6 +327,10 @@ def get_payment_details(invoice):
)) ))
def get_return_doc_reference(invoice): def get_return_doc_reference(invoice):
if not invoice.return_against:
frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
.format(frappe.bold('Return Against')), title=_('Missing Field'))
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date') invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
return frappe._dict(dict( return frappe._dict(dict(
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy') invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
@@ -310,7 +338,11 @@ def get_return_doc_reference(invoice):
def get_eway_bill_details(invoice): def get_eway_bill_details(invoice):
if invoice.is_return: if invoice.is_return:
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed')) frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
title=_('Invalid Fields'))
if not invoice.distance:
frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field'))
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' } mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' } vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
@@ -328,19 +360,54 @@ def get_eway_bill_details(invoice):
def validate_mandatory_fields(invoice): def validate_mandatory_fields(invoice):
if not invoice.company_address: if not invoice.company_address:
frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields')) frappe.throw(
_('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
title=_('Missing Fields')
)
if not invoice.customer_address: if not invoice.customer_address:
frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields')) frappe.throw(
_('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
title=_('Missing Fields')
)
if not frappe.db.get_value('Address', invoice.company_address, 'gstin'): if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
frappe.throw( frappe.throw(
_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'), _('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
@@ -348,6 +378,39 @@ def validate_mandatory_fields(invoice):
title=_('Missing Fields') title=_('Missing Fields')
) )
if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
frappe.throw( def validate_totals(einvoice):
_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'), item_list = einvoice['ItemList']
title=_('Missing Fields') value_details = einvoice['ValDtls']
)
total_item_ass_value = 0
total_item_cgst_value = 0
total_item_sgst_value = 0
total_item_igst_value = 0
total_item_value = 0
for item in item_list:
total_item_ass_value += flt(item['AssAmt'])
total_item_cgst_value += flt(item['CgstAmt'])
total_item_sgst_value += flt(item['SgstAmt'])
total_item_igst_value += flt(item['IgstAmt'])
total_item_value += flt(item['TotItemVal'])
if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1:
frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
calculated_invoice_value = \
flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
+ flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
+ flt(value_details['OthChrg']) - flt(value_details['Discount'])
if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
def make_einvoice(invoice): def make_einvoice(invoice):
validate_mandatory_fields(invoice) validate_mandatory_fields(invoice)
@@ -351,12 +418,12 @@ def make_einvoice(invoice):
item_list = get_item_list(invoice) item_list = get_item_list(invoice)
doc_details = get_doc_details(invoice) doc_details = get_doc_details(invoice)
invoice_value_details = get_invoice_value_details(invoice) invoice_value_details = get_invoice_value_details(invoice)
seller_details = get_party_details(invoice.company_address, company_address=1) seller_details = get_party_details(invoice.company_address)
if invoice.gst_category == 'Overseas': if invoice.gst_category == 'Overseas':
buyer_details = get_overseas_address_details(invoice.customer_address) buyer_details = get_overseas_address_details(invoice.customer_address)
else: else:
buyer_details = get_party_details(invoice.customer_address, billing_address=1) buyer_details = get_party_details(invoice.customer_address)
place_of_supply = get_place_of_supply(invoice, invoice.doctype) place_of_supply = get_place_of_supply(invoice, invoice.doctype)
if place_of_supply: if place_of_supply:
place_of_supply = place_of_supply.split('-')[0] place_of_supply = place_of_supply.split('-')[0]
@@ -365,12 +432,15 @@ def make_einvoice(invoice):
buyer_details.update(dict(place_of_supply=place_of_supply)) buyer_details.update(dict(place_of_supply=place_of_supply))
seller_details.update(dict(legal_name=invoice.company))
buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({}) 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: if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
if invoice.gst_category == 'Overseas': if invoice.gst_category == 'Overseas':
shipping_details = get_overseas_address_details(invoice.shipping_address_name) shipping_details = get_overseas_address_details(invoice.shipping_address_name)
else: else:
shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=1) shipping_details = get_party_details(invoice.shipping_address_name, shipping_address=True)
if invoice.is_pos and invoice.base_paid_amount: if invoice.is_pos and invoice.base_paid_amount:
payment_details = get_payment_details(invoice) payment_details = get_payment_details(invoice)
@@ -378,7 +448,7 @@ def make_einvoice(invoice):
if invoice.is_return and invoice.return_against: if invoice.is_return and invoice.return_against:
prev_doc_details = get_return_doc_reference(invoice) prev_doc_details = get_return_doc_reference(invoice)
if invoice.transporter and cint(invoice.distance): if invoice.transporter and flt(invoice.distance) and not invoice.is_return:
eway_bill_details = get_eway_bill_details(invoice) eway_bill_details = get_eway_bill_details(invoice)
# not yet implemented # not yet implemented
@@ -391,74 +461,81 @@ def make_einvoice(invoice):
period_details=period_details, prev_doc_details=prev_doc_details, period_details=period_details, prev_doc_details=prev_doc_details,
export_details=export_details, eway_bill_details=eway_bill_details export_details=export_details, eway_bill_details=eway_bill_details
) )
einvoice = json.loads(einvoice)
validations = json.loads(read_json('einv_validation')) try:
errors = validate_einvoice(validations, einvoice) einvoice = safe_json_load(einvoice)
if errors: einvoice = santize_einvoice_fields(einvoice)
message = "\n".join([ validate_totals(einvoice)
"E Invoice: ", json.dumps(einvoice, indent=4),
"-" * 50, except Exception:
"Errors: ", json.dumps(errors, indent=4) log_error(einvoice)
]) link_to_error_list = '<a href="List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
frappe.log_error(title="E Invoice Validation Failed", message=message) frappe.throw(
frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1) _('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
invoice.name, link_to_error_list),
title=_('E Invoice Creation Failed')
)
return einvoice return einvoice
def validate_einvoice(validations, einvoice, errors=None): def log_error(data=None):
if errors is None: if isinstance(data, six.string_types):
errors = [] data = json.loads(data)
for fieldname, field_validation in validations.items(): seperator = "--" * 50
value = einvoice.get(fieldname, None) err_tb = traceback.format_exc()
if not value or value == "None": err_msg = str(sys.exc_info()[1])
# remove keys with empty values data = json.dumps(data, indent=4)
einvoice.pop(fieldname, None)
continue
value_type = field_validation.get("type").lower() message = "\n".join([
if value_type in ['object', 'array']: "Error", err_msg, seperator,
child_validations = field_validation.get('properties') "Data:", data, seperator,
"Exception:", err_tb
])
frappe.log_error(title=_('E Invoice Request Failed'), message=message)
if isinstance(value, list): def santize_einvoice_fields(einvoice):
for d in value: int_fields = ["Pin","Distance","CrDay"]
validate_einvoice(child_validations, d, errors) float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
if not d: copy = einvoice.copy()
# remove empty dicts for key, value in copy.items():
einvoice.pop(fieldname, None) if isinstance(value, list):
for idx, d in enumerate(value):
santized_dict = santize_einvoice_fields(d)
if santized_dict:
einvoice[key][idx] = santized_dict
else:
einvoice[key].pop(idx)
if not einvoice[key]:
einvoice.pop(key, None)
elif isinstance(value, dict):
santized_dict = santize_einvoice_fields(value)
if santized_dict:
einvoice[key] = santized_dict
else: else:
validate_einvoice(child_validations, value, errors) einvoice.pop(key, None)
if not value:
# remove empty dicts
einvoice.pop(fieldname, None)
continue
# convert to int or str elif not value or value == "None":
if value_type == 'string': einvoice.pop(key, None)
einvoice[fieldname] = str(value)
elif value_type == 'number':
is_integer = '.' not in str(field_validation.get('maximum'))
precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value)
value = einvoice[fieldname]
max_length = field_validation.get('maxLength') elif key in float_fields:
minimum = flt(field_validation.get('minimum')) einvoice[key] = flt(value, 2)
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 elif key in int_fields:
einvoice[key] = cint(value)
if value_type == 'string' and len(value) > max_length: def safe_json_load(json_string):
errors.append(_('{} should not exceed {} characters').format(label, max_length)) JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
if value_type == 'number' and (value > maximum or value < minimum): try:
errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum)) return json.loads(json_string)
if pattern_str and not pattern.match(value) and field_validation.get('validationMsg'): except JSONDecodeError as e:
errors.append(field_validation.get('validationMsg')) # print a snippet of 40 characters around the location where error occured
pos = e.pos
return errors start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
snippet = json_string[start:end]
frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
class RequestFailed(Exception): pass class RequestFailed(Exception): pass
@@ -484,14 +561,11 @@ class GSPConnector():
def get_credentials(self): def get_credentials(self):
if self.invoice: if self.invoice:
gstin = self.get_seller_gstin() gstin = self.get_seller_gstin()
if not self.e_invoice_settings.enable:
frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin] credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
if credentials_for_gstin: if credentials_for_gstin:
credentials = credentials_for_gstin[0] self.credentials = credentials_for_gstin[0]
else: else:
frappe.throw(_('Cannot find e-invoicing credentials for GSTIN {}. Please check E-Invoice Settings').format(gstin)) frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
else: else:
credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
return credentials return credentials
@@ -545,7 +619,7 @@ class GSPConnector():
self.e_invoice_settings.reload() self.e_invoice_settings.reload()
except Exception: except Exception:
self.log_error(res) log_error(res)
self.raise_error(True) self.raise_error(True)
def get_headers(self): def get_headers(self):
@@ -567,14 +641,14 @@ class GSPConnector():
if res.get('success'): if res.get('success'):
return res.get('result') return res.get('result')
else: else:
self.log_error(res) log_error(res)
raise RequestFailed raise RequestFailed
except RequestFailed: except RequestFailed:
self.raise_error() self.raise_error()
except Exception: except Exception:
self.log_error() log_error()
self.raise_error(True) self.raise_error(True)
@staticmethod @staticmethod
def get_gstin_details(gstin): def get_gstin_details(gstin):
@@ -619,7 +693,7 @@ class GSPConnector():
self.raise_error(errors=errors) self.raise_error(errors=errors)
except Exception: except Exception:
self.log_error(data) log_error(data)
self.raise_error(True) self.raise_error(True)
def get_irn_details(self, irn): def get_irn_details(self, irn):
@@ -638,7 +712,7 @@ class GSPConnector():
self.raise_error(errors=errors) self.raise_error(errors=errors)
except Exception: except Exception:
self.log_error() log_error()
self.raise_error(True) self.raise_error(True)
def cancel_irn(self, irn, reason, remark): def cancel_irn(self, irn, reason, remark):
@@ -651,7 +725,7 @@ class GSPConnector():
try: try:
res = self.make_request('post', self.cancel_irn_url, headers, data) res = self.make_request('post', self.cancel_irn_url, headers, data)
if res.get('success'): if res.get('success') or '9999' in res.get('message'):
self.invoice.irn_cancelled = 1 self.invoice.irn_cancelled = 1
self.invoice.flags.updater_reference = { self.invoice.flags.updater_reference = {
'doctype': self.invoice.doctype, 'doctype': self.invoice.doctype,
@@ -668,7 +742,7 @@ class GSPConnector():
self.raise_error(errors=errors) self.raise_error(errors=errors)
except Exception: except Exception:
self.log_error(data) log_error(data)
self.raise_error(True) self.raise_error(True)
def generate_eway_bill(self, **kwargs): def generate_eway_bill(self, **kwargs):
args = frappe._dict(kwargs) args = frappe._dict(kwargs)
@@ -708,7 +782,7 @@ class GSPConnector():
self.raise_error(errors=errors) self.raise_error(errors=errors)
except Exception: except Exception:
self.log_error(data) log_error(data)
self.raise_error(True) self.raise_error(True)
def cancel_eway_bill(self, eway_bill, reason, remark): def cancel_eway_bill(self, eway_bill, reason, remark):
@@ -740,7 +814,7 @@ class GSPConnector():
self.raise_error(errors=errors) self.raise_error(errors=errors)
except Exception: except Exception:
self.log_error(data) log_error(data)
self.raise_error(True) self.raise_error(True)
def sanitize_error_message(self, message): def sanitize_error_message(self, message):
@@ -766,22 +840,6 @@ class GSPConnector():
return errors 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=[]): def raise_error(self, raise_exception=False, errors=[]):
title = _('E Invoice Request Failed') title = _('E Invoice Request Failed')
if errors: if errors:
@@ -801,6 +859,8 @@ class GSPConnector():
self.invoice.irn = res.get('Irn') self.invoice.irn = res.get('Irn')
self.invoice.ewaybill = res.get('EwbNo') self.invoice.ewaybill = res.get('EwbNo')
self.invoice.ack_no = res.get('AckNo')
self.invoice.ack_date = res.get('AckDt')
self.invoice.signed_einvoice = dec_signed_invoice self.invoice.signed_einvoice = dec_signed_invoice
self.invoice.signed_qr_code = res.get('SignedQRCode') self.invoice.signed_qr_code = res.get('SignedQRCode')
@@ -838,6 +898,11 @@ class GSPConnector():
self.invoice.flags.ignore_validate = True self.invoice.flags.ignore_validate = True
self.invoice.save() self.invoice.save()
def sanitize_for_json(string):
"""Escape JSON specific characters from a string."""
# json.dumps adds double-quotes to the string. Indexing to remove them.
return json.dumps(string)[1:-1]
@frappe.whitelist() @frappe.whitelist()
def get_einvoice(doctype, docname): def get_einvoice(doctype, docname):
invoice = frappe.get_doc(doctype, docname) invoice = frappe.get_doc(doctype, docname)
@@ -860,5 +925,9 @@ def generate_eway_bill(doctype, docname, **kwargs):
@frappe.whitelist() @frappe.whitelist()
def cancel_eway_bill(doctype, docname, eway_bill, reason, remark): def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
gsp_connector = GSPConnector(doctype, docname) # TODO: uncomment when eway_bill api from Adequare is enabled
gsp_connector.cancel_eway_bill(eway_bill, reason, remark) # gsp_connector = GSPConnector(doctype, docname)
# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
# update cancelled status only, to be able to cancel irn next
frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)