Merge branch 'develop' into tcs_calculation
diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py
index d6221a8..378b735 100644
--- a/erpnext/regional/india/__init__.py
+++ b/erpnext/regional/india/__init__.py
@@ -20,6 +20,7 @@
'Jharkhand',
'Karnataka',
'Kerala',
+ 'Ladakh',
'Lakshadweep Islands',
'Madhya Pradesh',
'Maharashtra',
@@ -59,6 +60,7 @@
"Jharkhand": "20",
"Karnataka": "29",
"Kerala": "32",
+ "Ladakh": "38",
"Lakshadweep Islands": "31",
"Madhya Pradesh": "23",
"Maharashtra": "27",
@@ -80,4 +82,4 @@
"West Bengal": "19",
}
-number_state_mapping = {v: k for k, v in iteritems(state_numbers)}
\ No newline at end of file
+number_state_mapping = {v: k for k, v in iteritems(state_numbers)}
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index 9fa94c4..a756b57 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -188,7 +188,6 @@
'fieldname': 'vehicle_no',
'label': 'Vehicle No',
'fieldtype': 'Data',
- 'depends_on': 'eval:(doc.mode_of_transport === "Road")',
'default': frm.doc.vehicle_no
},
{
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 5f701f2..322fa18 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -23,7 +23,7 @@
invalid_doctype = doc.doctype != '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')
- no_taxes_applied = len(doc.get('taxes', [])) == 0
+ 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:
return
@@ -37,7 +37,7 @@
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:
+ elif doc.irn and 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():
@@ -63,7 +63,7 @@
elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
- if not supply_type:
+ 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'))
@@ -128,7 +128,7 @@
if details:
frappe.local.gstin_cache[key] = details
return details
-
+
if not details:
return GSPConnector.get_gstin_details(gstin)
@@ -160,7 +160,7 @@
item.update(d.as_dict())
item.sr_no = d.idx
- item.description = d.item_name.replace('"', '\\"')
+ item.description = json.dumps(d.item_name)[1:-1]
item.qty = abs(item.qty)
item.discount_amount = 0
@@ -174,7 +174,7 @@
item.serial_no = ""
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
@@ -232,9 +232,9 @@
invoice_value_details.round_off = invoice.base_rounding_adjustment
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
-
+
invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
-
+
return invoice_value_details
def update_invoice_taxes(invoice, invoice_value_details):
@@ -251,13 +251,13 @@
if t.account_head in gst_accounts.cess_account:
# using after discount amt since item also uses after discount amt for cess calc
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
-
+
for tax_type in ['igst', 'cgst', 'sgst']:
if t.account_head in gst_accounts[f'{tax_type}_account']:
invoice_value_details[f'total_{tax_type}_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):
@@ -329,23 +329,23 @@
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:
if invoice.gst_category == 'Overseas':
shipping_details = get_overseas_address_details(invoice.shipping_address_name)
else:
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({})
@@ -357,7 +357,7 @@
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:
@@ -419,7 +419,7 @@
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
@@ -452,19 +452,19 @@
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)
@@ -473,7 +473,7 @@
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({
@@ -504,7 +504,7 @@
except Exception:
self.log_error(res)
self.raise_error(True)
-
+
def get_headers(self):
return {
'content-type': 'application/json',
@@ -526,7 +526,7 @@
else:
self.log_error(res)
raise RequestFailed
-
+
except RequestFailed:
self.raise_error()
@@ -571,7 +571,7 @@
else:
raise RequestFailed
-
+
except RequestFailed:
errors = self.sanitize_error_message(res.get('message'))
self.raise_error(errors=errors)
@@ -579,7 +579,7 @@
except Exception:
self.log_error(data)
self.raise_error(True)
-
+
def get_irn_details(self, irn):
headers = self.get_headers()
@@ -590,7 +590,7 @@
return res.get('result')
else:
raise RequestFailed
-
+
except RequestFailed:
errors = self.sanitize_error_message(res.get('message'))
self.raise_error(errors=errors)
@@ -598,7 +598,7 @@
except Exception:
self.log_error()
self.raise_error(True)
-
+
def cancel_irn(self, irn, reason, remark):
headers = self.get_headers()
data = json.dumps({
@@ -620,7 +620,7 @@
else:
raise RequestFailed
-
+
except RequestFailed:
errors = self.sanitize_error_message(res.get('message'))
self.raise_error(errors=errors)
@@ -669,7 +669,7 @@
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({
@@ -701,7 +701,7 @@
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:
@@ -740,7 +740,7 @@
"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:
@@ -753,7 +753,7 @@
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']
@@ -792,7 +792,7 @@
_file.save()
frappe.db.commit()
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
diff --git a/erpnext/regional/india/gst_state_code_data.json b/erpnext/regional/india/gst_state_code_data.json
index ff88e0f..8481c27 100644
--- a/erpnext/regional/india/gst_state_code_data.json
+++ b/erpnext/regional/india/gst_state_code_data.json
@@ -168,5 +168,10 @@
"state_number": "37",
"state_code": "AD",
"state_name": "Andhra Pradesh (New)"
+ },
+ {
+ "state_number": "38",
+ "state_code": "LA",
+ "state_name": "Ladakh"
}
]
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index e89885f..cb30605 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -55,6 +55,14 @@
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
+def validate_pan_for_india(doc, method):
+ if doc.get('country') != 'India' or not doc.pan:
+ return
+
+ p = re.compile("[A-Z]{5}[0-9]{4}[A-Z]{1}")
+ if not p.match(doc.pan):
+ frappe.throw(_("Invalid PAN No. The input you've entered doesn't match the format of PAN."))
+
def validate_tax_category(doc, method):
if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}):
if doc.is_inter_state:
@@ -772,3 +780,24 @@
)
return gl_entries
+
+@frappe.whitelist()
+def get_regional_round_off_accounts(company, account_list):
+ country = frappe.get_cached_value('Company', company, 'country')
+
+ if country != 'India':
+ return
+
+ if isinstance(account_list, string_types):
+ account_list = json.loads(account_list)
+
+ if not frappe.db.get_single_value('GST Settings', 'round_off_gst_values'):
+ return
+
+ gst_accounts = get_gst_accounts(company)
+ gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
+ + gst_accounts.get('igst_account')
+
+ account_list.extend(gst_account_list)
+
+ return account_list
\ No newline at end of file