Merge branch 'develop' into dont_update_pe_title
This commit is contained in:
commit
afc766bbf1
@ -11,15 +11,18 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Title",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-08-30 19:41:25.783852",
|
||||
"modified": "2021-03-03 11:50:38.748872",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Category",
|
||||
|
@ -22,8 +22,8 @@
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
<h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
|
||||
<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>
|
||||
@ -54,8 +54,8 @@
|
||||
<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
|
||||
<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>
|
||||
@ -89,7 +89,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow-x: auto;">
|
||||
<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
|
||||
<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -258,7 +258,7 @@
|
||||
{% } %}
|
||||
{% } else { %}
|
||||
{% if(data[i]["party"]|| " ") { %}
|
||||
{% if((data[i]["party"]) != __("'Total'")) { %}
|
||||
{% if(!data[i]["is_total_row"]) { %}
|
||||
<td>
|
||||
{% if(!(filters.customer || filters.supplier)) { %}
|
||||
{%= data[i]["party"] %}
|
||||
|
@ -1061,7 +1061,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2020-12-01 13:38:35.349024",
|
||||
"modified": "2021-03-04 00:38:35.349024",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting",
|
||||
@ -1071,7 +1071,7 @@
|
||||
"pin_to_top": 0,
|
||||
"shortcuts": [
|
||||
{
|
||||
"label": "Chart Of Accounts",
|
||||
"label": "Chart of Accounts",
|
||||
"link_to": "Account",
|
||||
"type": "DocType"
|
||||
},
|
||||
@ -1116,4 +1116,4 @@
|
||||
"type": "Dashboard"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 12:00:23.276329",
|
||||
"modified": "2021-03-02 17:34:04.190677",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
@ -113,5 +113,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -103,7 +103,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-01-29 12:02:16.106942",
|
||||
"modified": "2021-03-02 17:35:14.084342",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Mpesa Settings",
|
||||
@ -147,5 +147,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -70,7 +70,7 @@
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-29 20:24:56.916104",
|
||||
"modified": "2021-03-02 17:35:27.544259",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Plaid Settings",
|
||||
@ -88,5 +88,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -330,7 +330,7 @@
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-11-05 20:44:03.664891",
|
||||
"modified": "2021-03-02 17:35:41.953317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Shopify Settings",
|
||||
@ -348,5 +348,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -278,6 +278,9 @@ doc_events = {
|
||||
('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
|
||||
'validate': ['erpnext.regional.india.utils.set_place_of_supply']
|
||||
},
|
||||
('Sales Invoice', 'Purchase Invoice'): {
|
||||
'validate': ['erpnext.regional.india.utils.validate_document_name']
|
||||
},
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
|
||||
|
@ -528,6 +528,10 @@ class WorkOrder(Document):
|
||||
if not reset_only_qty:
|
||||
self.required_items = []
|
||||
|
||||
operation = None
|
||||
if self.get('operations') and len(self.operations) == 1:
|
||||
operation = self.operations[0].operation
|
||||
|
||||
if self.bom_no and self.qty:
|
||||
item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty,
|
||||
fetch_exploded = self.use_multi_level_bom)
|
||||
@ -536,6 +540,9 @@ class WorkOrder(Document):
|
||||
for d in self.get("required_items"):
|
||||
if item_dict.get(d.item_code):
|
||||
d.required_qty = item_dict.get(d.item_code).get("qty")
|
||||
|
||||
if not d.operation:
|
||||
d.operation = operation
|
||||
else:
|
||||
# Attribute a big number (999) to idx for sorting putpose in case idx is NULL
|
||||
# For instance in BOM Explosion Item child table, the items coming from sub assembly items
|
||||
@ -543,7 +550,7 @@ class WorkOrder(Document):
|
||||
self.append('required_items', {
|
||||
'rate': item.rate,
|
||||
'amount': item.amount,
|
||||
'operation': item.operation,
|
||||
'operation': item.operation or operation,
|
||||
'item_code': item.item_code,
|
||||
'item_name': item.item_name,
|
||||
'description': item.description,
|
||||
@ -879,7 +886,7 @@ def create_job_card(work_order, row, qty=0, enable_capacity_planning=False, auto
|
||||
doc.schedule_time_logs(row)
|
||||
|
||||
doc.insert()
|
||||
frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)))
|
||||
frappe.msgprint(_("Job card {0} created").format(get_link_to_form("Job Card", doc.name)), alert=True)
|
||||
|
||||
return doc
|
||||
|
||||
|
@ -97,7 +97,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-19 11:07:55.873991",
|
||||
"modified": "2021-03-03 17:49:59.579723",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Payroll Settings",
|
||||
@ -114,5 +114,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
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");
|
||||
async refresh(frm) {
|
||||
const einvoicing_enabled = await frappe.db.get_single_value("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;
|
||||
if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return;
|
||||
|
||||
const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
|
||||
|
||||
@ -83,7 +83,7 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
||||
const action = () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Generate E-Way Bill'),
|
||||
wide: 1,
|
||||
size: "large",
|
||||
fields: get_ewaybill_fields(frm),
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
@ -252,7 +252,7 @@ const request_irn_generation = (frm) => {
|
||||
const get_preview_dialog = (frm, action) => {
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Preview"),
|
||||
wide: 1,
|
||||
size: "large",
|
||||
fields: [
|
||||
{
|
||||
"label": "Preview",
|
||||
|
38
erpnext/regional/india/test_utils.py
Normal file
38
erpnext/regional/india/test_utils.py
Normal file
@ -0,0 +1,38 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unittest
|
||||
import frappe
|
||||
from unittest.mock import patch
|
||||
from erpnext.regional.india.utils import validate_document_name
|
||||
|
||||
|
||||
class TestIndiaUtils(unittest.TestCase):
|
||||
@patch("frappe.get_cached_value")
|
||||
def test_validate_document_name(self, mock_get_cached):
|
||||
mock_get_cached.return_value = "India" # mock country
|
||||
posting_date = "2021-05-01"
|
||||
|
||||
invalid_names = [ "SI$1231", "012345678901234567", "SI 2020 05",
|
||||
"SI.2020.0001", "PI2021 - 001" ]
|
||||
for name in invalid_names:
|
||||
doc = frappe._dict(name=name, posting_date=posting_date)
|
||||
self.assertRaises(frappe.ValidationError, validate_document_name, doc)
|
||||
|
||||
valid_names = [ "012345678901236", "SI/2020/0001", "SI/2020-0001",
|
||||
"2020-PI-0001", "PI2020-0001" ]
|
||||
for name in valid_names:
|
||||
doc = frappe._dict(name=name, posting_date=posting_date)
|
||||
try:
|
||||
validate_document_name(doc)
|
||||
except frappe.ValidationError:
|
||||
self.fail("Valid name {} throwing error".format(name))
|
||||
|
||||
@patch("frappe.get_cached_value")
|
||||
def test_validate_document_name_not_india(self, mock_get_cached):
|
||||
mock_get_cached.return_value = "Not India"
|
||||
doc = frappe._dict(name="SI$123", posting_date="2021-05-01")
|
||||
|
||||
try:
|
||||
validate_document_name(doc)
|
||||
except frappe.ValidationError:
|
||||
self.fail("Regional validation related to India are being applied to other countries")
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
import frappe, re, json
|
||||
from frappe import _
|
||||
import erpnext
|
||||
from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words
|
||||
from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
|
||||
from erpnext.regional.india import states, state_numbers
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
@ -14,6 +14,13 @@ from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from frappe.model.utils import get_fetch_values
|
||||
|
||||
|
||||
GST_INVOICE_NUMBER_FORMAT = re.compile(r"^[a-zA-Z0-9\-/]+$") #alphanumeric and - /
|
||||
GSTIN_FORMAT = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$")
|
||||
GSTIN_UIN_FORMAT = re.compile("^[0-9]{4}[A-Z]{3}[0-9]{5}[0-9A-Z]{3}")
|
||||
PAN_NUMBER_FORMAT = re.compile("[A-Z]{5}[0-9]{4}[A-Z]{1}")
|
||||
|
||||
|
||||
def validate_gstin_for_india(doc, method):
|
||||
if hasattr(doc, 'gst_state') and doc.gst_state:
|
||||
doc.gst_state_number = state_numbers[doc.gst_state]
|
||||
@ -37,12 +44,10 @@ def validate_gstin_for_india(doc, method):
|
||||
frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters."))
|
||||
|
||||
if gst_category and gst_category == 'UIN Holders':
|
||||
p = re.compile("^[0-9]{4}[A-Z]{3}[0-9]{5}[0-9A-Z]{3}")
|
||||
if not p.match(doc.gstin):
|
||||
if not GSTIN_UIN_FORMAT.match(doc.gstin):
|
||||
frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"))
|
||||
else:
|
||||
p = re.compile("^[0-9]{2}[A-Z]{4}[0-9A-Z]{1}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[1-9A-Z]{1}[0-9A-Z]{1}$")
|
||||
if not p.match(doc.gstin):
|
||||
if not GSTIN_FORMAT.match(doc.gstin):
|
||||
frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN."))
|
||||
|
||||
validate_gstin_check_digit(doc.gstin)
|
||||
@ -59,8 +64,7 @@ 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):
|
||||
if not PAN_NUMBER_FORMAT.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):
|
||||
@ -148,6 +152,20 @@ def get_itemised_tax_breakup_data(doc, account_wise=False):
|
||||
def set_place_of_supply(doc, method=None):
|
||||
doc.place_of_supply = get_place_of_supply(doc, doc.doctype)
|
||||
|
||||
def validate_document_name(doc, method=None):
|
||||
"""Validate GST invoice number requirements."""
|
||||
country = frappe.get_cached_value("Company", doc.company, "country")
|
||||
|
||||
# Date was chosen as start of next FY to avoid irritating current users.
|
||||
if country != "India" or getdate(doc.posting_date) < getdate("2021-04-01"):
|
||||
return
|
||||
|
||||
if len(doc.name) > 16:
|
||||
frappe.throw(_("Maximum length of document number should be 16 characters as per GST rules. Please change the naming series."))
|
||||
|
||||
if not GST_INVOICE_NUMBER_FORMAT.match(doc.name):
|
||||
frappe.throw(_("Document name should only contain alphanumeric values, dash(-) and slash(/) characters as per GST rules. Please change the naming series."))
|
||||
|
||||
# don't remove this function it is used in tests
|
||||
def test_method():
|
||||
'''test function'''
|
||||
@ -800,4 +818,4 @@ def get_regional_round_off_accounts(company, account_list):
|
||||
|
||||
account_list.extend(gst_account_list)
|
||||
|
||||
return account_list
|
||||
return account_list
|
||||
|
@ -140,7 +140,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 12:12:56.784014",
|
||||
"modified": "2021-03-02 17:35:53.603607",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
@ -157,5 +157,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
@ -190,7 +190,7 @@
|
||||
"idx": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-11 18:48:30.433058",
|
||||
"modified": "2021-03-02 17:34:57.642565",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Shopping Cart",
|
||||
"name": "Shopping Cart Settings",
|
||||
@ -207,5 +207,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
"sort_order": "ASC",
|
||||
"track_changes": 1
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user