diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 3ed37046b08..a76090d609d 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '12.18.0' +__version__ = '12.19.0' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js index 19fadbf6ded..cd727b1daaf 100644 --- a/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js +++ b/erpnext/accounts/doctype/bank_reconciliation/bank_reconciliation.js @@ -21,6 +21,14 @@ frappe.ui.form.on("Bank Reconciliation", { }; }); + frm.set_query("bank_account", function() { + return { + "filters": { + "is_company_account": 1 + } + }; + }); + frm.set_value("from_date", frappe.datetime.month_start()); frm.set_value("to_date", frappe.datetime.month_end()); }, diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 4d19a7c4537..709dccd2230 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -207,11 +207,11 @@ class JournalEntry(AccountsController): if d.reference_type=="Journal Entry": account_root_type = frappe.db.get_value("Account", d.account, "root_type") if account_root_type == "Asset" and flt(d.debit) > 0: - frappe.throw(_("For {0}, only credit accounts can be linked against another debit entry") - .format(d.account)) + frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets credited") + .format(d.idx, d.account)) elif account_root_type == "Liability" and flt(d.credit) > 0: - frappe.throw(_("For {0}, only debit accounts can be linked against another credit entry") - .format(d.account)) + frappe.throw(_("Row #{0}: For {1}, you can select reference document only if account gets debited") + .format(d.idx, d.account)) if d.reference_name == self.name: frappe.throw(_("You can not enter current voucher in 'Against Journal Entry' column")) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index acfc660c4f7..07e688dd45d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -531,7 +531,8 @@ "fieldtype": "Data", "hidden": 1, "label": "Title", - "print_hide": 1 + "print_hide": 1, + "read_only": 1 }, { "depends_on": "party", @@ -575,7 +576,7 @@ } ], "is_submittable": 1, - "modified": "2019-11-06 12:59:43.151721", + "modified": "2021-03-10 13:05:16.958866", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", @@ -619,4 +620,4 @@ "sort_order": "DESC", "title_field": "title", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index d1630f3ea69..73a946bd066 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -441,6 +441,10 @@ class PaymentEntry(AccountsController): .format(total_negative_outstanding), InvalidPaymentEntry) def set_title(self): + if frappe.flags.in_import and self.title: + # do not set title dynamically if title exists during data import. + return + if self.payment_type in ("Receive", "Pay"): self.title = self.party else: diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 1fb6dd38bb6..646aec13559 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -131,7 +131,7 @@ class PricingRule(Document): for d in self.items: max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount") if max_discount and flt(self.discount_percentage) > flt(max_discount): - throw(_("Max discount allowed for item: {0} is {1}%").format(self.item_code, max_discount)) + throw(_("Max discount allowed for item: {0} is {1}%").format(d.item_code, max_discount)) def validate_price_list_with_currency(self): if self.currency and self.for_price_list: diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py index 86504ba47fd..8d265080747 100644 --- a/erpnext/accounts/general_ledger.py +++ b/erpnext/accounts/general_ledger.py @@ -252,7 +252,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): if not round_off_gle: for k in ["voucher_type", "voucher_no", "company", - "posting_date", "remarks", "is_opening"]: + "posting_date", "remarks"]: round_off_gle[k] = gl_map[0][k] round_off_gle.update({ @@ -264,6 +264,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision): "cost_center": round_off_cost_center, "party_type": None, "party": None, + "is_opening": "No", "against_voucher_type": None, "against_voucher": None }) diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py index e15ae5cba9d..35c25109b73 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -224,8 +224,7 @@ def get_company_currency(filters=None): def calculate_values(accounts_by_name, gl_entries_by_account, companies, fiscal_year, filters): for entries in gl_entries_by_account.values(): for entry in entries: - key = entry.account_number or entry.account_name - d = accounts_by_name.get(key) + d = accounts_by_name.get(entry.account_name) if d: for company in companies: # check if posting date is within the period @@ -240,7 +239,8 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies): """accumulate children's values in parent accounts""" for d in reversed(accounts): if d.parent_account: - account = d.parent_account.split('-')[0].strip() + account = d.parent_account_name + if not accounts_by_name.get(account): continue @@ -251,16 +251,34 @@ def accumulate_values_into_parents(accounts, accounts_by_name, companies): accounts_by_name[account]["opening_balance"] = \ accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0) + def get_account_heads(root_type, companies, filters): accounts = get_accounts(root_type, filters) if not accounts: return None, None + accounts = update_parent_account_names(accounts) + accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) return accounts, accounts_by_name +def update_parent_account_names(accounts): + """Update parent_account_name in accounts list. + + parent_name is `name` of parent account which could have other prefix + of account_number and suffix of company abbr. This function adds key called + `parent_account_name` which does not have such prefix/suffix. + """ + name_to_account_map = { d.name : d.account_name for d in accounts } + + for account in accounts: + if account.parent_account: + account["parent_account_name"] = name_to_account_map[account.parent_account] + + return accounts + def get_companies(filters): companies = {} all_companies = get_subsidiary_companies(filters.get('company')) @@ -367,9 +385,9 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g convert_to_presentation_currency(gl_entries, currency_info) for entry in gl_entries: - key = entry.account_number or entry.account_name - validate_entries(key, entry, accounts_by_name, accounts) - gl_entries_by_account.setdefault(key, []).append(entry) + account_name = entry.account_name + validate_entries(account_name, entry, accounts_by_name, accounts) + gl_entries_by_account.setdefault(account_name, []).append(entry) return gl_entries_by_account @@ -438,8 +456,7 @@ def filter_accounts(accounts, depth=10): parent_children_map = {} accounts_by_name = {} for d in accounts: - key = d.account_number or d.account_name - accounts_by_name[key] = d + accounts_by_name[d.account_name] = d parent_children_map.setdefault(d.parent_account or None, []).append(d) filtered_accounts = [] diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index a492519591b..eb128f672aa 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -3,6 +3,7 @@ "description": "Settings for Buying Module", "doctype": "DocType", "document_type": "Other", + "engine": "InnoDB", "field_order": [ "supp_master_name", "supplier_group", @@ -92,7 +93,7 @@ "icon": "fa fa-cog", "idx": 1, "issingle": 1, - "modified": "2019-08-20 13:13:09.055189", + "modified": "2021-03-02 18:16:03.947813", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", @@ -107,5 +108,8 @@ "share": 1, "write": 1 } - ] + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index cc75a0afbe0..8309d4804f3 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -117,7 +117,7 @@ def call_mws_method(mws_method, *args, **kwargs): return response except Exception as e: delay = math.pow(4, x) * 125 - frappe.log_error(message=e, title=str(mws_method)) + frappe.log_error(message=e, title="Method {} failed".format(mws_method.__name__)) time.sleep(delay) continue diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index 122aa41f4b9..a33f653e49a 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -1,5 +1,4 @@ { - "actions": [], "creation": "2018-10-25 10:02:48.656165", "doctype": "DocType", "editable_grid": 1, @@ -69,8 +68,7 @@ } ], "issingle": 1, - "links": [], - "modified": "2020-10-29 20:24:56.916104", + "modified": "2021-03-02 18:05:50.794105", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Plaid Settings", @@ -88,5 +86,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json index 5339c99155b..d55583396ed 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json @@ -2,6 +2,7 @@ "creation": "2015-05-18 05:21:07.270859", "doctype": "DocType", "document_type": "System", + "engine": "InnoDB", "field_order": [ "status_html", "enable_shopify", @@ -258,8 +259,8 @@ } ], "issingle": 1, - "modified": "2020-05-28 12:32:11.384757", - "modified_by": "umair@erpnext.com", + "modified": "2021-03-02 18:06:00.868688", + "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "Shopify Settings", "owner": "Administrator", @@ -276,5 +277,6 @@ } ], "sort_field": "modified", - "sort_order": "DESC" -} + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 219e426d782..2d995ed0e67 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -261,6 +261,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.communication.doctype.call_log.call_log.set_caller_information", diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 6cb0e211f94..e91a160c0a8 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -175,7 +175,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2019-12-31 14:28:32.004121", + "modified": "2021-02-25 13:06:37.978785", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", @@ -192,5 +192,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 5c95e000f9c..5a1959f11db 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -1,16 +1,19 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals -import frappe, erpnext -from frappe import _ -from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today -from frappe.model.document import Document -from frappe.desk.form import assign_to +import erpnext +import frappe from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee +from frappe import _ +from frappe.desk.form import assign_to +from frappe.model.document import Document +from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate, + get_datetime, getdate, nowdate, today, unique) + class DuplicateDeclarationError(frappe.ValidationError): pass + class EmployeeBoardingController(Document): ''' Create the project and the task for the boarding process @@ -48,27 +51,38 @@ class EmployeeBoardingController(Document): continue task = frappe.get_doc({ - "doctype": "Task", - "project": self.project, - "subject": activity.activity_name + " : " + self.employee_name, - "description": activity.description, - "department": self.department, - "company": self.company, - "task_weight": activity.task_weight - }).insert(ignore_permissions=True) + "doctype": "Task", + "project": self.project, + "subject": activity.activity_name + " : " + self.employee_name, + "description": activity.description, + "department": self.department, + "company": self.company, + "task_weight": activity.task_weight + }).insert(ignore_permissions=True) activity.db_set("task", task.name) + users = [activity.user] if activity.user else [] if activity.role: - user_list = frappe.db.sql_list('''select distinct(parent) from `tabHas Role` - where parenttype='User' and role=%s''', activity.role) - users = users + user_list + user_list = frappe.db.sql_list(''' + SELECT + DISTINCT(has_role.parent) + FROM + `tabHas Role` has_role + LEFT JOIN `tabUser` user + ON has_role.parent = user.name + WHERE + has_role.parenttype = 'User' + AND user.enabled = 1 + AND has_role.role = %s + ''', activity.role) + users = unique(users + user_list) if "Administrator" in users: users.remove("Administrator") # assign the task the users if users: - self.assign_task_to_users(task, set(users)) + self.assign_task_to_users(task, users) def assign_task_to_users(self, task, users): for user in users: @@ -453,4 +467,4 @@ def get_previous_claimed_amount(employee, payroll_period, non_pro_rata=False, co }, as_dict=True) if sum_of_claimed_amount and flt(sum_of_claimed_amount[0].total_amount) > 0: total_claimed_amount = sum_of_claimed_amount[0].total_amount - return total_claimed_amount \ No newline at end of file + return total_claimed_amount diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py index c1204d36152..40b2e03c92a 100644 --- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py +++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py @@ -88,7 +88,7 @@ def get_bom_stock(filters): GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) def get_manufacturer_records(): - details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no, parent"]) + details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) manufacture_details = frappe._dict() for detail in details: dic = manufacture_details.setdefault(detail.get('parent'), {}) diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js index cafa1bbb911..c52c615e4b5 100644 --- a/erpnext/projects/doctype/project/project.js +++ b/erpnext/projects/doctype/project/project.js @@ -75,23 +75,23 @@ frappe.ui.form.on("Project", { frm.add_custom_button(__('Cancelled'), () => { frm.events.set_status(frm, 'Cancelled'); }, __('Set Status')); - } - if (frappe.model.can_read("Task")) { - frm.add_custom_button(__("Gantt Chart"), function () { - frappe.route_options = { - "project": frm.doc.name - }; - frappe.set_route("List", "Task", "Gantt"); - }); - - frm.add_custom_button(__("Kanban Board"), () => { - frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { - project: frm.doc.project_name - }).then(() => { - frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); + if (frappe.model.can_read("Task")) { + frm.add_custom_button(__("Gantt Chart"), function () { + frappe.route_options = { + "project": frm.doc.name + }; + frappe.set_route("List", "Task", "Gantt"); }); - }); + + frm.add_custom_button(__("Kanban Board"), () => { + frappe.call('erpnext.projects.doctype.project.project.create_kanban_board_if_not_exists', { + project: frm.doc.project_name + }).then(() => { + frappe.set_route('List', 'Task', 'Kanban', frm.doc.project_name); + }); + }); + } } }, diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 4547fc97a31..e8df13023c6 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -507,6 +507,7 @@ erpnext.utils.update_child_items = function(opts) { }, { fieldtype:'Currency', fieldname:"rate", + options: "currency", default: 0, read_only: 0, in_list_view: 1, diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py index 3fc3d401f9c..305f411cae1 100644 --- a/erpnext/regional/india/e_invoice/utils.py +++ b/erpnext/regional/india/e_invoice/utils.py @@ -160,7 +160,7 @@ def get_item_list(invoice): 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 @@ -196,9 +196,11 @@ def update_item_taxes(invoice, item): item[attr] = 0 for t in invoice.taxes: - # this contains item wise tax rate & tax amount (incl. discount) - item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) - if t.account_head in gst_accounts_list: + is_applicable = t.tax_amount and t.account_head in gst_accounts_list + if is_applicable: + # this contains item wise tax rate & tax amount (incl. discount) + item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) + item_tax_rate = item_tax_detail[0] # item tax amount excluding discount amount item_tax_amount = (item_tax_rate / 100) * item.base_net_amount @@ -223,7 +225,7 @@ def get_invoice_value_details(invoice): if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount: invoice_value_details.base_total = abs(invoice.base_total) - invoice_value_details.invoice_discount_amt = invoice.base_discount_amount + invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount) else: invoice_value_details.base_total = abs(invoice.base_net_total) # since tax already considers discount amount diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 89677433392..a808048d17d 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -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, calculate_outstanding_amount from erpnext.controllers.accounts_controller import get_taxes_and_charges @@ -15,6 +15,13 @@ from erpnext.accounts.utils import get_account_currency from frappe.contacts.doctype.address.address import get_address_display 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] @@ -38,12 +45,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) @@ -162,6 +167,20 @@ def set_transporter_address(doc, method=None): doc.transporter_address = transporter_address doc.transporter_address_display = get_address_display(transporter_address) +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''' @@ -715,25 +734,12 @@ def update_grand_total_for_rcm(doc, method): if country != 'India': return - if not doc.total_taxes_and_charges: + gst_tax, base_gst_tax = get_gst_tax_amount(doc) + + if not base_gst_tax: return if doc.reverse_charge == 'Y': - gst_accounts = get_gst_accounts(doc.company) - gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ - + gst_accounts.get('igst_account') - - base_gst_tax = 0 - gst_tax = 0 - - for tax in doc.get('taxes'): - if tax.category not in ("Total", "Valuation and Total"): - continue - - if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: - base_gst_tax += tax.base_tax_amount_after_discount_amount - gst_tax += tax.tax_amount_after_discount_amount - doc.taxes_and_charges_added -= gst_tax doc.total_taxes_and_charges -= gst_tax doc.base_taxes_and_charges_added -= base_gst_tax @@ -765,6 +771,11 @@ def make_regional_gl_entries(gl_entries, doc): if country != 'India': return gl_entries + gst_tax, base_gst_tax = get_gst_tax_amount(doc) + + if not base_gst_tax: + return gl_entries + if doc.reverse_charge == 'Y': gst_accounts = get_gst_accounts(doc.company) gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \ @@ -792,3 +803,21 @@ def make_regional_gl_entries(gl_entries, doc): ) return gl_entries + +def get_gst_tax_amount(doc): + gst_accounts = get_gst_accounts(doc.company) + gst_account_list = gst_accounts.get('cgst_account', []) + gst_accounts.get('sgst_account', []) \ + + gst_accounts.get('igst_account', []) + + base_gst_tax = 0 + gst_tax = 0 + + for tax in doc.get('taxes'): + if tax.category not in ("Total", "Valuation and Total"): + continue + + if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list: + base_gst_tax += tax.base_tax_amount_after_discount_amount + gst_tax += tax.tax_amount_after_discount_amount + + return gst_tax, base_gst_tax diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 61c41bbddf4..96925f90fef 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -236,6 +236,7 @@ class Gstr1Report(object): self.cgst_sgst_invoices = [] unidentified_gst_accounts = [] + unidentified_gst_accounts_invoice = [] for parent, account, item_wise_tax_detail, tax_amount in self.tax_details: if account in self.gst_accounts.cess_account: self.invoice_cess.setdefault(parent, tax_amount) @@ -251,6 +252,7 @@ class Gstr1Report(object): if not (cgst_or_sgst or account in self.gst_accounts.igst_account): if "gst" in account.lower() and account not in unidentified_gst_accounts: unidentified_gst_accounts.append(account) + unidentified_gst_accounts_invoice.append(parent) continue for item_code, tax_amounts in item_wise_tax_detail.items(): @@ -273,7 +275,7 @@ class Gstr1Report(object): # Build itemised tax for export invoices where tax table is blank for invoice, items in iteritems(self.invoice_items): - if invoice not in self.items_based_on_tax_rate \ + if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \ and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax": self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys()) diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json index dc2c4ce4a96..9541684a76c 100644 --- a/erpnext/selling/doctype/selling_settings/selling_settings.json +++ b/erpnext/selling/doctype/selling_settings/selling_settings.json @@ -573,7 +573,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-06-25 12:56:16.332039", + "modified": "2021-03-02 17:35:14.084342", "modified_by": "Administrator", "module": "Selling", "name": "Selling Settings", @@ -605,7 +605,7 @@ "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 0, + "track_changes": 1, "track_seen": 0, "track_views": 0 } diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py index 15b6d522e1d..d6155de80a9 100644 --- a/erpnext/setup/doctype/company/delete_company_transactions.py +++ b/erpnext/setup/doctype/company/delete_company_transactions.py @@ -26,8 +26,9 @@ def delete_company_transactions(company_name): tabDocField where fieldtype='Link' and options='Company'"""): if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", "Party Account", "Employee", "Sales Taxes and Charges Template", - "Purchase Taxes and Charges Template", "POS Profile", 'BOM', - "Item default", "Customer", "Supplier"): + "Purchase Taxes and Charges Template", "POS Profile", "BOM", + "Company", "Bank Account", "Item Tax Template", "Mode Of Payment", + "Item Default", "Customer", "Supplier", "GST Account"): delete_for_doctype(doctype, company_name) # reset company values diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.js b/erpnext/setup/doctype/global_defaults/global_defaults.js index 552331aac89..942dd5989ea 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.js +++ b/erpnext/setup/doctype/global_defaults/global_defaults.js @@ -17,7 +17,7 @@ frappe.ui.form.on('Global Defaults', { method: "frappe.client.get_list", args: { doctype: "UOM Conversion Factor", - filters: { "category": "Length" }, + filters: { "category": __("Length") }, fields: ["to_uom"], limit_page_length: 500 }, diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json index e22af9a601d..c19c4ddd7b8 100644 --- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json +++ b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json @@ -161,7 +161,7 @@ "icon": "fa fa-shopping-cart", "idx": 1, "issingle": 1, - "modified": "2020-07-07 02:13:23.175604", + "modified": "2021-03-02 18:05:56.721122", "modified_by": "Administrator", "module": "Shopping Cart", "name": "Shopping Cart Settings", @@ -178,5 +178,6 @@ } ], "sort_field": "modified", - "sort_order": "ASC" + "sort_order": "ASC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js index 1bfa2cf56c6..97f5494c5f3 100644 --- a/erpnext/stock/dashboard/item_dashboard.js +++ b/erpnext/stock/dashboard/item_dashboard.js @@ -156,7 +156,8 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb fieldtype: 'Float', description: __('Available {0}', [actual_qty]) }, {fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 }, ], - }) + }); + var submitted = false; dialog.show(); dialog.get_field('item_code').set_input(item); @@ -180,6 +181,7 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb } dialog.set_primary_action(__('Submit'), function() { + if(submitted) return; var values = dialog.get_values(); if(!values) { return; @@ -192,6 +194,7 @@ erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callb frappe.msgprint(__('Source and target warehouse must be different')); } + submitted = true; frappe.call({ method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry', args: values, diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index fe47e669261..6f454d5cb8b 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -177,7 +177,7 @@ class Item(WebsiteGenerator): if not self.valuation_rate and self.standard_rate: self.valuation_rate = self.standard_rate - if not self.valuation_rate: + if not self.valuation_rate and not self.is_customer_provided_item: frappe.throw(_("Valuation Rate is mandatory if Opening Stock entered")) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 145562cdc1b..8ea98cba6f6 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -196,7 +196,7 @@ def get_item_warehouse_map(filters, sle): else: qty_diff = flt(d.actual_qty) - value_diff = flt(d.stock_value) - flt(qty_dict.bal_val) + value_diff = flt(d.stock_value_difference) if d.posting_date < from_date: qty_dict.opening_qty += qty_diff diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index bef7450b7ef..1fc9f202fed 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -115,10 +115,17 @@ def get_serial_nos_data_after_transactions(args): order by posting_date, posting_time asc """, args, as_dict=1) for d in data: - if d.actual_qty > 0: - serial_nos.extend(get_serial_nos_data(d.serial_no)) - else: - serial_nos = list(set(serial_nos) - set(get_serial_nos_data(d.serial_no))) + for sn in get_serial_nos_data(d.serial_no): + if d.actual_qty > 0: + if sn not in serial_nos: + serial_nos.append(sn) + else: + serial_nos.remove(sn) + elif d.actual_qty < 0: + if sn in serial_nos: + serial_nos.remove(sn) + else: + serial_nos.append(sn) return '\n'.join(serial_nos) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index bad40cc37fc..abb3c1b1110 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -39,8 +39,8 @@ frappe.ui.form.on("Issue", { refresh: function (frm) { - if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") { - if (frm.doc.service_level_agreement) { + if (frm.doc.status !== "Closed") { + if (frm.doc.service_level_agreement && frm.doc.agreement_fulfilled === "Ongoing") { set_time_to_resolve_and_response(frm); }