diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 2f86c6c1de2..335e8a15ab0 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -301,17 +301,21 @@ def process_deferred_accounting(posting_date=None): start_date = add_months(today(), -1) end_date = add_days(today(), -1) - for record_type in ('Income', 'Expense'): - doc = frappe.get_doc(dict( - doctype='Process Deferred Accounting', - posting_date=posting_date, - start_date=start_date, - end_date=end_date, - type=record_type - )) + companies = frappe.get_all('Company') - doc.insert() - doc.submit() + for company in companies: + for record_type in ('Income', 'Expense'): + doc = frappe.get_doc(dict( + doctype='Process Deferred Accounting', + company=company.name, + posting_date=posting_date, + start_date=start_date, + end_date=end_date, + type=record_type + )) + + doc.insert() + doc.submit() def make_gl_entries(doc, credit_account, debit_account, against, amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None): diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py index 5f110e2727c..ffc9d1c4658 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py @@ -51,7 +51,7 @@ class BankStatementImport(DataImport): self.import_file, self.google_sheets_url ) - if 'Bank Account' not in json.dumps(preview): + if 'Bank Account' not in json.dumps(preview['columns']): frappe.throw(_("Please add the Bank Account column")) from frappe.core.page.background_jobs.background_jobs import get_info diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py index 2f5d36c8fab..311745d3cd8 100644 --- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py @@ -1010,21 +1010,21 @@ class TestPurchaseInvoice(unittest.TestCase): # Check GLE for Purchase Invoice # Zero net effect on final TDS Payable on invoice expected_gle = [ - ['_Test Account Cost for Goods Sold - _TC', 30000, 0], - ['_Test Account Excise Duty - _TC', 0, 3000], - ['Creditors - _TC', 0, 27000], - ['TDS Payable - _TC', 3000, 3000] + ['_Test Account Cost for Goods Sold - _TC', 30000], + ['_Test Account Excise Duty - _TC', -3000], + ['Creditors - _TC', -27000], + ['TDS Payable - _TC', 0] ] - gl_entries = frappe.db.sql("""select account, debit, credit + gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s + group by account order by account asc""", (purchase_invoice.name), as_dict=1) for i, gle in enumerate(gl_entries): self.assertEqual(expected_gle[i][0], gle.account) - self.assertEqual(expected_gle[i][1], gle.debit) - self.assertEqual(expected_gle[i][2], gle.credit) + self.assertEqual(expected_gle[i][1], gle.amount) def update_tax_witholding_category(company, account, date): from erpnext.accounts.utils import get_fiscal_year diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 114b7d2d352..dbc7f8632fc 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1957,6 +1957,33 @@ class TestSalesInvoice(unittest.TestCase): einvoice = make_einvoice(si) validate_totals(einvoice) + def test_item_tax_net_range(self): + item = create_item("T Shirt") + + item.set('taxes', []) + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", + "minimum_net_rate": 0, + "maximum_net_rate": 500 + }) + + item.append("taxes", { + "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", + "minimum_net_rate": 501, + "maximum_net_rate": 1000 + }) + + item.save() + + sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") + + # Apply discount + sales_invoice.apply_discount_on = 'Net Total' + sales_invoice.discount_amount = 300 + sales_invoice.save() + self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") + def get_sales_invoice_for_e_invoice(): si = make_sales_invoice_for_ewaybill() si.naming_series = 'INV-2020-.#####' @@ -1985,32 +2012,6 @@ def get_sales_invoice_for_e_invoice(): return si - def test_item_tax_net_range(self): - item = create_item("T Shirt") - - item.set('taxes', []) - item.append("taxes", { - "item_tax_template": "_Test Account Excise Duty @ 10 - _TC", - "minimum_net_rate": 0, - "maximum_net_rate": 500 - }) - - item.append("taxes", { - "item_tax_template": "_Test Account Excise Duty @ 12 - _TC", - "minimum_net_rate": 501, - "maximum_net_rate": 1000 - }) - - item.save() - - sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True) - self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC") - - # Apply discount - sales_invoice.apply_discount_on = 'Net Total' - sales_invoice.discount_amount = 300 - sales_invoice.save() - self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") def make_test_address_for_ewaybill(): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): @@ -2087,9 +2088,9 @@ def make_sales_invoice_for_ewaybill(): if not gst_account: gst_settings.append("gst_accounts", { "company": "_Test Company", - "cgst_account": "CGST - _TC", - "sgst_account": "SGST - _TC", - "igst_account": "IGST - _TC", + "cgst_account": "Output Tax CGST - _TC", + "sgst_account": "Output Tax SGST - _TC", + "igst_account": "Output Tax IGST - _TC", }) gst_settings.save() @@ -2106,7 +2107,7 @@ def make_sales_invoice_for_ewaybill(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "CGST - _TC", + "account_head": "Output Tax CGST - _TC", "cost_center": "Main - _TC", "description": "CGST @ 9.0", "rate": 9 @@ -2114,7 +2115,7 @@ def make_sales_invoice_for_ewaybill(): si.append("taxes", { "charge_type": "On Net Total", - "account_head": "SGST - _TC", + "account_head": "Output Tax SGST - _TC", "cost_center": "Main - _TC", "description": "SGST @ 9.0", "rate": 9 diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.js b/erpnext/accounts/doctype/tax_rule/tax_rule.js index 370890e4d8e..bc497163e8b 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.js +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.js @@ -1,24 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // License: GNU General Public License v3. See license.txt -cur_frm.add_fetch("customer", "customer_group", "customer_group" ); -cur_frm.add_fetch("supplier", "supplier_group_name", "supplier_group" ); - -frappe.ui.form.on("Tax Rule", "tax_type", function(frm) { - frm.toggle_reqd("sales_tax_template", frm.doc.tax_type=="Sales"); - frm.toggle_reqd("purchase_tax_template", frm.doc.tax_type=="Purchase"); -}) - -frappe.ui.form.on("Tax Rule", "onload", function(frm) { - if(frm.doc.__islocal) { - frm.set_value("use_for_shopping_cart", 1); - } -}) - -frappe.ui.form.on("Tax Rule", "refresh", function(frm) { - frappe.ui.form.trigger("Tax Rule", "tax_type"); -}) - frappe.ui.form.on("Tax Rule", "customer", function(frm) { if(frm.doc.customer) { frappe.call({ diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.json b/erpnext/accounts/doctype/tax_rule/tax_rule.json index ef155381c0b..27467484329 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.json +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.json @@ -1,1103 +1,250 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "ACC-TAX-RULE-.YYYY.-.#####", - "beta": 0, - "creation": "2015-08-07 02:33:52.670866", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "actions": [], + "allow_import": 1, + "autoname": "ACC-TAX-RULE-.YYYY.-.#####", + "creation": "2015-08-07 02:33:52.670866", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "tax_type", + "use_for_shopping_cart", + "column_break_1", + "sales_tax_template", + "purchase_tax_template", + "filters", + "customer", + "supplier", + "item", + "billing_city", + "billing_county", + "billing_state", + "billing_zipcode", + "billing_country", + "tax_category", + "column_break_2", + "customer_group", + "supplier_group", + "item_group", + "shipping_city", + "shipping_county", + "shipping_state", + "shipping_zipcode", + "shipping_country", + "section_break_4", + "from_date", + "column_break_7", + "to_date", + "section_break_6", + "priority", + "column_break_20", + "company" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Sales", - "fieldname": "tax_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Tax Type", - "length": 0, - "no_copy": 0, - "options": "Sales\nPurchase", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "Sales", + "fieldname": "tax_type", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Tax Type", + "options": "Sales\nPurchase" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "use_for_shopping_cart", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Use for Shopping Cart", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "fieldname": "use_for_shopping_cart", + "fieldtype": "Check", + "label": "Use for Shopping Cart" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "sales_tax_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Tax Template", - "length": 0, - "no_copy": 0, - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fieldname": "sales_tax_template", + "fieldtype": "Link", + "label": "Sales Tax Template", + "options": "Sales Taxes and Charges Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "purchase_tax_template", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Tax Template", - "length": 0, - "no_copy": 0, - "options": "Purchase Taxes and Charges Template", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fieldname": "purchase_tax_template", + "fieldtype": "Link", + "label": "Purchase Tax Template", + "options": "Purchase Taxes and Charges Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "filters", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Filters", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "filters", + "fieldtype": "Section Break", + "label": "Filters" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "customer", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer", - "length": 0, - "no_copy": 0, - "options": "Customer", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fieldname": "customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Supplier", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fieldname": "supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item", + "fieldtype": "Link", + "label": "Item", + "options": "Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_city", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing City", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_city", + "fieldtype": "Data", + "label": "Billing City" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_county", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing County", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_county", + "fieldtype": "Data", + "label": "Billing County" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_state", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing State", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_state", + "fieldtype": "Data", + "label": "Billing State" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_zipcode", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing Zipcode", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_zipcode", + "fieldtype": "Data", + "label": "Billing Zipcode" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "billing_country", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing Country", - "length": 0, - "no_copy": 0, - "options": "Country", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "billing_country", + "fieldtype": "Link", + "label": "Billing Country", + "options": "Country" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_category", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_2", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Sales\"", - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Sales\"", + "fetch_from": "customer.customer_group", + "fieldname": "customer_group", + "fieldtype": "Link", + "label": "Customer Group", + "options": "Customer Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Supplier Group", - "length": 0, - "no_copy": 0, - "options": "Supplier Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:doc.tax_type==\"Purchase\"", + "fetch_from": "supplier.supplier_group", + "fieldname": "supplier_group", + "fieldtype": "Link", + "label": "Supplier Group", + "options": "Supplier Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_group", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Item Group", - "length": 0, - "no_copy": 0, - "options": "Item Group", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "item_group", + "fieldtype": "Link", + "label": "Item Group", + "options": "Item Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_city", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping City", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "shipping_city", + "fieldtype": "Data", + "label": "Shipping City" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_county", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping County", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "shipping_county", + "fieldtype": "Data", + "label": "Shipping County" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_state", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping State", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "shipping_state", + "fieldtype": "Data", + "label": "Shipping State" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_zipcode", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Zipcode", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "shipping_zipcode", + "fieldtype": "Data", + "label": "Shipping Zipcode" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_country", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Country", - "length": 0, - "no_copy": 0, - "options": "Country", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "shipping_country", + "fieldtype": "Link", + "label": "Shipping Country", + "options": "Country" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Validity", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "label": "Validity" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_7", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "priority", - "fieldtype": "Int", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Priority", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "fieldname": "priority", + "fieldtype": "Int", + "label": "Priority" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_20", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-12-27 01:22:17.721636", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Tax Rule", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2021-06-04 23:14:27.186879", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Tax Rule", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py index ac1ffd9e750..cf7226822ed 100644 --- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py @@ -50,7 +50,7 @@ class TestTaxRule(unittest.TestCase): tax_rule1 = make_tax_rule(customer_group= "All Customer Groups", sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01") tax_rule1.save() - self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}), + self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":1}), "_Test Sales Taxes and Charges Template - _TC") def test_conflict_with_overlapping_dates(self): diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index e025fc69054..b97dc401e6a 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None): select company, sum(debit_in_account_currency) - sum(credit_in_account_currency) from `tabGL Entry` where party_type = %s and party=%s + and is_cancelled = 0 group by company""", (party_type, party))) for d in companies: 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 7793af737f9..56a67bb0989 100644 --- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py +++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py @@ -380,7 +380,7 @@ def set_gl_entries_by_account(from_date, to_date, root_lft, root_rgt, filters, g gl_entries = frappe.db.sql("""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company, gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency, acc.account_name, acc.account_number - from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s + from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0 {additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s order by gl.account, gl.posting_date""".format(additional_conditions=additional_conditions), { diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py index 66a9b601257..ed6e28da1e6 100644 --- a/erpnext/accounts/utils.py +++ b/erpnext/accounts/utils.py @@ -784,7 +784,7 @@ def get_children(doctype, parent, company, is_root=False): return acc def create_payment_gateway_account(gateway, payment_channel="Email"): - from erpnext.setup.setup_wizard.operations.company_setup import create_bank_account + from erpnext.setup.setup_wizard.operations.install_fixtures import create_bank_account company = frappe.db.get_value("Global Defaults", None, "default_company") if not company: diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 1dbd7c60c3e..132dd1769c5 100644 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -97,6 +97,9 @@ "is_fixed_asset", "item_tax_rate", "section_break_72", + "production_plan", + "production_plan_item", + "production_plan_sub_assembly_item", "page_break" ], "fields": [ @@ -803,13 +806,37 @@ "options": "Company:company:default_currency", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "production_plan", + "fieldtype": "Link", + "label": "Production Plan", + "options": "Production Plan", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Item", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_sub_assembly_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Production Plan Sub Assembly Item", + "no_copy": 1, + "read_only": 1 } ], "idx": 1, "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-03-22 11:46:12.357435", + "modified": "2021-06-28 19:22:22.715365", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/crm/doctype/appointment/appointment.json b/erpnext/crm/doctype/appointment/appointment.json index 8517ddec321..306be7faa74 100644 --- a/erpnext/crm/doctype/appointment/appointment.json +++ b/erpnext/crm/doctype/appointment/appointment.json @@ -102,7 +102,7 @@ } ], "links": [], - "modified": "2020-01-28 16:16:45.447213", + "modified": "2021-06-29 18:27:02.832979", "modified_by": "Administrator", "module": "CRM", "name": "Appointment", @@ -153,6 +153,18 @@ "role": "Sales User", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "write": 1 } ], "quick_entry": 1, diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index d1d096843bf..ce3de40fc3d 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -168,12 +168,13 @@ class Lead(SellingController): if self.phone: contact.append("phone_nos", { "phone": self.phone, - "is_primary": 1 + "is_primary_phone": 1 }) if self.mobile_no: contact.append("phone_nos", { - "phone": self.mobile_no + "phone": self.mobile_no, + "is_primary_mobile_no":1 }) contact.insert(ignore_permissions=True) diff --git a/erpnext/education/utils.py b/erpnext/education/utils.py index 9db8a4a90df..3070e6a3e8a 100644 --- a/erpnext/education/utils.py +++ b/erpnext/education/utils.py @@ -355,11 +355,11 @@ def get_or_create_course_enrollment(course, program): student = get_current_student() course_enrollment = get_enrollment("course", course, student.name) if not course_enrollment: - program_enrollment = get_enrollment('program', program, student.name) + program_enrollment = get_enrollment('program', program.name, student.name) if not program_enrollment: frappe.throw(_("You are not enrolled in program {0}").format(program)) return - return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program, student.name)) + return student.enroll_in_course(course_name=course, program_enrollment=get_enrollment('program', program.name, student.name)) else: return frappe.get_doc('Course Enrollment', course_enrollment) diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py index 3c2e59ab821..b0e662d3f32 100644 --- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py +++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py @@ -7,16 +7,21 @@ import frappe import unittest from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import process_balance_info, verify_transaction from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice +from erpnext.erpnext_integrations.utils import create_mode_of_payment class TestMpesaSettings(unittest.TestCase): + def setUp(self): + # create payment gateway in setup + create_mpesa_settings(payment_gateway_name="_Test") + create_mpesa_settings(payment_gateway_name="_Account Balance") + create_mpesa_settings(payment_gateway_name="Payment") + def tearDown(self): frappe.db.sql('delete from `tabMpesa Settings`') frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"') def test_creation_of_payment_gateway(self): - create_mpesa_settings(payment_gateway_name="_Test") - - mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test") + mode_of_payment = create_mode_of_payment('Mpesa-_Test', payment_type="Phone") self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"})) self.assertTrue(mode_of_payment.name) self.assertEqual(mode_of_payment.type, "Phone") @@ -47,7 +52,6 @@ class TestMpesaSettings(unittest.TestCase): integration_request.delete() def test_processing_of_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES") @@ -90,7 +94,6 @@ class TestMpesaSettings(unittest.TestCase): pos_invoice.delete() def test_processing_of_multiple_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") @@ -141,7 +144,6 @@ class TestMpesaSettings(unittest.TestCase): pos_invoice.delete() def test_processing_of_only_one_succes_callback_payload(self): - create_mpesa_settings(payment_gateway_name="Payment") mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account") frappe.db.set_value("Account", mpesa_account, "account_currency", "KES") frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500") @@ -202,6 +204,7 @@ def create_mpesa_settings(payment_gateway_name="Express"): doc = frappe.get_doc(dict( #nosec doctype="Mpesa Settings", + sandbox=1, payment_gateway_name=payment_gateway_name, consumer_key="5sMu9LVI1oS3oBGPJfh3JyvLHwZOdTKn", consumer_secret="VI1oS3oBGPJfh3JyvLHw", diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index 3840e781b4c..a5e162f8b5d 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -52,7 +52,8 @@ def create_mode_of_payment(gateway, payment_type="General"): "payment_gateway": gateway }, ['payment_account']) - if not frappe.db.exists("Mode of Payment", gateway) and payment_gateway_account: + mode_of_payment = frappe.db.exists("Mode of Payment", gateway) + if not mode_of_payment and payment_gateway_account: mode_of_payment = frappe.get_doc({ "doctype": "Mode of Payment", "mode_of_payment": gateway, @@ -66,6 +67,10 @@ def create_mode_of_payment(gateway, payment_type="General"): }) mode_of_payment.insert(ignore_permissions=True) + return mode_of_payment + elif mode_of_payment: + return frappe.get_doc("Mode of Payment", mode_of_payment) + def get_tracking_url(carrier, tracking_number): # Return the formatted Tracking URL. tracking_url = '' diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 291df74e3dc..e5f272d769e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -157,6 +157,7 @@ website_route_rules = [ "parents": [{"label": _("Material Request"), "route": "material-requests"}] } }, + {"from_route": "/project", "to_route": "Project"} ] standard_portal_menu_items = [ diff --git a/erpnext/hr/doctype/attendance/attendance.js b/erpnext/hr/doctype/attendance/attendance.js index c3c3cb82f94..7964078c7f0 100644 --- a/erpnext/hr/doctype/attendance/attendance.js +++ b/erpnext/hr/doctype/attendance/attendance.js @@ -11,5 +11,5 @@ cur_frm.cscript.onload = function(doc, cdt, cdn) { cur_frm.fields_dict.employee.get_query = function(doc,cdt,cdn) { return{ query: "erpnext.controllers.queries.employee_query" - } + } } diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index f3b8a799b3c..3412675d811 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -15,6 +15,7 @@ class Attendance(Document): validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"]) self.validate_attendance_date() self.validate_duplicate_record() + self.validate_employee_status() self.check_leave_record() def validate_attendance_date(self): @@ -38,6 +39,10 @@ class Attendance(Document): frappe.throw(_("Attendance for employee {0} is already marked for the date {1}").format( frappe.bold(self.employee), frappe.bold(self.attendance_date))) + def validate_employee_status(self): + if frappe.db.get_value("Employee", self.employee, "status") == "Inactive": + frappe.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee)) + def check_leave_record(self): leave_record = frappe.db.sql(""" select leave_type, half_day, half_day_date diff --git a/erpnext/hr/doctype/attendance/attendance_list.js b/erpnext/hr/doctype/attendance/attendance_list.js index 0c7eafe9c61..9a3bac0eb23 100644 --- a/erpnext/hr/doctype/attendance/attendance_list.js +++ b/erpnext/hr/doctype/attendance/attendance_list.js @@ -21,6 +21,9 @@ frappe.listview_settings['Attendance'] = { label: __('For Employee'), fieldtype: 'Link', options: 'Employee', + get_query: () => { + return {query: "erpnext.controllers.queries.employee_query"} + }, reqd: 1, onchange: function() { dialog.set_df_property("unmarked_days", "hidden", 1); diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 578eccf787d..96ea686706c 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -72,7 +72,8 @@ class TestExpenseClaim(unittest.TestCase): def test_expense_claim_gl_entry(self): payable_account = get_payable_account(company_name) taxes = generate_taxes() - expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", do_not_submit=True, taxes=taxes) + expense_claim = make_expense_claim(payable_account, 300, 200, company_name, "Travel Expenses - _TC4", + do_not_submit=True, taxes=taxes) expense_claim.submit() gl_entries = frappe.db.sql("""select account, debit, credit @@ -82,7 +83,7 @@ class TestExpenseClaim(unittest.TestCase): self.assertTrue(gl_entries) expected_values = dict((d[0], d) for d in [ - ['CGST - _TC4',18.0, 0.0], + ['Output Tax CGST - _TC4',18.0, 0.0], [payable_account, 0.0, 218.0], ["Travel Expenses - _TC4", 200.0, 0.0] ]) @@ -145,7 +146,7 @@ def generate_taxes(): parent_account = frappe.db.get_value('Account', {'company': company_name, 'is_group':1, 'account_type': 'Tax'}, 'name') - account = create_account(company=company_name, account_name="CGST", account_type="Tax", parent_account=parent_account) + account = create_account(company=company_name, account_name="Output Tax CGST", account_type="Tax", parent_account=parent_account) return {'taxes':[{ "account_head": account, "rate": 0, diff --git a/erpnext/hr/doctype/training_event/training_event.js b/erpnext/hr/doctype/training_event/training_event.js index b7d34b178a0..064dfb24557 100644 --- a/erpnext/hr/doctype/training_event/training_event.js +++ b/erpnext/hr/doctype/training_event/training_event.js @@ -20,11 +20,10 @@ frappe.ui.form.on('Training Event', { frappe.set_route("List", "Training Feedback"); }); } - } -}); + frm.events.set_employee_query(frm); + }, -frappe.ui.form.on("Training Event Employee", { - employee: function (frm) { + set_employee_query: function(frm) { let emp = []; for (let d in frm.doc.employees) { if (frm.doc.employees[d].employee) { @@ -40,3 +39,10 @@ frappe.ui.form.on("Training Event Employee", { }); } }); + +frappe.ui.form.on("Training Event Employee", { + employee: function(frm) { + frm.events.set_employee_query(frm); + } +}); + diff --git a/erpnext/hr/doctype/training_event_employee/training_event_employee.json b/erpnext/hr/doctype/training_event_employee/training_event_employee.json index 2d313e9faca..bcb7d5e5bc0 100644 --- a/erpnext/hr/doctype/training_event_employee/training_event_employee.json +++ b/erpnext/hr/doctype/training_event_employee/training_event_employee.json @@ -19,6 +19,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Employee", + "no_copy": 1, "options": "Employee" }, { @@ -68,7 +69,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-05-21 12:41:59.336237", + "modified": "2021-07-02 17:20:27.630176", "modified_by": "Administrator", "module": "HR", "name": "Training Event Employee", diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json index 18bd4aea78b..68bac8ed8cd 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.json @@ -35,7 +35,9 @@ "no_copy": 1, "options": "Loan Security Pledge", "print_hide": 1, - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "loan_application.applicant", @@ -45,47 +47,63 @@ "in_standard_filter": 1, "label": "Applicant", "options": "applicant_type", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_security_details_section", "fieldtype": "Section Break", - "label": "Loan Security Details" + "label": "Loan Security Details", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan", "fieldtype": "Link", "label": "Loan", - "options": "Loan" + "options": "Loan", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_application", "fieldtype": "Link", "label": "Loan Application", - "options": "Loan Application" + "options": "Loan Application", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "total_security_value", "fieldtype": "Currency", "label": "Total Security Value", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "maximum_loan_value", "fieldtype": "Currency", "label": "Maximum Loan Value", "options": "Company:company:default_currency", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "loan_details_section", "fieldtype": "Section Break", - "label": "Loan Details" + "label": "Loan Details", + "show_days": 1, + "show_seconds": 1 }, { "default": "Requested", @@ -94,37 +112,49 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Status", - "options": "Requested\nUnpledged\nPledged\nPartially Pledged", - "read_only": 1 + "options": "Requested\nUnpledged\nPledged\nPartially Pledged\nCancelled", + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "pledge_time", "fieldtype": "Datetime", "label": "Pledge Time", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "securities", "fieldtype": "Table", "label": "Securities", "options": "Pledge", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_11", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_10", "fieldtype": "Section Break", - "label": "Totals" + "label": "Totals", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "company", "fieldtype": "Link", "label": "Company", "options": "Company", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fetch_from": "loan.applicant_type", @@ -132,35 +162,45 @@ "fieldtype": "Select", "label": "Applicant Type", "options": "Employee\nMember\nCustomer", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "more_information_section", "fieldtype": "Section Break", - "label": "More Information" + "label": "More Information", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "reference_no", "fieldtype": "Data", - "label": "Reference No" + "label": "Reference No", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_18", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "allow_on_submit": 1, "fieldname": "description", "fieldtype": "Text", - "label": "Description" + "label": "Description", + "show_days": 1, + "show_seconds": 1 } ], "index_web_pages_for_search": 1, "is_submittable": 1, "links": [], - "modified": "2021-04-19 18:23:16.953305", + "modified": "2021-06-29 17:15:16.082256", "modified_by": "Administrator", "module": "Loan Management", "name": "Loan Security Pledge", diff --git a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py index cbc8376aa56..c390b6c526d 100644 --- a/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py +++ b/erpnext/loan_management/doctype/loan_security_pledge/loan_security_pledge.py @@ -23,6 +23,12 @@ class LoanSecurityPledge(Document): update_shortfall_status(self.loan, self.total_security_value) update_loan(self.loan, self.maximum_loan_value) + def on_cancel(self): + if self.loan: + self.db_set("status", "Cancelled") + self.db_set("pledge_time", None) + update_loan(self.loan, self.maximum_loan_value, cancel=1) + def validate_duplicate_securities(self): security_list = [] for security in self.securities: @@ -36,7 +42,7 @@ class LoanSecurityPledge(Document): existing_pledge = '' if self.loan: - existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan}, ['name']) + existing_pledge = frappe.db.get_value('Loan Security Pledge', {'loan': self.loan, 'docstatus': 1}, ['name']) if existing_pledge: loan_security_type = frappe.db.get_value('Pledge', {'parent': existing_pledge}, ['loan_security_type']) @@ -77,8 +83,12 @@ class LoanSecurityPledge(Document): self.total_security_value = total_security_value self.maximum_loan_value = maximum_loan_value -def update_loan(loan, maximum_value_against_pledge): +def update_loan(loan, maximum_value_against_pledge, cancel=0): maximum_loan_value = frappe.db.get_value('Loan', {'name': loan}, ['maximum_loan_amount']) - frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 - WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) + if cancel: + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s + WHERE name=%s""", (maximum_loan_value - maximum_value_against_pledge, loan)) + else: + frappe.db.sql(""" UPDATE `tabLoan` SET maximum_loan_amount=%s, is_secured_loan=1 + WHERE name=%s""", (maximum_loan_value + maximum_value_against_pledge, loan)) diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js index 4c31bd0b7d0..f19a1b08681 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Blanket Order', { refresh: function(frm) { erpnext.hide_company(); - if (frm.doc.customer && frm.doc.docstatus === 1) { + if (frm.doc.customer && frm.doc.docstatus === 1 && frm.doc.to_date > frappe.datetime.get_today()) { frm.add_custom_button(__("Sales Order"), function() { frappe.model.open_mapped_doc({ method: "erpnext.manufacturing.doctype.blanket_order.blanket_order.make_order", diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json index 0330e5c85c9..a63fc4da69a 100644 --- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.json +++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "naming_series:", "creation": "2018-05-24 07:18:08.256060", "doctype": "DocType", @@ -79,6 +80,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "to_date", "fieldtype": "Date", "label": "To Date", @@ -129,8 +131,10 @@ "label": "Terms and Conditions Details" } ], + "index_web_pages_for_search": 1, "is_submittable": 1, - "modified": "2019-11-18 19:37:37.151686", + "links": [], + "modified": "2021-06-29 00:30:30.621636", "modified_by": "Administrator", "module": "Manufacturing", "name": "Blanket Order", diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 27019dbbae2..15a7c316c91 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -325,8 +325,7 @@ frappe.ui.form.on("BOM", { freeze: true, args: { update_parent: true, - from_child_bom:false, - save: frm.doc.docstatus === 1 ? true : false + from_child_bom:false }, callback: function(r) { refresh_field("items"); diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index f38d1b98922..7e539183b0c 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -36,6 +36,9 @@ "materials_section", "inspection_required", "quality_inspection_template", + "column_break_31", + "bom_level", + "section_break_33", "items", "scrap_section", "scrap_items", @@ -513,6 +516,22 @@ "no_copy": 1, "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "column_break_31", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "bom_level", + "fieldtype": "Int", + "label": "BOM Level", + "read_only": 1 + }, + { + "fieldname": "section_break_33", + "fieldtype": "Section Break", + "hide_border": 1 } ], "icon": "fa fa-sitemap", @@ -520,7 +539,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2021-03-16 12:25:09.081968", + "modified": "2021-05-16 12:25:09.081968", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index c58f017258e..c32a8a95a17 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -154,6 +154,7 @@ class BOM(WebsiteGenerator): self.calculate_cost() self.update_stock_qty() self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False) + self.set_bom_level() def get_context(self, context): context.parents = [{'name': 'boms', 'title': _('All BOMs') }] @@ -676,6 +677,19 @@ class BOM(WebsiteGenerator): """Get a complete tree representation preserving order of child items.""" return BOMTree(self.name) + def set_bom_level(self, update=False): + levels = [] + + self.bom_level = 0 + for row in self.items: + if row.bom_no: + levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0) + + if levels: + self.bom_level = max(levels) + 1 + + if update: + self.db_set("bom_level", self.bom_level) def get_bom_item_rate(args, bom_doc): if bom_doc.rm_cost_as_per == 'Valuation Rate': @@ -860,7 +874,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): frappe.form_dict.parent = parent if frappe.form_dict.parent: - bom_doc = frappe.get_doc("BOM", frappe.form_dict.parent) + bom_doc = frappe.get_cached_doc("BOM", frappe.form_dict.parent) frappe.has_permission("BOM", doc=bom_doc, throw=True) bom_items = frappe.get_all('BOM Item', @@ -871,7 +885,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): item_names = tuple(d.get('item_code') for d in bom_items) items = frappe.get_list('Item', - fields=['image', 'description', 'name', 'stock_uom', 'item_name'], + fields=['image', 'description', 'name', 'stock_uom', 'item_name', 'is_sub_contracted_item'], filters=[['name', 'in', item_names]]) # to get only required item dicts for bom_item in bom_items: @@ -884,6 +898,7 @@ def get_children(doctype, parent=None, is_root=False, **filters): bom_item.parent_bom_qty = bom_doc.quantity bom_item.expandable = 0 if bom_item.value in ('', None) else 1 + bom_item.image = frappe.db.escape(bom_item.image) return bom_items @@ -1100,6 +1115,8 @@ def make_variant_bom(source_name, bom_no, item, variant_items, target_doc=None): }, 'BOM Item': { 'doctype': 'BOM Item', + # stop get_mapped_doc copying parent bom_no to children + 'field_no_map': ['bom_no'], 'condition': lambda doc: doc.has_variants == 0 }, }, target_doc, postprocess) diff --git a/erpnext/manufacturing/doctype/bom/bom_item_preview.html b/erpnext/manufacturing/doctype/bom/bom_item_preview.html index 6cd5f8cb3cf..6088e46265b 100644 --- a/erpnext/manufacturing/doctype/bom/bom_item_preview.html +++ b/erpnext/manufacturing/doctype/bom/bom_item_preview.html @@ -1,13 +1,31 @@
+ {% if data.value %} + + {{ __("Open BOM {0}", [data.value.bold()]) }} + {% endif %} + {% if data.item_code %} + + {{ __("Open Item {0}", [data.item_code.bold()]) }} + {% endif %} +
+
diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js
index 185b9ed4bcf..60fb377f476 100644
--- a/erpnext/manufacturing/doctype/bom/bom_tree.js
+++ b/erpnext/manufacturing/doctype/bom/bom_tree.js
@@ -64,7 +64,7 @@ frappe.treeview_settings["BOM"] = {
if(node.is_root && node.data.value!="BOM") {
frappe.model.with_doc("BOM", node.data.value, function() {
var bom = frappe.model.get_doc("BOM", node.data.value);
- node.data.image = bom.image || "";
+ node.data.image = escape(bom.image) || "";
node.data.description = bom.description || "";
});
}
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 7f8f2ef68d0..420bb008039 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -192,15 +192,20 @@ class JobCard(Document):
"completed_qty": args.get("completed_qty") or 0.0
})
elif args.get("start_time"):
- for name in employees:
- self.append("time_logs", {
- "from_time": get_datetime(args.get("start_time")),
- "employee": name.get('employee'),
- "operation": args.get("sub_operation"),
- "completed_qty": 0.0
- })
+ new_args = {
+ "from_time": get_datetime(args.get("start_time")),
+ "operation": args.get("sub_operation"),
+ "completed_qty": 0.0
+ }
- if not self.employee:
+ if employees:
+ for name in employees:
+ new_args.employee = name.get('employee')
+ self.add_start_time_log(new_args)
+ else:
+ self.add_start_time_log(new_args)
+
+ if not self.employee and employees:
self.set_employees(employees)
if self.status == "On Hold":
@@ -208,6 +213,9 @@ class JobCard(Document):
self.save()
+ def add_start_time_log(self, args):
+ self.append("time_logs", args)
+
def set_employees(self, employees):
for name in employees:
self.append('employee', {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 450aa04a73d..d198a6962a5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -4,7 +4,7 @@
frappe.ui.form.on('Production Plan', {
setup: function(frm) {
frm.custom_make_buttons = {
- 'Work Order': 'Work Order',
+ 'Work Order': 'Work Order / Subcontract PO',
'Material Request': 'Material Request',
};
@@ -68,17 +68,13 @@ frappe.ui.form.on('Production Plan', {
frm.trigger("show_progress");
if (frm.doc.status !== "Completed") {
- if (frm.doc.po_items && frm.doc.status !== "Closed") {
- frm.add_custom_button(__("Work Order"), ()=> {
- frm.trigger("make_work_order");
- }, __('Create'));
- }
+ frm.add_custom_button(__("Work Order Tree"), ()=> {
+ frappe.set_route('Tree', 'Work Order', {production_plan: frm.doc.name});
+ }, __('View'));
- if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
- frm.add_custom_button(__("Material Request"), ()=> {
- frm.trigger("make_material_request");
- }, __('Create'));
- }
+ frm.add_custom_button(__("Production Plan Summary"), ()=> {
+ frappe.set_route('query-report', 'Production Plan Summary', {production_plan: frm.doc.name});
+ }, __('View'));
if (frm.doc.status === "Closed") {
frm.add_custom_button(__("Re-open"), function() {
@@ -89,6 +85,18 @@ frappe.ui.form.on('Production Plan', {
frm.events.close_open_production_plan(frm, true);
}, __("Status"));
}
+
+ if (frm.doc.po_items && frm.doc.status !== "Closed") {
+ frm.add_custom_button(__("Work Order / Subcontract PO"), ()=> {
+ frm.trigger("make_work_order");
+ }, __('Create'));
+ }
+
+ if (frm.doc.mr_items && !in_list(['Material Requested', 'Closed'], frm.doc.status)) {
+ frm.add_custom_button(__("Material Request"), ()=> {
+ frm.trigger("make_material_request");
+ }, __('Create'));
+ }
}
}
@@ -233,6 +241,17 @@ frappe.ui.form.on('Production Plan', {
});
},
+ get_sub_assembly_items: function(frm) {
+ frappe.call({
+ method: "get_sub_assembly_items",
+ freeze: true,
+ doc: frm.doc,
+ callback: function() {
+ refresh_field("sub_assembly_items");
+ }
+ });
+ },
+
get_items_for_mr: function(frm) {
if (!frm.doc.for_warehouse) {
frappe.throw(__("Select warehouse for material requests"));
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 1c0dde227c5..84378956c61 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -32,6 +32,9 @@
"po_items",
"section_break_25",
"prod_plan_references",
+ "section_break_24",
+ "get_sub_assembly_items",
+ "sub_assembly_items",
"material_request_planning",
"include_non_stock_items",
"include_subcontracted_items",
@@ -187,7 +190,7 @@
"depends_on": "get_items_from",
"fieldname": "get_items",
"fieldtype": "Button",
- "label": "Get Items For Work Order"
+ "label": "Get Finished Goods for Manufacture"
},
{
"fieldname": "po_items",
@@ -199,7 +202,7 @@
{
"fieldname": "material_request_planning",
"fieldtype": "Section Break",
- "label": "Material Request Planning"
+ "label": "Material Requirement Planning"
},
{
"default": "1",
@@ -237,12 +240,13 @@
},
{
"fieldname": "section_break_27",
- "fieldtype": "Section Break"
+ "fieldtype": "Section Break",
+ "hide_border": 1
},
{
"fieldname": "mr_items",
"fieldtype": "Table",
- "label": "Material Request Plan Item",
+ "label": "Raw Materials",
"no_copy": 1,
"options": "Material Request Plan Item"
},
@@ -337,13 +341,30 @@
"hidden": 1,
"label": "Production Plan Item Reference",
"options": "Production Plan Item Reference"
+ },
+ {
+ "fieldname": "section_break_24",
+ "fieldtype": "Section Break",
+ "hide_border": 1
+ },
+ {
+ "fieldname": "sub_assembly_items",
+ "fieldtype": "Table",
+ "label": "Sub Assembly Items",
+ "no_copy": 1,
+ "options": "Production Plan Sub Assembly Item"
+ },
+ {
+ "fieldname": "get_sub_assembly_items",
+ "fieldtype": "Button",
+ "label": "Get Sub Assembly Items"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
- "modified": "2021-05-24 16:59:03.643211",
+ "modified": "2021-06-28 20:00:33.905114",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 0ede1bd4ab3..38a0ee77ad7 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -5,10 +5,11 @@
from __future__ import unicode_literals
import frappe, json, copy
from frappe import msgprint, _
-from six import string_types, iteritems
+from six import iteritems
from frappe.model.document import Document
-from frappe.utils import cstr, flt, cint, nowdate, add_days, comma_and, now_datetime, ceil
+from frappe.utils import (flt, cint, nowdate, add_days, comma_and, now_datetime,
+ ceil, get_link_to_form, getdate)
from frappe.utils.csvutils import build_csv_response
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_children
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
@@ -349,49 +350,88 @@ class ProductionPlan(Document):
@frappe.whitelist()
def make_work_order(self):
- wo_list = []
+ wo_list, po_list = [], []
+ subcontracted_po = {}
+
self.validate_data()
+ self.make_work_order_for_finished_goods(wo_list)
+ self.make_work_order_for_subassembly_items(wo_list, subcontracted_po)
+ self.make_subcontracted_purchase_order(subcontracted_po, po_list)
+ self.show_list_created_message('Work Order', wo_list)
+ self.show_list_created_message('Purchase Order', po_list)
+
+ def make_work_order_for_finished_goods(self, wo_list):
items_data = self.get_production_items()
for key, item in items_data.items():
+ if self.sub_assembly_items:
+ item['use_multi_level_bom'] = 0
+
work_order = self.create_work_order(item)
if work_order:
wo_list.append(work_order)
- if item.get("make_work_order_for_sub_assembly_items"):
- work_orders = self.make_work_order_for_sub_assembly_items(item)
- wo_list.extend(work_orders)
+ def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po):
+ for row in self.sub_assembly_items:
+ if row.type_of_manufacturing == 'Subcontract':
+ subcontracted_po.setdefault(row.supplier, []).append(row)
+ continue
+
+ args = {}
+ self.prepare_args_for_sub_assembly_items(row, args)
+ work_order = self.create_work_order(args)
+ if work_order:
+ wo_list.append(work_order)
+
+ def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
+ if not subcontracted_po:
+ return
+
+ for supplier, po_list in subcontracted_po.items():
+ po = frappe.new_doc('Purchase Order')
+ po.supplier = supplier
+ po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
+ po.is_subcontracted_item = 'Yes'
+ for row in po_list:
+ args = {
+ 'item_code': row.production_item,
+ 'warehouse': row.fg_warehouse,
+ 'production_plan_sub_assembly_item': row.name,
+ 'bom': row.bom_no,
+ 'production_plan': self.name
+ }
+
+ for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
+ 'description', 'production_plan_item']:
+ args[field] = row.get(field)
+
+ po.append('items', args)
+
+ po.set_missing_values()
+ po.flags.ignore_mandatory = True
+ po.flags.ignore_validate = True
+ po.insert()
+ purchase_orders.append(po.name)
+
+ def show_list_created_message(self, doctype, doc_list=None):
+ if not doc_list:
+ return
frappe.flags.mute_messages = False
+ if doc_list:
+ doc_list = [get_link_to_form(doctype, p) for p in doc_list]
+ msgprint(_("{0} created").format(comma_and(doc_list)))
- if wo_list:
- wo_list = ["""%s""" % \
- (p, p) for p in wo_list]
- msgprint(_("{0} created").format(comma_and(wo_list)))
- else :
- msgprint(_("No Work Orders created"))
+ def prepare_args_for_sub_assembly_items(self, row, args):
+ for field in ["production_item", "item_name", "qty", "fg_warehouse",
+ "description", "bom_no", "stock_uom", "bom_level", "production_plan_item"]:
+ args[field] = row.get(field)
- def make_work_order_for_sub_assembly_items(self, item):
- work_orders = []
- bom_data = {}
-
- get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty"))
-
- for key, data in bom_data.items():
- data.update({
- 'qty': data.get("stock_qty"),
- 'production_plan': self.name,
- 'use_multi_level_bom': item.get("use_multi_level_bom"),
- 'company': self.company,
- 'fg_warehouse': item.get("fg_warehouse"),
- 'update_consumed_material_cost_in_project': 0
- })
-
- work_order = self.create_work_order(data)
- if work_order:
- work_orders.append(work_order)
-
- return work_orders
+ args.update({
+ "use_multi_level_bom": 0,
+ "production_plan": self.name,
+ "production_plan_sub_assembly_item": row.name
+ })
def create_work_order(self, item):
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError, get_default_warehouse
@@ -476,9 +516,32 @@ class ProductionPlan(Document):
else :
msgprint(_("No material request created"))
+ @frappe.whitelist()
+ def get_sub_assembly_items(self, manufacturing_type=None):
+ self.sub_assembly_items = []
+ for row in self.po_items:
+ bom_data = []
+ get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
+ self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
+
+ self.save()
+
+ def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
+ bom_data = sorted(bom_data, key = lambda i: i.bom_level)
+
+ for data in bom_data:
+ data.qty = data.stock_qty
+ data.production_plan_item = row.name
+ data.fg_warehouse = row.warehouse
+ data.schedule_date = row.planned_start_date
+ data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
+ else "In House")
+
+ self.append("sub_assembly_items", data)
+
@frappe.whitelist()
def download_raw_materials(doc, warehouses=None):
- if isinstance(doc, string_types):
+ if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
@@ -660,7 +723,7 @@ def get_sales_orders(self):
@frappe.whitelist()
def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
- if isinstance(row, string_types):
+ if isinstance(row, str):
row = frappe._dict(json.loads(row))
company = frappe.db.escape(company)
@@ -684,8 +747,11 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
group by item_code, warehouse
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
-def get_warehouse_list(warehouses, warehouse_list=[]):
- if isinstance(warehouses, string_types):
+def get_warehouse_list(warehouses, warehouse_list=None):
+ if not warehouse_list:
+ warehouse_list = []
+
+ if isinstance(warehouses, str):
warehouses = json.loads(warehouses)
for row in warehouses:
@@ -697,7 +763,7 @@ def get_warehouse_list(warehouses, warehouse_list=[]):
@frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
- if isinstance(doc, string_types):
+ if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
warehouse_list = []
@@ -726,6 +792,9 @@ def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_d
so_item_details = frappe._dict()
for data in po_items:
+ if not data.get("include_exploded_items") and doc.get("sub_assembly_items"):
+ data["include_exploded_items"] = 1
+
planned_qty = data.get('required_qty') or data.get('planned_qty')
ignore_existing_ordered_qty = data.get('ignore_existing_ordered_qty') or ignore_existing_ordered_qty
warehouse = doc.get('for_warehouse')
@@ -857,23 +926,28 @@ def get_item_data(item_code):
# "description": item_details.get("description")
}
-def get_sub_assembly_items(bom_no, bom_data, to_produce_qty):
+def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
data = get_children('BOM', parent = bom_no)
for d in data:
if d.expandable:
- key = (d.name, d.value)
- if key not in bom_data:
- bom_data.setdefault(key, {
- 'stock_qty': 0,
- 'description': d.description,
- 'production_item': d.item_code,
- 'item_name': d.item_name,
- 'stock_uom': d.stock_uom,
- 'uom': d.stock_uom,
- 'bom_no': d.value
- })
+ parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
+ bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level")
+ if d.value else 0)
- bom_item = bom_data.get(key)
- bom_item["stock_qty"] += (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
+ stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
+ bom_data.append(frappe._dict({
+ 'parent_item_code': parent_item_code,
+ 'description': d.description,
+ 'production_item': d.item_code,
+ 'item_name': d.item_name,
+ 'stock_uom': d.stock_uom,
+ 'uom': d.stock_uom,
+ 'bom_no': d.value,
+ 'is_sub_contracted_item': d.is_sub_contracted_item,
+ 'bom_level': bom_level,
+ 'indent': indent,
+ 'stock_qty': stock_qty
+ }))
- get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"])
+ if d.value:
+ get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
index 09ec24a67a2..ca597f63278 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan_dashboard.py
@@ -9,5 +9,9 @@ def get_data():
'label': _('Transactions'),
'items': ['Work Order', 'Material Request']
},
+ {
+ 'label': _('Subcontract'),
+ 'items': ['Purchase Order']
+ },
]
}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 768f99eb431..cce1bb61b6b 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -169,7 +169,7 @@ class TestProductionPlan(unittest.TestCase):
pln.get_items()
pln.submit()
- self.assertTrue(pln.po_items[0].planned_qty, 3)
+ self.assertTrue(pln.po_items[0].planned_qty, 3)
pln.make_work_order()
work_order = frappe.db.get_value('Work Order', {
@@ -193,10 +193,10 @@ class TestProductionPlan(unittest.TestCase):
for so_item in so_items:
so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
self.assertEqual(so_wo_qty, 0.0)
-
+
latest_plan = frappe.get_doc('Production Plan', pln.name)
latest_plan.cancel()
-
+
def test_pp_to_mr_customer_provided(self):
#Material Request from Production Plan for Customer Provided
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
@@ -236,10 +236,10 @@ class TestProductionPlan(unittest.TestCase):
pln.append("po_items", {
"item_code": item_code,
"bom_no": frappe.db.get_value('BOM', {'item': "Test BOM 1"}),
- "planned_qty": 3,
- "make_work_order_for_sub_assembly_items": 1
+ "planned_qty": 3
})
+ pln.get_sub_assembly_items('In House')
pln.submit()
pln.make_work_order()
diff --git a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
index 89ab7aa0a06..f829d57475a 100644
--- a/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_item/production_plan_item.json
@@ -9,18 +9,17 @@
"include_exploded_items",
"item_code",
"bom_no",
- "planned_qty",
"column_break_6",
- "make_work_order_for_sub_assembly_items",
+ "planned_qty",
"warehouse",
"planned_start_date",
"section_break_9",
"pending_qty",
"ordered_qty",
- "produced_qty",
"column_break_17",
"description",
"stock_uom",
+ "produced_qty",
"reference_section",
"sales_order",
"sales_order_item",
@@ -32,11 +31,10 @@
],
"fields": [
{
- "columns": 2,
- "default": "0",
+ "columns": 1,
+ "default": "1",
"fieldname": "include_exploded_items",
"fieldtype": "Check",
- "in_list_view": 1,
"label": "Include Exploded Items"
},
{
@@ -80,13 +78,6 @@
"fieldname": "column_break_6",
"fieldtype": "Column Break"
},
- {
- "default": "0",
- "description": "If enabled, system will create the work order for the exploded items against which BOM is available.",
- "fieldname": "make_work_order_for_sub_assembly_items",
- "fieldtype": "Check",
- "label": "Make Work Order for Sub Assembly Items"
- },
{
"fieldname": "warehouse",
"fieldtype": "Link",
@@ -218,7 +209,7 @@
"idx": 1,
"istable": 1,
"links": [],
- "modified": "2021-04-28 19:14:57.772123",
+ "modified": "2021-06-28 18:31:06.822168",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan Item",
diff --git a/erpnext/accounts/accounts b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py
similarity index 100%
rename from erpnext/accounts/accounts
rename to erpnext/manufacturing/doctype/production_plan_sub_assembly_item/__init__.py
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
new file mode 100644
index 00000000000..657ee35a852
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -0,0 +1,202 @@
+{
+ "actions": [],
+ "creation": "2020-12-27 16:08:36.127199",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "production_item",
+ "item_name",
+ "fg_warehouse",
+ "parent_item_code",
+ "schedule_date",
+ "column_break_3",
+ "qty",
+ "bom_no",
+ "bom_level",
+ "type_of_manufacturing",
+ "supplier",
+ "work_order_details_section",
+ "work_order",
+ "purchase_order",
+ "production_plan_item",
+ "column_break_7",
+ "produced_qty",
+ "received_qty",
+ "indent",
+ "section_break_19",
+ "uom",
+ "stock_uom",
+ "column_break_22",
+ "description"
+ ],
+ "fields": [
+ {
+ "fetch_from": "sub_assembly_item_code.item_name",
+ "fieldname": "item_name",
+ "fieldtype": "Data",
+ "label": "Item Name",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
+ {
+ "depends_on": "eval:doc.type_of_manufacturing == \"In House\"",
+ "fieldname": "work_order_details_section",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
+ {
+ "fieldname": "work_order",
+ "fieldtype": "Link",
+ "label": "Work Order",
+ "options": "Work Order",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_7",
+ "fieldtype": "Column Break"
+ },
+ {
+ "columns": 1,
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "in_list_view": 1,
+ "label": "Required Qty",
+ "read_only": 1
+ },
+ {
+ "fieldname": "purchase_order",
+ "fieldtype": "Link",
+ "label": "Purchase Order",
+ "options": "Purchase Order",
+ "read_only": 1
+ },
+ {
+ "fieldname": "received_qty",
+ "fieldtype": "Float",
+ "label": "Received Qty"
+ },
+ {
+ "fieldname": "bom_no",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Bom No",
+ "options": "BOM"
+ },
+ {
+ "fieldname": "production_plan_item",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Production Plan Item",
+ "read_only": 1
+ },
+ {
+ "fieldname": "parent_item_code",
+ "fieldtype": "Link",
+ "label": "Finished Good",
+ "options": "Item",
+ "read_only": 1
+ },
+ {
+ "columns": 1,
+ "fetch_from": "bom_no.bom_level",
+ "fieldname": "bom_level",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Level (BOM)",
+ "read_only": 1
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "section_break_19",
+ "fieldtype": "Section Break",
+ "label": "Item Details"
+ },
+ {
+ "fieldname": "uom",
+ "fieldtype": "Link",
+ "label": "UOM",
+ "options": "UOM",
+ "read_only": 1
+ },
+ {
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "label": "Stock UOM",
+ "options": "UOM",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_22",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "description",
+ "read_only": 1
+ },
+ {
+ "fieldname": "production_item",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Sub Assembly Item Code",
+ "options": "Item",
+ "read_only": 1
+ },
+ {
+ "fieldname": "indent",
+ "fieldtype": "Int",
+ "label": "Indent"
+ },
+ {
+ "fieldname": "fg_warehouse",
+ "fieldtype": "Link",
+ "label": "Target Warehouse",
+ "options": "Warehouse"
+ },
+ {
+ "fieldname": "produced_qty",
+ "fieldtype": "Data",
+ "label": "Produced Quantity",
+ "read_only": 1
+ },
+ {
+ "default": "In House",
+ "fieldname": "type_of_manufacturing",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Manufacturing Type",
+ "options": "In House\nSubcontract"
+ },
+ {
+ "fieldname": "supplier",
+ "fieldtype": "Link",
+ "label": "Supplier",
+ "mandatory_depends_on": "eval:doc.type_of_manufacturing == 'Subcontract'",
+ "options": "Supplier"
+ },
+ {
+ "fieldname": "schedule_date",
+ "fieldtype": "Datetime",
+ "in_list_view": 1,
+ "label": "Schedule Date"
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-28 20:10:56.296410",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan Sub Assembly Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py
new file mode 100644
index 00000000000..6850a2eb4ed
--- /dev/null
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.py
@@ -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 ProductionPlanSubAssemblyItem(Document):
+ pass
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 44d76d2b01c..3b56854aaf3 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -64,11 +64,16 @@
"description",
"stock_uom",
"column_break2",
+ "references_section",
"material_request",
"material_request_item",
"sales_order_item",
+ "column_break_61",
"production_plan",
"production_plan_item",
+ "production_plan_sub_assembly_item",
+ "parent_work_order",
+ "bom_level",
"product_bundle_item",
"amended_from"
],
@@ -546,17 +551,26 @@
"no_copy": 1,
"print_hide": 1,
"read_only": 1
- }
+ },
+ {
+ "fieldname": "production_plan_sub_assembly_item",
+ "fieldtype": "Data",
+ "label": "Production Plan Sub-assembly Item",
+ "no_copy": 1,
+ "print_hide": 1,
+ "read_only": 1
+ }
],
"icon": "fa fa-cogs",
"idx": 1,
"image_field": "image",
"is_submittable": 1,
"links": [],
- "modified": "2021-06-20 15:19:14.902699",
+ "modified": "2021-06-28 16:19:14.902699",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order",
+ "nsm_parent_field": "parent_work_order",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 180815d80e4..779ae42d653 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -483,7 +483,7 @@ class WorkOrder(Document):
self.set('operations', [])
- if not self.bom_no:
+ if not self.bom_no or not frappe.get_cached_value('BOM', self.bom_no, 'with_operations'):
return
operations = []
@@ -590,6 +590,7 @@ class WorkOrder(Document):
def validate_operation_time(self):
for d in self.operations:
if not d.time_in_mins > 0:
+ print(self.bom_no, self.production_item)
frappe.throw(_("Operation Time must be greater than 0 for Operation {0}").format(d.operation))
def update_required_items(self):
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 48907adc5f3..858b5546b02 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -20,17 +20,20 @@ def get_exploded_items(bom, data, indent=0, qty=1):
fields= ['qty','bom_no','qty','scrap','item_code','item_name','description','uom'])
for item in exploded_items:
+ print(item.bom_no, indent)
item["indent"] = indent
data.append({
'item_code': item.item_code,
'item_name': item.item_name,
'indent': indent,
+ 'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level")
+ if item.bom_no else ""),
'bom': item.bom_no,
'qty': item.qty * qty,
'uom': item.uom,
'description': item.description,
'scrap': item.scrap
- })
+ })
if item.bom_no:
get_exploded_items(item.bom_no, data, indent=indent+1, qty=item.qty)
@@ -68,6 +71,12 @@ def get_columns():
"fieldname": "uom",
"width": 100
},
+ {
+ "label": "BOM Level",
+ "fieldtype": "Data",
+ "fieldname": "bom_level",
+ "width": 100
+ },
{
"label": "Standard Description",
"fieldtype": "data",
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index 1c6758e6f36..ed8b93929a1 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -70,12 +70,12 @@ def get_bom_stock(filters):
ON bom_item.item_code = ledger.item_code
{conditions}
WHERE
- bom_item.parent = '{bom}' and bom_item.parenttype='BOM'
+ bom_item.parent = {bom} and bom_item.parenttype='BOM'
GROUP BY bom_item.item_code""".format(
qty_field=qty_field,
table=table,
conditions=conditions,
- bom=bom,
+ bom=frappe.db.escape(bom),
qty_to_produce=qty_to_produce or 1)
)
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
index bd68db190e7..cb771e49941 100644
--- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.js
@@ -68,6 +68,18 @@ frappe.query_reports["Job Card Summary"] = {
get_data: function(txt) {
return frappe.db.get_link_options('Item', txt);
}
+ },
+ {
+ label: __("Workstation"),
+ fieldname: "workstation",
+ fieldtype: "Link",
+ options: "Workstation"
+ },
+ {
+ label: __("Operation"),
+ fieldname: "operation",
+ fieldtype: "Link",
+ options: "Operation"
}
]
};
diff --git a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
index 9f08fc34cb8..ecf2b74bbed 100644
--- a/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
+++ b/erpnext/manufacturing/report/job_card_summary/job_card_summary.json
@@ -1,14 +1,16 @@
{
- "add_total_row": 0,
+ "add_total_row": 1,
+ "columns": [],
"creation": "2020-04-20 12:00:21.436619",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
+ "filters": [],
"idx": 0,
"is_standard": "Yes",
- "letter_head": "Gadgets International",
- "modified": "2020-04-20 12:00:21.436619",
+ "letter_head": "",
+ "modified": "2020-12-30 11:49:21.713561",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Job Card Summary",
diff --git a/erpnext/manufacturing/report/production_plan_summary/__init__.py b/erpnext/manufacturing/report/production_plan_summary/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js
new file mode 100644
index 00000000000..59396fef16e
--- /dev/null
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.js
@@ -0,0 +1,32 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Production Plan Summary"] = {
+ "filters": [
+ {
+ fieldname: "production_plan",
+ label: __("Production Plan"),
+ fieldtype: "Link",
+ options: "Production Plan",
+ reqd: 1,
+ get_query: function() {
+ return {
+ filters: {
+ "docstatus": 1
+ }
+ };
+ }
+ }
+ ],
+ "formatter": function(value, row, column, data, default_formatter) {
+ value = default_formatter(value, row, column, data);
+
+ if (column.fieldname == "document_name") {
+ var color = data.pending_qty > 0 ? 'red': 'green';
+ value = `${data['document_name']}`;
+ }
+
+ return value;
+ },
+};
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json
new file mode 100644
index 00000000000..33aca21a6ea
--- /dev/null
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2020-12-27 11:43:39.781793",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-12-27 11:43:42.677584",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "Production Plan Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Production Plan",
+ "report_name": "Production Plan Summary",
+ "report_type": "Script Report",
+ "roles": [
+ {
+ "role": "Manufacturing User"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
new file mode 100644
index 00000000000..81b1791ae81
--- /dev/null
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
@@ -0,0 +1,136 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.utils import flt
+
+def execute(filters=None):
+ columns, data = [], []
+ data = get_data(filters)
+ columns = get_column(filters)
+
+ return columns, data
+
+def get_data(filters):
+ data = []
+
+ order_details = {}
+ get_work_order_details(filters, order_details)
+ get_purchase_order_details(filters, order_details)
+ get_production_plan_item_details(filters, data, order_details)
+
+ return data
+
+def get_production_plan_item_details(filters, data, order_details):
+ itemwise_indent = {}
+
+ production_plan_doc = frappe.get_cached_doc("Production Plan", filters.get("production_plan"))
+ for row in production_plan_doc.po_items:
+ work_order = frappe.get_cached_value("Work Order", {"production_plan_item": row.name,
+ "bom_no": row.bom_no, "production_item": row.item_code}, "name")
+
+ if row.item_code not in itemwise_indent:
+ itemwise_indent.setdefault(row.item_code, {})
+
+ data.append({
+ "indent": 0,
+ "item_code": row.item_code,
+ "item_name": frappe.get_cached_value("Item", row.item_code, "item_name"),
+ "qty": row.planned_qty,
+ "document_type": "Work Order",
+ "document_name": work_order,
+ "bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"),
+ "produced_qty": order_details.get((work_order, row.item_code)).get("produced_qty"),
+ "pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code)).get("produced_qty"))
+ })
+
+ get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details)
+
+def get_production_plan_sub_assembly_item_details(filters, row, production_plan_doc, data, order_details):
+ for item in production_plan_doc.sub_assembly_items:
+ if row.name == item.production_plan_item:
+ subcontracted_item = (item.type_of_manufacturing == 'Subcontract')
+
+ if subcontracted_item:
+ docname = frappe.get_cached_value("Purchase Order Item",
+ {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "parent")
+ else:
+ docname = frappe.get_cached_value("Work Order",
+ {"production_plan_sub_assembly_item": item.name, "docstatus": ("<", 2)}, "name")
+
+ data.append({
+ "indent": 1,
+ "item_code": item.production_item,
+ "item_name": item.item_name,
+ "qty": item.qty,
+ "document_type": "Work Order" if not subcontracted_item else "Purchase Order",
+ "document_name": docname,
+ "bom_level": item.bom_level,
+ "produced_qty": order_details.get((docname, item.production_item)).get("produced_qty"),
+ "pending_qty": flt(item.qty) - flt(order_details.get((docname, item.production_item)).get("produced_qty"))
+ })
+
+def get_work_order_details(filters, order_details):
+ for row in frappe.get_all("Work Order", filters = {"production_plan": filters.get("production_plan")},
+ fields=["name", "produced_qty", "production_plan", "production_item"]):
+ order_details.setdefault((row.name, row.production_item), row)
+
+def get_purchase_order_details(filters, order_details):
+ for row in frappe.get_all("Purchase Order Item", filters = {"production_plan": filters.get("production_plan")},
+ fields=["parent", "received_qty as produced_qty", "item_code"]):
+ order_details.setdefault((row.parent, row.item_code), row)
+
+def get_column(filters):
+ return [
+ {
+ "label": "Finished Good",
+ "fieldtype": "Link",
+ "fieldname": "item_code",
+ "width": 300,
+ "options": "Item"
+ },
+ {
+ "label": "Item Name",
+ "fieldtype": "data",
+ "fieldname": "item_name",
+ "width": 100
+ },
+ {
+ "label": "Document Type",
+ "fieldtype": "Link",
+ "fieldname": "document_type",
+ "width": 150,
+ "options": "DocType"
+ },
+ {
+ "label": "Document Name",
+ "fieldtype": "Dynamic Link",
+ "fieldname": "document_name",
+ "width": 150
+ },
+ {
+ "label": "BOM Level",
+ "fieldtype": "Int",
+ "fieldname": "bom_level",
+ "width": 100
+ },
+ {
+ "label": "Order Qty",
+ "fieldtype": "Float",
+ "fieldname": "qty",
+ "width": 120
+ },
+ {
+ "label": "Received Qty",
+ "fieldtype": "Float",
+ "fieldname": "produced_qty",
+ "width": 160
+ },
+ {
+ "label": "Pending Qty",
+ "fieldtype": "Float",
+ "fieldname": "pending_qty",
+ "width": 110
+ }
+ ]
diff --git a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
index fb047b230ce..612dad0bf51 100644
--- a/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
+++ b/erpnext/manufacturing/report/work_order_summary/work_order_summary.py
@@ -19,7 +19,7 @@ def execute(filters=None):
return columns, data, None, chart_data
def get_data(filters):
- query_filters = {"docstatus": 1}
+ query_filters = {"docstatus": ("<", 2)}
fields = ["name", "status", "sales_order", "production_item", "qty", "produced_qty",
"planned_start_date", "planned_end_date", "actual_start_date", "actual_end_date", "lead_time"]
@@ -62,7 +62,8 @@ def get_chart_based_on_status(data):
"Not Started": 0,
"In Process": 0,
"Stopped": 0,
- "Completed": 0
+ "Completed": 0,
+ "Draft": 0
}
for d in data:
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index b08b90bbf74..5345a15eedc 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -290,6 +290,7 @@ erpnext.patches.v13_0.set_training_event_attendance
erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.update_job_card_details
+erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.create_website_items
erpnext.patches.v13_0.populate_e_commerce_settings
-erpnext.patches.v13_0.make_homepage_products_website_items
\ No newline at end of file
+erpnext.patches.v13_0.make_homepage_products_website_items
diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py
new file mode 100644
index 00000000000..0d03c42e980
--- /dev/null
+++ b/erpnext/patches/v13_0/update_level_in_bom.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+ for document in ["bom", "bom_item", "bom_explosion_item"]:
+ frappe.reload_doc('manufacturing', 'doctype', document)
+
+ frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1")
+
+ bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
+ where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item`
+ where parent=bom.name and ifnull(bom_no, '')!='')""")
+
+ count = 0
+ while(count < len(bom_list)):
+ for parent_bom in get_parent_boms(bom_list[count]):
+ bom_doc = frappe.get_cached_doc("BOM", parent_bom)
+ bom_doc.set_bom_level(update=True)
+ bom_list.append(parent_bom)
+ count += 1
+
+def get_parent_boms(bom_no):
+ return frappe.db.sql_list("""
+ select distinct bom_item.parent from `tabBOM Item` bom_item
+ where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
+ and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
+ """, bom_no)
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index f2892600d12..496c37b2fad 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -135,10 +135,26 @@ frappe.ui.form.on('Payroll Entry', {
});
frm.set_query('employee', 'employees', () => {
- if (!frm.doc.company) {
- frappe.msgprint(__("Please set a Company"));
- return [];
+ let error_fields = [];
+ let mandatory_fields = ['company', 'payroll_frequency', 'start_date', 'end_date'];
+
+ let message = __('Mandatory fields required in {0}', [__(frm.doc.doctype)]);
+
+ mandatory_fields.forEach(field => {
+ if (!frm.doc[field]) {
+ error_fields.push(frappe.unscrub(field));
+ }
+ });
+
+ if (error_fields && error_fields.length) {
+ message = message + '
- -
+-
{{ _("No tasks") }}
-{% endif %} + {{ progress_bar(doc.percent_complete) }} + {% if doc.tasks %} +{{ _("No Tasks") }}
+ {% endif %} - + {% if doc.timesheets %} +{{ _("No Timesheets") }}
+ {% endif %} -- {% endif %} -{% else %} -
{{ _("No time sheets") }}
-{% endif %} - -{% if doc.attachments %} - - -