diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js index 3c12f85f93..9a6c389339 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.js @@ -7,7 +7,7 @@ frappe.ui.form.on('Accounting Dimension', { frm.set_query('document_type', () => { let invalid_doctypes = frappe.model.core_doctypes_list; invalid_doctypes.push('Accounting Dimension', 'Project', - 'Cost Center', 'Accounting Dimension Detail'); + 'Cost Center', 'Accounting Dimension Detail', 'Company'); return { filters: { diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py index 8834385135..f888d9e038 100644 --- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py +++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py @@ -19,7 +19,7 @@ class AccountingDimension(Document): def validate(self): if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project', - 'Cost Center', 'Accounting Dimension Detail') : + 'Cost Center', 'Accounting Dimension Detail', 'Company') : msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type) frappe.throw(msg) diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.json b/erpnext/accounts/doctype/pos_profile/pos_profile.json index 4e22218c6e..570111acac 100644 --- a/erpnext/accounts/doctype/pos_profile/pos_profile.json +++ b/erpnext/accounts/doctype/pos_profile/pos_profile.json @@ -14,6 +14,7 @@ "column_break_9", "update_stock", "ignore_pricing_rule", + "hide_unavailable_items", "warehouse", "campaign", "company_address", @@ -307,13 +308,19 @@ "fieldtype": "Check", "label": "Update Stock", "read_only": 1 + }, + { + "default": "0", + "fieldname": "hide_unavailable_items", + "fieldtype": "Check", + "label": "Hide Unavailable Items" } ], "icon": "icon-cog", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-20 13:16:50.665081", + "modified": "2020-10-29 13:18:38.795925", "modified_by": "Administrator", "module": "Accounts", "name": "POS Profile", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index c681c897fc..cc8ed4bc49 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -504,10 +504,10 @@ }, { "default": "0", - "depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules", + "depends_on": "eval:in_list(['Discount Percentage'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules", "fieldname": "apply_discount_on_rate", "fieldtype": "Check", - "label": "Apply Discount on Rate" + "label": "Apply Discount on Discounted Rate" }, { "default": "0", @@ -563,7 +563,7 @@ "icon": "fa fa-gift", "idx": 1, "links": [], - "modified": "2020-08-26 12:24:44.740734", + "modified": "2020-10-28 16:53:14.416172", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 454776e4e7..149c47673c 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -60,6 +60,15 @@ class PricingRule(Document): if self.price_or_product_discount == 'Price' and not self.rate_or_discount: throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) + if self.apply_discount_on_rate: + if not self.priority: + throw(_("As the field {0} is enabled, the field {1} is mandatory.") + .format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority"))) + + if self.priority and cint(self.priority) == 1: + throw(_("As the field {0} is enabled, the value of the field {1} should be more than 1.") + .format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority"))) + def validate_applicable_for_selling_or_buying(self): if not self.selling and not self.buying: throw(_("Atleast one of the Selling or Buying must be selected")) @@ -226,12 +235,11 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa item_details = frappe._dict({ "doctype": args.doctype, + "has_margin": False, "name": args.name, "parent": args.parent, "parenttype": args.parenttype, - "child_docname": args.get('child_docname'), - "discount_percentage_on_rate": [], - "discount_amount_on_rate": [] + "child_docname": args.get('child_docname') }) if args.ignore_pricing_rule or not args.item_code: @@ -279,6 +287,10 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa else: get_product_discount_rule(pricing_rule, item_details, args, doc) + if not item_details.get("has_margin"): + item_details.margin_type = None + item_details.margin_rate_or_amount = 0.0 + item_details.has_pricing_rule = 1 item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules]) @@ -330,13 +342,11 @@ def get_pricing_rule_details(args, pricing_rule): def apply_price_discount_rule(pricing_rule, item_details, args): item_details.pricing_rule_for = pricing_rule.rate_or_discount - if ((pricing_rule.margin_type == 'Amount' and pricing_rule.currency == args.currency) + if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency) or (pricing_rule.margin_type == 'Percentage')): item_details.margin_type = pricing_rule.margin_type item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount - else: - item_details.margin_type = None - item_details.margin_rate_or_amount = 0.0 + item_details.has_margin = True if pricing_rule.rate_or_discount == 'Rate': pricing_rule_rate = 0.0 @@ -351,9 +361,9 @@ def apply_price_discount_rule(pricing_rule, item_details, args): if pricing_rule.rate_or_discount != apply_on: continue field = frappe.scrub(apply_on) - if pricing_rule.apply_discount_on_rate: - discount_field = "{0}_on_rate".format(field) - item_details[discount_field].append(pricing_rule.get(field, 0)) + if pricing_rule.apply_discount_on_rate and item_details.get("discount_percentage"): + # Apply discount on discounted rate + item_details[field] += ((100 - item_details[field]) * (pricing_rule.get(field, 0) / 100)) else: if field not in item_details: item_details.setdefault(field, 0) @@ -361,14 +371,6 @@ def apply_price_discount_rule(pricing_rule, item_details, args): item_details[field] += (pricing_rule.get(field, 0) if pricing_rule else args.get(field, 0)) -def set_discount_amount(rate, item_details): - for field in ['discount_percentage_on_rate', 'discount_amount_on_rate']: - for d in item_details.get(field): - dis_amount = (rate * d / 100 - if field == 'discount_percentage_on_rate' else d) - rate -= dis_amount - item_details.rate = rate - def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules, get_pricing_rule_items) diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py index 3555ca895f..22a031c162 100644 --- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py @@ -385,7 +385,7 @@ class TestPricingRule(unittest.TestCase): so.load_from_db() self.assertEqual(so.items[1].is_free_item, 1) self.assertEqual(so.items[1].item_code, "_Test Item 2") - + def test_cumulative_pricing_rule(self): frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule') test_record = { @@ -429,34 +429,61 @@ class TestPricingRule(unittest.TestCase): details = get_item_details(args) self.assertTrue(details) - + def test_pricing_rule_for_condition(self): frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") - + make_pricing_rule(selling=1, margin_type="Percentage", \ condition="customer=='_Test Customer 1' and is_return==0", discount_percentage=10) - + # Incorrect Customer and Correct is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 2", is_return=0) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] self.assertEquals(item.rate, 100) - + # Correct Customer and Incorrect is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] self.assertEquals(item.rate, 100) - + # Correct Customer and correct is_return value si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0) si.items[0].price_list_rate = 1000 si.submit() item = si.items[0] self.assertEquals(item.rate, 900) - + + def test_multiple_pricing_rules(self): + make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1, + title="_Test Pricing Rule 1") + make_pricing_rule(discount_percentage=10, selling=1, title="_Test Pricing Rule 2", priority=2, + apply_multiple_pricing_rules=1) + si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1) + self.assertEqual(si.items[0].discount_percentage, 30) + si.delete() + + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + + def test_multiple_pricing_rules_with_apply_discount_on_discounted_rate(self): + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule") + + make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1, + title="_Test Pricing Rule 1") + make_pricing_rule(discount_percentage=10, selling=1, priority=2, + apply_discount_on_rate=1, title="_Test Pricing Rule 2", apply_multiple_pricing_rules=1) + + si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1) + self.assertEqual(si.items[0].discount_percentage, 28) + si.delete() + + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 1") + frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule 2") + def make_pricing_rule(**args): args = frappe._dict(args) @@ -468,6 +495,7 @@ def make_pricing_rule(**args): "applicable_for": args.applicable_for, "selling": args.selling or 0, "currency": "USD", + "apply_discount_on_rate": args.apply_discount_on_rate or 0, "buying": args.buying or 0, "min_qty": args.min_qty or 0.0, "max_qty": args.max_qty or 0.0, @@ -476,9 +504,13 @@ def make_pricing_rule(**args): "rate": args.rate or 0.0, "margin_type": args.margin_type, "margin_rate_or_amount": args.margin_rate_or_amount or 0.0, - "condition": args.condition or '' + "condition": args.condition or '', + "apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0 }) + if args.get("priority"): + doc.priority = args.get("priority") + apply_on = doc.apply_on.replace(' ', '_').lower() child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'} doc.append(child_table.get(doc.apply_on), { diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 25840d4fb7..b003328cc4 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -14,9 +14,8 @@ import frappe from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.get_item_details import get_conversion_factor -from frappe import _, throw -from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today - +from frappe import _, bold +from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money class MultiplePricingRuleConflict(frappe.ValidationError): pass @@ -42,6 +41,7 @@ def get_pricing_rules(args, doc=None): if not pricing_rules: return [] if apply_multiple_pricing_rules(pricing_rules): + pricing_rules = sorted_by_priority(pricing_rules) for pricing_rule in pricing_rules: pricing_rule = filter_pricing_rules(args, pricing_rule, doc) if pricing_rule: @@ -53,6 +53,20 @@ def get_pricing_rules(args, doc=None): return rules +def sorted_by_priority(pricing_rules): + # If more than one pricing rules, then sort by priority + pricing_rules_list = [] + pricing_rule_dict = {} + for pricing_rule in pricing_rules: + if not pricing_rule.get("priority"): continue + + pricing_rule_dict.setdefault(cint(pricing_rule.get("priority")), []).append(pricing_rule) + + for key in sorted(pricing_rule_dict): + pricing_rules_list.append(pricing_rule_dict.get(key)) + + return pricing_rules_list or pricing_rules + def filter_pricing_rule_based_on_condition(pricing_rules, doc=None): filtered_pricing_rules = [] if doc: @@ -284,12 +298,13 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr fieldname = field if fieldname: - msg = _("""If you {0} {1} quantities of the item {2}, the scheme {3} - will be applied on the item.""").format(type_of_transaction, args.get(fieldname), item_code, args.rule_description) + msg = (_("If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item.") + .format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description))) if fieldname in ['min_amt', 'max_amt']: - msg = _("""If you {0} {1} worth item {2}, the scheme {3} will be applied on the item. - """).format(frappe.fmt_money(type_of_transaction, args.get(fieldname)), item_code, args.rule_description) + msg = (_("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.") + .format(type_of_transaction, fmt_money(args.get(fieldname), currency=args.get("currency")), + bold(item_code), bold(args.rule_description))) frappe.msgprint(msg) diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py index 811fc356cf..c17fccdce0 100644 --- a/erpnext/accounts/doctype/subscription/test_subscription.py +++ b/erpnext/accounts/doctype/subscription/test_subscription.py @@ -237,7 +237,7 @@ class TestSubscription(unittest.TestCase): subscription.party_type = 'Customer' subscription.party = '_Test Customer' subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) - subscription.start_date = '2018-01-01' + subscription.start_date = add_days(nowdate(), -1000) subscription.insert() subscription.process() # generate first invoice diff --git a/erpnext/accounts/report/pos_register/pos_register.py b/erpnext/accounts/report/pos_register/pos_register.py index 0bcde64f7f..52f7fe238e 100644 --- a/erpnext/accounts/report/pos_register/pos_register.py +++ b/erpnext/accounts/report/pos_register/pos_register.py @@ -63,6 +63,7 @@ def get_pos_entries(filters, group_by_field): FROM `tabPOS Invoice` p {from_sales_invoice_payment} WHERE + p.docstatus = 1 and {group_by_mop_condition} {conditions} ORDER BY diff --git a/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json b/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json index bc2edc9d7d..94debf1243 100644 --- a/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json +++ b/erpnext/assets/dashboard_chart/asset_value_analytics/asset_value_analytics.json @@ -9,9 +9,9 @@ "filters_json": "{\"status\":\"In Location\",\"filter_based_on\":\"Fiscal Year\",\"period_start_date\":\"2020-04-01\",\"period_end_date\":\"2021-03-31\",\"date_based_on\":\"Purchase Date\",\"group_by\":\"--Select a group--\"}", "group_by_type": "Count", "idx": 0, - "is_public": 0, + "is_public": 1, "is_standard": 1, - "modified": "2020-07-23 13:53:33.211371", + "modified": "2020-10-28 23:15:58.432189", "modified_by": "Administrator", "module": "Assets", "name": "Asset Value Analytics", diff --git a/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json b/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json index e79d2d7372..78611da003 100644 --- a/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json +++ b/erpnext/assets/dashboard_chart/category_wise_asset_value/category_wise_asset_value.json @@ -8,9 +8,9 @@ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Asset Category\",\"is_existing_asset\":0}", "idx": 0, - "is_public": 0, + "is_public": 1, "is_standard": 1, - "modified": "2020-07-23 13:39:32.429240", + "modified": "2020-10-28 23:16:16.939070", "modified_by": "Administrator", "module": "Assets", "name": "Category-wise Asset Value", diff --git a/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json b/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json index 481586e7ca..848184cc14 100644 --- a/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json +++ b/erpnext/assets/dashboard_chart/location_wise_asset_value/location_wise_asset_value.json @@ -8,9 +8,9 @@ "dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_date\":\"frappe.datetime.add_months(frappe.datetime.nowdate(), -12)\",\"to_date\":\"frappe.datetime.nowdate()\"}", "filters_json": "{\"status\":\"In Location\",\"group_by\":\"Location\",\"is_existing_asset\":0}", "idx": 0, - "is_public": 0, + "is_public": 1, "is_standard": 1, - "modified": "2020-07-23 13:42:44.912551", + "modified": "2020-10-28 23:16:07.883312", "modified_by": "Administrator", "module": "Assets", "name": "Location-wise Asset Value", diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 79fcb957d4..d422876047 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -55,6 +55,7 @@ "fieldtype": "Date", "in_list_view": 1, "label": "Depreciation Posting Date", + "mandatory_depends_on": "eval:parent.doctype == 'Asset'", "reqd": 1 }, { @@ -86,7 +87,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-09-16 12:11:30.631788", + "modified": "2020-10-30 15:22:29.119868", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 0181ae78b4..d568ef1ced 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -876,7 +876,7 @@ class TestPurchaseOrder(unittest.TestCase): }, { "item_code":item_code,"rm_item_code":"Sub Contracted Raw Material 4","item_name":"_Test Item", - "qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos", "name": po.supplied_items[1].name + "qty":250,"warehouse":"_Test Warehouse - _TC", "stock_uom":"Nos" }, ] @@ -885,6 +885,10 @@ class TestPurchaseOrder(unittest.TestCase): se = frappe.get_doc(make_subcontract_transfer_entry(po.name, rm_item_string)) se.submit() + # Test po_detail field has value or not + for item_row in se.items: + self.assertEqual(item_row.po_detail, po.supplied_items[item_row.idx - 1].name) + po_doc = frappe.get_doc("Purchase Order", po.name) for row in po_doc.supplied_items: # Valid that whether transferred quantity is matching with supplied qty or not in the purchase order diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 983cfa8c15..6108a614ca 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -263,6 +263,7 @@ class AccountsController(TransactionBase): if self.doctype == "Quotation" and self.quotation_to == "Customer" and parent_dict.get("party_name"): parent_dict.update({"customer": parent_dict.get("party_name")}) + self.pricing_rules = [] for item in self.get("items"): if item.get("item_code"): args = parent_dict.copy() @@ -301,6 +302,7 @@ class AccountsController(TransactionBase): if ret.get("pricing_rules"): self.apply_pricing_rule_on_items(item, ret) + self.set_pricing_rule_details(item, ret) if self.doctype == "Purchase Invoice": self.set_expense_account(for_validate) @@ -322,6 +324,9 @@ class AccountsController(TransactionBase): if item.get('discount_amount'): item.rate = item.price_list_rate - item.discount_amount + if item.get("apply_discount_on_discounted_rate") and pricing_rule_args.get("rate"): + item.rate = pricing_rule_args.get("rate") + elif pricing_rule_args.get('free_item_data'): apply_pricing_rule_for_free_items(self, pricing_rule_args.get('free_item_data')) @@ -335,6 +340,18 @@ class AccountsController(TransactionBase): frappe.msgprint(_("Row {0}: user has not applied the rule {1} on the item {2}") .format(item.idx, frappe.bold(title), frappe.bold(item.item_code))) + def set_pricing_rule_details(self, item_row, args): + pricing_rules = get_applied_pricing_rules(args.get("pricing_rules")) + if not pricing_rules: return + + for pricing_rule in pricing_rules: + self.append("pricing_rules", { + "pricing_rule": pricing_rule, + "item_code": item_row.item_code, + "child_docname": item_row.name, + "rule_applied": True + }) + def set_taxes(self): if not self.meta.get_field("taxes"): return diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index af2474d3de..5fabf7017b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -301,7 +301,7 @@ class BuyingController(StockController): # backflushed_batch_qty_map = get_backflushed_batch_qty_map(item.purchase_order, item.item_code) for raw_material in transferred_raw_materials + non_stock_items: - rm_item_key = (raw_material.rm_item_code, item.purchase_order) + rm_item_key = (raw_material.rm_item_code, item.item_code, item.purchase_order) raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {}) consumed_qty = raw_material_data.get('qty', 0) @@ -914,7 +914,7 @@ def get_backflushed_subcontracted_raw_materials(purchase_orders): purchase_receipt_supplied_items = get_supplied_items(args[1], args[2], references) for data in purchase_receipt_supplied_items: - pr_key = (data.rm_item_code, args[0]) + pr_key = (data.rm_item_code, data.main_item_code, args[0]) if pr_key not in backflushed_raw_materials_map: backflushed_raw_materials_map.setdefault(pr_key, frappe._dict({ "qty": 0.0, @@ -940,7 +940,7 @@ def get_backflushed_subcontracted_raw_materials(purchase_orders): def get_supplied_items(item_code, purchase_receipt, references): return frappe.get_all("Purchase Receipt Item Supplied", - fields=["rm_item_code", "consumed_qty", "serial_no", "batch_no"], + fields=["rm_item_code", "main_item_code", "consumed_qty", "serial_no", "batch_no"], filters={"main_item_code": item_code, "parent": purchase_receipt, "reference_name": ("in", references)}) def get_asset_item_details(asset_items): diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 92cfdb7f1a..81d07c1327 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -608,16 +608,19 @@ class calculate_taxes_and_totals(object): base_rate_with_margin = 0.0 if item.price_list_rate: if item.pricing_rules and not self.doc.ignore_pricing_rule: + has_margin = False for d in get_applied_pricing_rules(item.pricing_rules): pricing_rule = frappe.get_cached_doc('Pricing Rule', d) - if (pricing_rule.margin_type == 'Amount' and pricing_rule.currency == self.doc.currency)\ + if (pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == self.doc.currency)\ or (pricing_rule.margin_type == 'Percentage'): item.margin_type = pricing_rule.margin_type item.margin_rate_or_amount = pricing_rule.margin_rate_or_amount - else: - item.margin_type = None - item.margin_rate_or_amount = 0.0 + has_margin = True + + if not has_margin: + item.margin_type = None + item.margin_rate_or_amount = 0.0 if item.margin_type and item.margin_rate_or_amount: margin_value = item.margin_rate_or_amount if item.margin_type == 'Amount' else flt(item.price_list_rate) * flt(item.margin_rate_or_amount) / 100 diff --git a/erpnext/education/doctype/student_attendance/student_attendance.py b/erpnext/education/doctype/student_attendance/student_attendance.py index 595dff9b9d..72a8f55c66 100644 --- a/erpnext/education/doctype/student_attendance/student_attendance.py +++ b/erpnext/education/doctype/student_attendance/student_attendance.py @@ -75,6 +75,6 @@ class StudentAttendance(Document): }) if attendance_record: - record = get_link_to_form('Attendance Record', attendance_record) + record = get_link_to_form('Student Attendance', attendance_record) frappe.throw(_('Student Attendance record {0} already exists against the Student {1}') .format(record, frappe.bold(self.student)), title=_('Duplicate Entry')) diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index 1a64bc5e24..b723387a09 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -56,23 +56,35 @@ frappe.ui.form.on('Production Plan', { refresh: function(frm) { if (frm.doc.docstatus === 1) { 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')); + } + + 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')); + } + + if (frm.doc.status === "Closed") { + frm.add_custom_button(__("Re-open"), function() { + frm.events.close_open_production_plan(frm, false); + }, __("Status")); + } else { + frm.add_custom_button(__("Close"), function() { + frm.events.close_open_production_plan(frm, true); + }, __("Status")); + } + } } - if (frm.doc.docstatus === 1 && frm.doc.po_items - && frm.doc.status != 'Completed') { - frm.add_custom_button(__("Work Order"), ()=> { - frm.trigger("make_work_order"); - }, __('Create')); + if (frm.doc.status !== "Closed") { + frm.page.set_inner_btn_group_as_primary(__('Create')); } - - if (frm.doc.docstatus === 1 && frm.doc.mr_items - && !in_list(['Material Requested', 'Completed'], frm.doc.status)) { - frm.add_custom_button(__("Material Request"), ()=> { - frm.trigger("make_material_request"); - }, __('Create')); - } - - frm.page.set_inner_btn_group_as_primary(__('Create')); frm.trigger("material_requirement"); const projected_qty_formula = `