diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py index 53838fbd17..4c628a4d95 100644 --- a/erpnext/accounts/doctype/budget/budget.py +++ b/erpnext/accounts/doctype/budget/budget.py @@ -184,6 +184,11 @@ def validate_budget_records(args, budget_records, expense_amount): amount = expense_amount or get_amount(args, budget) yearly_action, monthly_action = get_actions(args, budget) + if yearly_action in ("Stop", "Warn"): + compare_expense_with_budget( + args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount + ) + if monthly_action in ["Stop", "Warn"]: budget_amount = get_accumulated_monthly_budget( budget.monthly_distribution, args.posting_date, args.fiscal_year, budget.budget_amount @@ -195,28 +200,28 @@ def validate_budget_records(args, budget_records, expense_amount): args, budget_amount, _("Accumulated Monthly"), monthly_action, budget.budget_against, amount ) - if ( - yearly_action in ("Stop", "Warn") - and monthly_action != "Stop" - and yearly_action != monthly_action - ): - compare_expense_with_budget( - args, flt(budget.budget_amount), _("Annual"), yearly_action, budget.budget_against, amount - ) - def compare_expense_with_budget(args, budget_amount, action_for, action, budget_against, amount=0): - actual_expense = amount or get_actual_expense(args) - if actual_expense > budget_amount: - diff = actual_expense - budget_amount + actual_expense = get_actual_expense(args) + total_expense = actual_expense + amount + + if total_expense > budget_amount: + if actual_expense > budget_amount: + error_tense = _("is already") + diff = actual_expense - budget_amount + else: + error_tense = _("will be") + diff = total_expense - budget_amount + currency = frappe.get_cached_value("Company", args.company, "default_currency") - msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It will exceed by {5}").format( + msg = _("{0} Budget for Account {1} against {2} {3} is {4}. It {5} exceed by {6}").format( _(action_for), frappe.bold(args.account), - args.budget_against_field, + frappe.unscrub(args.budget_against_field), frappe.bold(budget_against), frappe.bold(fmt_money(budget_amount, currency=currency)), + error_tense, frappe.bold(fmt_money(diff, currency=currency)), ) @@ -227,9 +232,9 @@ def compare_expense_with_budget(args, budget_amount, action_for, action, budget_ action = "Warn" if action == "Stop": - frappe.throw(msg, BudgetError) + frappe.throw(msg, BudgetError, title=_("Budget Exceeded")) else: - frappe.msgprint(msg, indicator="orange") + frappe.msgprint(msg, indicator="orange", title=_("Budget Exceeded")) def get_actions(args, budget): @@ -351,7 +356,9 @@ def get_actual_expense(args): """ select sum(gle.debit) - sum(gle.credit) from `tabGL Entry` gle - where gle.account=%(account)s + where + is_cancelled = 0 + and gle.account=%(account)s {condition1} and gle.fiscal_year=%(fiscal_year)s and gle.company=%(company)s diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 3f69d5c7cd..498fc7c295 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -137,8 +137,7 @@ "fieldname": "finance_book", "fieldtype": "Link", "label": "Finance Book", - "options": "Finance Book", - "read_only": 1 + "options": "Finance Book" }, { "fieldname": "2_add_edit_gl_entries", @@ -539,7 +538,7 @@ "idx": 176, "is_submittable": 1, "links": [], - "modified": "2022-11-28 17:40:01.241908", + "modified": "2023-01-17 12:53:53.280620", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py index b543016eaa..a1239d64a0 100644 --- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py +++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py @@ -675,7 +675,7 @@ def get_bin_qty(item_code, warehouse): def get_pos_reserved_qty(item_code, warehouse): reserved_qty = frappe.db.sql( - """select sum(p_item.qty) as qty + """select sum(p_item.stock_qty) as qty from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item where p.name = p_item.parent and ifnull(p.consolidated_invoice, '') = '' diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py index 1ce780eac8..57feaa03eb 100644 --- a/erpnext/accounts/doctype/pricing_rule/utils.py +++ b/erpnext/accounts/doctype/pricing_rule/utils.py @@ -687,11 +687,21 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None): def apply_pricing_rule_for_free_items(doc, pricing_rule_args): if pricing_rule_args: - items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item) + args = {(d["item_code"], d["pricing_rules"]): d for d in pricing_rule_args} - for args in pricing_rule_args: - if not items or (args.get("item_code"), args.get("pricing_rules")) not in items: - doc.append("items", args) + for item in doc.items: + if not item.is_free_item: + continue + + free_item_data = args.get((item.item_code, item.pricing_rules)) + if free_item_data: + free_item_data.pop("item_name") + free_item_data.pop("description") + item.update(free_item_data) + args.pop((item.item_code, item.pricing_rules)) + + for free_item in args.values(): + doc.append("items", free_item) def get_pricing_rule_items(pr_doc, other_items=False) -> list: diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index e96847e1b6..0ffd9463e6 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1169,6 +1169,46 @@ class TestSalesInvoice(unittest.TestCase): frappe.db.sql("delete from `tabPOS Profile`") + def test_bin_details_of_packed_item(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Test Product Bundle Item New"): + bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item New 1", {"is_stock_item": 1}) + make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2) + + si = create_sales_invoice( + item_code="_Test Product Bundle Item New", + update_stock=1, + warehouse="_Test Warehouse - _TC", + transaction_date=add_days(nowdate(), -1), + do_not_submit=1, + ) + + make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100) + + bin_details = frappe.db.get_value( + "Bin", + {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"}, + ["actual_qty", "projected_qty", "ordered_qty"], + as_dict=1, + ) + + si.transaction_date = nowdate() + si.save() + + packed_item = si.packed_items[0] + self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty)) + self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty)) + self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty)) + def test_pos_si_without_payment(self): make_pos_profile() diff --git a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json index ce8c0c3708..46b430c659 100644 --- a/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json +++ b/erpnext/accounts/doctype/tax_withheld_vouchers/tax_withheld_vouchers.json @@ -1,6 +1,6 @@ { "actions": [], - "autoname": "autoincrement", + "autoname": "hash", "creation": "2022-09-13 16:18:59.404842", "doctype": "DocType", "editable_grid": 1, @@ -36,11 +36,11 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2022-09-13 23:40:41.479208", + "modified": "2023-01-13 13:40:41.479208", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withheld Vouchers", - "naming_rule": "Autoincrement", + "naming_rule": "Random", "owner": "Administrator", "permissions": [], "sort_field": "modified", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 1bce43fd31..2c829b258b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -410,12 +410,26 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): tds_amount = 0 invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1} + ## for TDS to be deducted on advances + payment_entry_filters = { + "party_type": "Supplier", + "party": ("in", parties), + "docstatus": 1, + "apply_tax_withholding_amount": 1, + "unallocated_amount": (">", 0), + "posting_date": ["between", (tax_details.from_date, tax_details.to_date)], + "tax_withholding_category": tax_details.get("tax_withholding_category"), + } + field = "sum(tax_withholding_net_total)" if cint(tax_details.consider_party_ledger_amount): invoice_filters.pop("apply_tds", None) field = "sum(grand_total)" + payment_entry_filters.pop("apply_tax_withholding_amount", None) + payment_entry_filters.pop("tax_withholding_category", None) + supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0 supp_jv_credit_amt = ( @@ -427,14 +441,28 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers): "party": ("in", parties), "reference_type": ("!=", "Purchase Invoice"), }, - "sum(credit_in_account_currency)", + "sum(credit_in_account_currency - debit_in_account_currency)", ) or 0.0 ) + # Get Amount via payment entry + payment_entry_amounts = frappe.db.get_all( + "Payment Entry", + filters=payment_entry_filters, + fields=["sum(unallocated_amount) as amount", "payment_type"], + group_by="payment_type", + ) + supp_credit_amt += supp_jv_credit_amt supp_credit_amt += inv.tax_withholding_net_total + for type in payment_entry_amounts: + if type.payment_type == "Pay": + supp_credit_amt += type.amount + else: + supp_credit_amt -= type.amount + threshold = tax_details.get("threshold", 0) cumulative_threshold = tax_details.get("cumulative_threshold", 0) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 23caac047a..1e86cf5d2e 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -16,7 +16,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): def setUpClass(self): # create relevant supplier, etc create_records() - create_tax_with_holding_category() + create_tax_withholding_category_records() def tearDown(self): cancel_invoices() @@ -38,7 +38,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): pi = create_purchase_invoice(supplier="Test TDS Supplier") pi.submit() - # assert equal tax deduction on total invoice amount uptil now + # assert equal tax deduction on total invoice amount until now self.assertEqual(pi.taxes_and_charges_deducted, 3000) self.assertEqual(pi.grand_total, 7000) invoices.append(pi) @@ -47,7 +47,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): pi = create_purchase_invoice(supplier="Test TDS Supplier", rate=5000) pi.submit() - # assert equal tax deduction on total invoice amount uptil now + # assert equal tax deduction on total invoice amount until now self.assertEqual(pi.taxes_and_charges_deducted, 500) invoices.append(pi) @@ -130,7 +130,7 @@ class TestTaxWithholdingCategory(unittest.TestCase): invoices.append(si) # create another invoice whose total when added to previously created invoice, - # surpasses cumulative threshhold + # surpasses cumulative threshold si = create_sales_invoice(customer="Test TCS Customer", rate=12000) si.submit() @@ -329,6 +329,38 @@ class TestTaxWithholdingCategory(unittest.TestCase): for d in reversed(invoices): d.cancel() + def test_tax_withholding_via_payment_entry_for_advances(self): + frappe.db.set_value( + "Supplier", "Test TDS Supplier7", "tax_withholding_category", "Advance TDS Category" + ) + + # create payment entry + pe1 = create_payment_entry( + payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000 + ) + pe1.submit() + + self.assertFalse(pe1.get("taxes")) + + pe2 = create_payment_entry( + payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000 + ) + pe2.submit() + + self.assertFalse(pe2.get("taxes")) + + pe3 = create_payment_entry( + payment_type="Pay", party_type="Supplier", party="Test TDS Supplier7", paid_amount=4000 + ) + pe3.apply_tax_withholding_amount = 1 + pe3.save() + pe3.submit() + + self.assertEquals(pe3.get("taxes")[0].tax_amount, 1200) + pe1.cancel() + pe2.cancel() + pe3.cancel() + def cancel_invoices(): purchase_invoices = frappe.get_all( @@ -450,6 +482,32 @@ def create_sales_invoice(**args): return si +def create_payment_entry(**args): + # return payment entry doc object + args = frappe._dict(args) + pe = frappe.get_doc( + { + "doctype": "Payment Entry", + "posting_date": today(), + "payment_type": args.payment_type, + "party_type": args.party_type, + "party": args.party, + "company": "_Test Company", + "paid_from": "Cash - _TC", + "paid_to": "Creditors - _TC", + "paid_amount": args.paid_amount or 10000, + "received_amount": args.paid_amount or 10000, + "reference_no": args.reference_no or "12345", + "reference_date": today(), + "paid_from_account_currency": "INR", + "paid_to_account_currency": "INR", + } + ) + + pe.save() + return pe + + def create_records(): # create a new suppliers for name in [ @@ -460,6 +518,7 @@ def create_records(): "Test TDS Supplier4", "Test TDS Supplier5", "Test TDS Supplier6", + "Test TDS Supplier7", ]: if frappe.db.exists("Supplier", name): continue @@ -530,142 +589,129 @@ def create_records(): ).insert() -def create_tax_with_holding_category(): +def create_tax_withholding_category_records(): fiscal_year = get_fiscal_year(today(), company="_Test Company") + from_date = fiscal_year[1] + to_date = fiscal_year[2] + # Cumulative threshold - if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TDS"): - frappe.get_doc( - { - "doctype": "Tax Withholding Category", - "name": "Cumulative Threshold TDS", - "category_name": "10% TDS", - "rates": [ - { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 0, - "cumulative_threshold": 30000.00, - } - ], - "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], - } - ).insert() + create_tax_withholding_category( + category_name="Cumulative Threshold TDS", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=0, + cumulative_threshold=30000.00, + ) - if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"): - frappe.get_doc( - { - "doctype": "Tax Withholding Category", - "name": "Cumulative Threshold TCS", - "category_name": "10% TCS", - "rates": [ - { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 0, - "cumulative_threshold": 30000.00, - } - ], - "accounts": [{"company": "_Test Company", "account": "TCS - _TC"}], - } - ).insert() + # Category for TCS + create_tax_withholding_category( + category_name="Cumulative Threshold TCS", + rate=10, + from_date=from_date, + to_date=to_date, + account="TCS - _TC", + single_threshold=0, + cumulative_threshold=30000.00, + ) - # Single thresold - if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"): - frappe.get_doc( - { - "doctype": "Tax Withholding Category", - "name": "Single Threshold TDS", - "category_name": "10% TDS", - "rates": [ - { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 20000.00, - "cumulative_threshold": 0, - } - ], - "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], - } - ).insert() + # Single threshold + create_tax_withholding_category( + category_name="Single Threshold TDS", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=20000, + cumulative_threshold=0, + ) - if not frappe.db.exists("Tax Withholding Category", "New TDS Category"): - frappe.get_doc( - { - "doctype": "Tax Withholding Category", - "name": "New TDS Category", - "category_name": "New TDS Category", - "round_off_tax_amount": 1, - "consider_party_ledger_amount": 1, - "tax_on_excess_amount": 1, - "rates": [ - { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 0, - "cumulative_threshold": 30000, - } - ], - "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], - } - ).insert() + create_tax_withholding_category( + category_name="New TDS Category", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=0, + cumulative_threshold=30000, + round_off_tax_amount=1, + consider_party_ledger_amount=1, + tax_on_excess_amount=1, + ) - if not frappe.db.exists("Tax Withholding Category", "Test Service Category"): - frappe.get_doc( - { - "doctype": "Tax Withholding Category", - "name": "Test Service Category", - "category_name": "Test Service Category", - "rates": [ - { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 2000, - "cumulative_threshold": 2000, - } - ], - "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], - } - ).insert() + create_tax_withholding_category( + category_name="Test Service Category", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=2000, + cumulative_threshold=2000, + ) - if not frappe.db.exists("Tax Withholding Category", "Test Goods Category"): - frappe.get_doc( - { - "doctype": "Tax Withholding Category", - "name": "Test Goods Category", - "category_name": "Test Goods Category", - "rates": [ - { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 2000, - "cumulative_threshold": 2000, - } - ], - "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], - } - ).insert() + create_tax_withholding_category( + category_name="Test Goods Category", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=2000, + cumulative_threshold=2000, + ) - if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"): + create_tax_withholding_category( + category_name="Test Multi Invoice Category", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=5000, + cumulative_threshold=10000, + ) + + create_tax_withholding_category( + category_name="Advance TDS Category", + rate=10, + from_date=from_date, + to_date=to_date, + account="TDS - _TC", + single_threshold=5000, + cumulative_threshold=10000, + consider_party_ledger_amount=1, + ) + + +def create_tax_withholding_category( + category_name, + rate, + from_date, + to_date, + account, + single_threshold=0, + cumulative_threshold=0, + round_off_tax_amount=0, + consider_party_ledger_amount=0, + tax_on_excess_amount=0, +): + if not frappe.db.exists("Tax Withholding Category", category_name): frappe.get_doc( { "doctype": "Tax Withholding Category", - "name": "Test Multi Invoice Category", - "category_name": "Test Multi Invoice Category", + "name": category_name, + "category_name": category_name, + "round_off_tax_amount": round_off_tax_amount, + "consider_party_ledger_amount": consider_party_ledger_amount, + "tax_on_excess_amount": tax_on_excess_amount, "rates": [ { - "from_date": fiscal_year[1], - "to_date": fiscal_year[2], - "tax_withholding_rate": 10, - "single_threshold": 5000, - "cumulative_threshold": 10000, + "from_date": from_date, + "to_date": to_date, + "tax_withholding_rate": rate, + "single_threshold": single_threshold, + "cumulative_threshold": cumulative_threshold, } ], - "accounts": [{"company": "_Test Company", "account": "TDS - _TC"}], + "accounts": [{"company": "_Test Company", "account": account}], } ).insert() diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py index 6cc86c3efe..3e11643776 100644 --- a/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py +++ b/erpnext/accounts/report/deferred_revenue_and_expense/deferred_revenue_and_expense.py @@ -378,15 +378,14 @@ class Deferred_Revenue_and_Expense_Report(object): ret += [{}] # add total row - if ret is not []: - if self.filters.type == "Revenue": - total_row = frappe._dict({"name": "Total Deferred Income"}) - elif self.filters.type == "Expense": - total_row = frappe._dict({"name": "Total Deferred Expense"}) + if self.filters.type == "Revenue": + total_row = frappe._dict({"name": "Total Deferred Income"}) + elif self.filters.type == "Expense": + total_row = frappe._dict({"name": "Total Deferred Expense"}) - for idx, period in enumerate(self.period_list, 0): - total_row[period.key] = self.period_total[idx].total - ret.append(total_row) + for idx, period in enumerate(self.period_list, 0): + total_row[period.key] = self.period_total[idx].total + ret.append(total_row) return ret diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py index 572d9d3865..f0360b27dc 100644 --- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py @@ -889,6 +889,11 @@ class TestPurchaseOrder(FrappeTestCase): self.assertEqual(po.status, "Completed") self.assertEqual(mr.status, "Received") + def test_variant_item_po(self): + po = create_purchase_order(item_code="_Test Variant Item", qty=1, rate=100, do_not_save=1) + + self.assertRaises(frappe.ValidationError, po.save) + def prepare_data_for_internal_transfer(): from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier @@ -994,8 +999,8 @@ def create_purchase_order(**args): }, ) - po.set_missing_values() if not args.do_not_save: + po.set_missing_values() po.insert() if not args.do_not_submit: if po.is_subcontracted: diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index cd1168d4ac..8b4d28bc7d 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -22,7 +22,7 @@ class SellingController(StockController): def onload(self): super(SellingController, self).onload() if self.doctype in ("Sales Order", "Delivery Note", "Sales Invoice"): - for item in self.get("items"): + for item in self.get("items") + (self.get("packed_items") or []): item.update(get_bin_details(item.item_code, item.warehouse, include_child_warehouses=True)) def validate(self): diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index d4972973d0..dd2a67032f 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -58,7 +58,7 @@ status_map = { "eval:(self.per_delivered == 100 or self.skip_delivery_note) and self.per_billed == 100 and self.docstatus == 1", ], ["Cancelled", "eval:self.docstatus==2"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ["On Hold", "eval:self.status=='On Hold'"], ], "Purchase Order": [ @@ -79,7 +79,7 @@ status_map = { ["Delivered", "eval:self.status=='Delivered'"], ["Cancelled", "eval:self.docstatus==2"], ["On Hold", "eval:self.status=='On Hold'"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], "Delivery Note": [ ["Draft", None], @@ -87,7 +87,7 @@ status_map = { ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Cancelled", "eval:self.docstatus==2"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], "Purchase Receipt": [ ["Draft", None], @@ -95,7 +95,7 @@ status_map = { ["Return Issued", "eval:self.per_returned == 100 and self.docstatus == 1"], ["Completed", "eval:self.per_billed == 100 and self.docstatus == 1"], ["Cancelled", "eval:self.docstatus==2"], - ["Closed", "eval:self.status=='Closed'"], + ["Closed", "eval:self.status=='Closed' and self.docstatus != 2"], ], "Material Request": [ ["Draft", None], diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py index 828c655e79..bbe04d5514 100644 --- a/erpnext/e_commerce/doctype/website_item/test_website_item.py +++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py @@ -174,7 +174,10 @@ class TestWebsiteItem(unittest.TestCase): # Website Item Portal Tests Begin def test_website_item_breadcrumbs(self): - "Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group." + """ + Check if breadcrumbs include homepage, product listing navigation page, + parent item group(s) and item group + """ from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups item_code = "Test Breadcrumb Item" @@ -197,7 +200,7 @@ class TestWebsiteItem(unittest.TestCase): breadcrumbs = get_parent_item_groups(item.item_group) self.assertEqual(breadcrumbs[0]["name"], "Home") - self.assertEqual(breadcrumbs[1]["name"], "Shop by Category") + self.assertEqual(breadcrumbs[1]["name"], "All Products") self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1") diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js index 4dd8205a70..4304193afa 100644 --- a/erpnext/manufacturing/doctype/bom/bom.js +++ b/erpnext/manufacturing/doctype/bom/bom.js @@ -65,7 +65,21 @@ frappe.ui.form.on("BOM", { }); }, - onload_post_render(frm) { + validate: function(frm) { + if (frm.doc.fg_based_operating_cost && frm.doc.with_operations) { + frappe.throw({message: __("Please check either with operations or FG Based Operating Cost."), title: __("Mandatory")}); + } + }, + + with_operations: function(frm) { + frm.set_df_property("fg_based_operating_cost", "hidden", frm.doc.with_operations ? 1 : 0); + }, + + fg_based_operating_cost: function(frm) { + frm.set_df_property("with_operations", "hidden", frm.doc.fg_based_operating_cost ? 1 : 0); + }, + + onload_post_render: function(frm) { frm.get_field("items").grid.set_multiple_add("item_code", "qty"); }, @@ -532,18 +546,25 @@ erpnext.bom.update_cost = function(doc) { }; erpnext.bom.calculate_op_cost = function(doc) { - var op = doc.operations || []; doc.operating_cost = 0.0; doc.base_operating_cost = 0.0; - for(var i=0;i { + let operating_cost = flt(flt(item.hour_rate) * flt(item.time_in_mins) / 60, 2); + let base_operating_cost = flt(operating_cost * doc.conversion_rate, 2); + frappe.model.set_value('BOM Operation',item.name, { + "operating_cost": operating_cost, + "base_operating_cost": base_operating_cost + }); - doc.operating_cost += operating_cost; - doc.base_operating_cost += base_operating_cost; + doc.operating_cost += operating_cost; + doc.base_operating_cost += base_operating_cost; + }); + } else if(doc.fg_based_operating_cost) { + let total_operating_cost = doc.quantity * flt(doc.operating_cost_per_bom_quantity); + doc.operating_cost = total_operating_cost; + doc.base_operating_cost = flt(total_operating_cost * doc.conversion_rate, 2); } refresh_field(['operating_cost', 'base_operating_cost']); }; diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index c31b69f3dc..c2b331fcfd 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -33,6 +33,9 @@ "column_break_23", "transfer_material_against", "routing", + "fg_based_operating_cost", + "fg_based_section_section", + "operating_cost_per_bom_quantity", "operations_section", "operations", "materials_section", @@ -575,7 +578,26 @@ { "fieldname": "scrap_items_section", "fieldtype": "Section Break", + "hide_border": 1, "label": "Scrap Items" + }, + { + "default": "0", + "fieldname": "fg_based_operating_cost", + "fieldtype": "Check", + "label": "FG based Operating Cost" + }, + { + "depends_on": "fg_based_operating_cost", + "fieldname": "fg_based_section_section", + "fieldtype": "Section Break", + "label": "FG Based Operating Cost Section" + }, + { + "depends_on": "fg_based_operating_cost", + "fieldname": "operating_cost_per_bom_quantity", + "fieldtype": "Currency", + "label": "Operating Cost Per BOM Quantity" } ], "icon": "fa fa-sitemap", @@ -583,7 +605,7 @@ "image_field": "image", "is_submittable": 1, "links": [], - "modified": "2023-01-03 18:42:27.732107", + "modified": "2023-01-10 07:47:08.652616", "modified_by": "Administrator", "module": "Manufacturing", "name": "BOM", diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 53af28df8a..8ab79e68be 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -614,18 +614,26 @@ class BOM(WebsiteGenerator): """Update workstation rate and calculates totals""" self.operating_cost = 0 self.base_operating_cost = 0 - for d in self.get("operations"): - if d.workstation: - self.update_rate_and_time(d, update_hour_rate) + if self.get("with_operations"): + for d in self.get("operations"): + if d.workstation: + self.update_rate_and_time(d, update_hour_rate) - operating_cost = d.operating_cost - base_operating_cost = d.base_operating_cost - if d.set_cost_based_on_bom_qty: - operating_cost = flt(d.cost_per_unit) * flt(self.quantity) - base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity) + operating_cost = d.operating_cost + base_operating_cost = d.base_operating_cost + if d.set_cost_based_on_bom_qty: + operating_cost = flt(d.cost_per_unit) * flt(self.quantity) + base_operating_cost = flt(d.base_cost_per_unit) * flt(self.quantity) - self.operating_cost += flt(operating_cost) - self.base_operating_cost += flt(base_operating_cost) + self.operating_cost += flt(operating_cost) + self.base_operating_cost += flt(base_operating_cost) + + elif self.get("fg_based_operating_cost"): + total_operating_cost = flt(self.get("quantity")) * flt( + self.get("operating_cost_per_bom_quantity") + ) + self.operating_cost = total_operating_cost + self.base_operating_cost = flt(total_operating_cost * self.conversion_rate, 2) def update_rate_and_time(self, row, update_hour_rate=False): if not row.hour_rate or update_hour_rate: diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py index 16f5c79372..d60feb2b39 100644 --- a/erpnext/manufacturing/doctype/bom/test_bom.py +++ b/erpnext/manufacturing/doctype/bom/test_bom.py @@ -202,6 +202,33 @@ class TestBOM(FrappeTestCase): self.assertEqual(bom.items[0].rate, 20) + def test_bom_cost_with_fg_based_operating_cost(self): + bom = frappe.copy_doc(test_records[4]) + bom.insert() + + raw_material_cost = 0.0 + op_cost = 0.0 + + op_cost = bom.quantity * bom.operating_cost_per_bom_quantity + + for row in bom.items: + raw_material_cost += row.amount + + base_raw_material_cost = raw_material_cost * flt( + bom.conversion_rate, bom.precision("conversion_rate") + ) + base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate")) + + # test amounts in selected currency, almostEqual checks for 7 digits by default + self.assertAlmostEqual(bom.operating_cost, op_cost) + self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost) + self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost) + + # test amounts in selected currency + self.assertAlmostEqual(bom.base_operating_cost, base_op_cost) + self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost) + self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost) + def test_subcontractor_sourced_item(self): item_code = "_Test Subcontracted FG Item 1" set_backflush_based_on("Material Transferred for Subcontract") diff --git a/erpnext/manufacturing/doctype/bom/test_records.json b/erpnext/manufacturing/doctype/bom/test_records.json index 507d319b51..e9cbdfe638 100644 --- a/erpnext/manufacturing/doctype/bom/test_records.json +++ b/erpnext/manufacturing/doctype/bom/test_records.json @@ -162,5 +162,31 @@ "item": "_Test Variant Item", "quantity": 1.0, "with_operations": 1 + }, + { + "items": [ + { + "amount": 5000.0, + "doctype": "BOM Item", + "item_code": "_Test Item", + "parentfield": "items", + "qty": 2.0, + "rate": 3000.0, + "uom": "_Test UOM", + "stock_uom": "_Test UOM", + "source_warehouse": "_Test Warehouse - _TC", + "include_item_in_manufacturing": 1 + } + ], + "docstatus": 1, + "doctype": "BOM", + "is_active": 1, + "is_default": 1, + "currency": "USD", + "item": "_Test Variant Item", + "quantity": 1.0, + "with_operations": 0, + "fg_based_operating_cost": 1, + "operating_cost_per_bom_quantity": 140 } ] diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 41067d8dfa..6744d16254 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -324,3 +324,4 @@ erpnext.patches.v14_0.create_incoterms_and_migrate_shipment erpnext.patches.v14_0.setup_clear_repost_logs erpnext.patches.v14_0.create_accounting_dimensions_for_payment_request erpnext.patches.v14_0.update_entry_type_for_journal_entry +erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers diff --git a/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py b/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py new file mode 100644 index 0000000000..e20ba73dbb --- /dev/null +++ b/erpnext/patches/v14_0/change_autoname_for_tax_withheld_vouchers.py @@ -0,0 +1,12 @@ +import frappe + + +def execute(): + if ( + frappe.db.sql( + """select data_type FROM information_schema.columns + where column_name = 'name' and table_name = 'tabTax Withheld Vouchers'""" + )[0][0] + == "bigint" + ): + frappe.db.change_column_type("Tax Withheld Vouchers", "name", "varchar(140)") diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 2dde542d2d..ce3ae4fc7c 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -80,7 +80,7 @@ class Task(NestedSet): if frappe.db.get_value("Task", d.task, "status") not in ("Completed", "Cancelled"): frappe.throw( _( - "Cannot complete task {0} as its dependant task {1} are not ccompleted / cancelled." + "Cannot complete task {0} as its dependant task {1} are not completed / cancelled." ).format(frappe.bold(self.name), frappe.bold(d.task)) ) diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index 1179364b83..f3bd09a67a 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -387,6 +387,9 @@ def make_sales_invoice(source_name, item_code=None, customer=None, currency=None "timesheets", { "time_sheet": timesheet.name, + "project_name": time_log.project_name, + "from_time": time_log.from_time, + "to_time": time_log.to_time, "billing_hours": time_log.billing_hours, "billing_amount": time_log.billing_amount, "timesheet_detail": time_log.name, diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js index 51664f8885..911343d8b6 100644 --- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js +++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js @@ -1,7 +1,7 @@ frappe.provide("erpnext.accounts.bank_reconciliation"); erpnext.accounts.bank_reconciliation.DialogManager = class DialogManager { - constructor(company, bank_account) { + constructor(company, bank_account, bank_statement_from_date, bank_statement_to_date, filter_by_reference_date, from_reference_date, to_reference_date) { this.bank_account = bank_account; this.company = company; this.make_dialog(); diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 1f8a5e39f2..2ce0c7eb00 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -122,24 +122,16 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments { calculate_item_values() { var me = this; if (!this.discount_amount_applied) { - $.each(this.frm.doc["items"] || [], function(i, item) { + for (const item of this.frm.doc.items || []) { frappe.model.round_floats_in(item); item.net_rate = item.rate; - - if ((!item.qty) && me.frm.doc.is_return) { - item.amount = flt(item.rate * -1, precision("amount", item)); - } else if ((!item.qty) && me.frm.doc.is_debit_note) { - item.amount = flt(item.rate, precision("amount", item)); - } else { - item.amount = flt(item.rate * item.qty, precision("amount", item)); - } - - item.net_amount = item.amount; + item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty; + item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item)); item.item_tax_amount = 0.0; item.total_weight = flt(item.weight_per_unit * item.stock_qty); me.set_in_company_currency(item, ["price_list_rate", "rate", "amount", "net_rate", "net_amount"]); - }); + } } } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 5c1c6d1919..3a778fa496 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1473,6 +1473,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe "parenttype": d.parenttype, "parent": d.parent, "pricing_rules": d.pricing_rules, + "is_free_item": d.is_free_item, "warehouse": d.warehouse, "serial_no": d.serial_no, "batch_no": d.batch_no, diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index accf5f22a6..ca6a51a6f3 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -26,7 +26,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import ( from erpnext.selling.doctype.customer.customer import check_credit_limit from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.stock.doctype.item.item import get_item_defaults -from erpnext.stock.get_item_details import get_default_bom +from erpnext.stock.get_item_details import get_default_bom, get_price_list_rate from erpnext.stock.stock_balance import get_reserved_qty, update_bin_qty form_grid_templates = {"items": "templates/form_grid/item_grid.html"} @@ -590,6 +590,23 @@ def make_material_request(source_name, target_doc=None): target.qty = qty - requested_item_qty.get(source.name, 0) target.stock_qty = flt(target.qty) * flt(target.conversion_factor) + args = target.as_dict().copy() + args.update( + { + "company": source_parent.get("company"), + "price_list": frappe.db.get_single_value("Buying Settings", "buying_price_list"), + "currency": source_parent.get("currency"), + "conversion_rate": source_parent.get("conversion_rate"), + } + ) + + target.rate = flt( + get_price_list_rate(args=args, item_doc=frappe.get_cached_doc("Item", target.item_code)).get( + "price_list_rate" + ) + ) + target.amount = target.qty * target.rate + doc = get_mapped_doc( "Sales Order", source_name, diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index e777f52f7a..d4d7c58eb8 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -552,6 +552,42 @@ class TestSalesOrder(FrappeTestCase): workflow.is_active = 0 workflow.save() + def test_bin_details_of_packed_item(self): + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Test Product Bundle Item New"): + bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item New 1", {"is_stock_item": 1}) + make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2) + + so = make_sales_order( + item_code="_Test Product Bundle Item New", + warehouse="_Test Warehouse - _TC", + transaction_date=add_days(nowdate(), -1), + do_not_submit=1, + ) + + make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100) + + bin_details = frappe.db.get_value( + "Bin", + {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"}, + ["actual_qty", "projected_qty", "ordered_qty"], + as_dict=1, + ) + + so.transaction_date = nowdate() + so.save() + + packed_item = so.packed_items[0] + self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty)) + self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty)) + self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty)) + def test_update_child_product_bundle(self): # test Update Items with product bundle if not frappe.db.exists("Item", "_Product Bundle Item"): diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 95bbf84616..2fdfcf647d 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -148,12 +148,12 @@ def get_item_for_list_in_html(context): def get_parent_item_groups(item_group_name, from_item=False): - base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"} + base_nav_page = {"name": _("All Products"), "route": "/all-products"} if from_item and frappe.request.environ.get("HTTP_REFERER"): # base page after 'Home' will vary on Item page last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0] - if last_page and last_page in ("shop-by-category", "all-products"): + if last_page and last_page == "shop-by-category": base_nav_page_title = " ".join(last_page.split("-")).title() base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page} diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index d747383d6a..903e2af3cb 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -490,6 +490,46 @@ class TestDeliveryNote(FrappeTestCase): self.assertEqual(gle_warehouse_amount, 1400) + def test_bin_details_of_packed_item(self): + from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle + from erpnext.stock.doctype.item.test_item import make_item + + # test Update Items with product bundle + if not frappe.db.exists("Item", "_Test Product Bundle Item New"): + bundle_item = make_item("_Test Product Bundle Item New", {"is_stock_item": 0}) + bundle_item.append( + "item_defaults", {"company": "_Test Company", "default_warehouse": "_Test Warehouse - _TC"} + ) + bundle_item.save(ignore_permissions=True) + + make_item("_Packed Item New 1", {"is_stock_item": 1}) + make_product_bundle("_Test Product Bundle Item New", ["_Packed Item New 1"], 2) + + si = create_delivery_note( + item_code="_Test Product Bundle Item New", + update_stock=1, + warehouse="_Test Warehouse - _TC", + transaction_date=add_days(nowdate(), -1), + do_not_submit=1, + ) + + make_stock_entry(item="_Packed Item New 1", target="_Test Warehouse - _TC", qty=120, rate=100) + + bin_details = frappe.db.get_value( + "Bin", + {"item_code": "_Packed Item New 1", "warehouse": "_Test Warehouse - _TC"}, + ["actual_qty", "projected_qty", "ordered_qty"], + as_dict=1, + ) + + si.transaction_date = nowdate() + si.save() + + packed_item = si.packed_items[0] + self.assertEqual(flt(bin_details.actual_qty), flt(packed_item.actual_qty)) + self.assertEqual(flt(bin_details.projected_qty), flt(packed_item.projected_qty)) + self.assertEqual(flt(bin_details.ordered_qty), flt(packed_item.ordered_qty)) + def test_return_for_serialized_items(self): se = make_serialized_item() serial_no = get_serial_nos(se.get("items")[0].serial_no)[0] @@ -650,6 +690,11 @@ class TestDeliveryNote(FrappeTestCase): update_delivery_note_status(dn.name, "Closed") self.assertEqual(frappe.db.get_value("Delivery Note", dn.name, "Status"), "Closed") + # Check cancelling closed delivery note + dn.load_from_db() + dn.cancel() + self.assertEqual(dn.status, "Cancelled") + def test_dn_billing_status_case1(self): # SO -> DN -> SI so = make_sales_order() diff --git a/erpnext/stock/doctype/item_attribute/item_attribute.py b/erpnext/stock/doctype/item_attribute/item_attribute.py index 391ff06918..ac4c313e28 100644 --- a/erpnext/stock/doctype/item_attribute/item_attribute.py +++ b/erpnext/stock/doctype/item_attribute/item_attribute.py @@ -74,11 +74,10 @@ class ItemAttribute(Document): def validate_duplication(self): values, abbrs = [], [] for d in self.item_attribute_values: - d.abbr = d.abbr.upper() - if d.attribute_value in values: - frappe.throw(_("{0} must appear only once").format(d.attribute_value)) + if d.attribute_value.lower() in map(str.lower, values): + frappe.throw(_("Attribute value: {0} must appear only once").format(d.attribute_value.title())) values.append(d.attribute_value) - if d.abbr in abbrs: - frappe.throw(_("{0} must appear only once").format(d.abbr)) + if d.abbr.lower() in map(str.lower, abbrs): + frappe.throw(_("Abbreviation: {0} must appear only once").format(d.abbr.title())) abbrs.append(d.abbr) diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py index 9c6f4f4a35..808f19e274 100644 --- a/erpnext/stock/doctype/pick_list/pick_list.py +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -11,7 +11,7 @@ from frappe import _ from frappe.model.document import Document from frappe.model.mapper import map_child_doc from frappe.query_builder import Case -from frappe.query_builder.functions import Locate +from frappe.query_builder.functions import IfNull, Locate, Sum from frappe.utils import cint, floor, flt, today from frappe.utils.nestedset import get_descendants_of @@ -503,42 +503,30 @@ def get_available_item_locations_for_serialized_item( def get_available_item_locations_for_batched_item( item_code, from_warehouses, required_qty, company ): - warehouse_condition = "and warehouse in %(warehouses)s" if from_warehouses else "" - batch_locations = frappe.db.sql( - """ - SELECT - sle.`warehouse`, - sle.`batch_no`, - SUM(sle.`actual_qty`) AS `qty` - FROM - `tabStock Ledger Entry` sle, `tabBatch` batch - WHERE - sle.batch_no = batch.name - and sle.`item_code`=%(item_code)s - and sle.`company` = %(company)s - and batch.disabled = 0 - and sle.is_cancelled=0 - and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s - {warehouse_condition} - GROUP BY - sle.`warehouse`, - sle.`batch_no`, - sle.`item_code` - HAVING `qty` > 0 - ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation`, sle.`batch_no`, sle.`warehouse` - """.format( - warehouse_condition=warehouse_condition - ), - { # nosec - "item_code": item_code, - "company": company, - "today": today(), - "warehouses": from_warehouses, - }, - as_dict=1, + sle = frappe.qb.DocType("Stock Ledger Entry") + batch = frappe.qb.DocType("Batch") + + query = ( + frappe.qb.from_(sle) + .from_(batch) + .select(sle.warehouse, sle.batch_no, Sum(sle.actual_qty).as_("qty")) + .where( + (sle.batch_no == batch.name) + & (sle.item_code == item_code) + & (sle.company == company) + & (batch.disabled == 0) + & (sle.is_cancelled == 0) + & (IfNull(batch.expiry_date, "2200-01-01") > today()) + ) + .groupby(sle.warehouse, sle.batch_no, sle.item_code) + .having(Sum(sle.actual_qty) > 0) + .orderby(IfNull(batch.expiry_date, "2200-01-01"), batch.creation, sle.batch_no, sle.warehouse) ) - return batch_locations + if from_warehouses: + query = query.where(sle.warehouse.isin(from_warehouses)) + + return query.run(as_dict=True) def get_available_item_locations_for_serial_and_batched_item( diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 897fca3978..fb1f77ad3b 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -169,6 +169,8 @@ frappe.ui.form.on('Stock Entry', { }, refresh: function(frm) { + frm.trigger("get_items_from_transit_entry"); + if(!frm.doc.docstatus) { frm.trigger('validate_purpose_consumption'); frm.add_custom_button(__('Material Request'), function() { @@ -337,6 +339,28 @@ frappe.ui.form.on('Stock Entry', { } }, + get_items_from_transit_entry: function(frm) { + if (frm.doc.docstatus===0) { + frm.add_custom_button(__('Transit Entry'), function() { + erpnext.utils.map_current_doc({ + method: "erpnext.stock.doctype.stock_entry.stock_entry.make_stock_in_entry", + source_doctype: "Stock Entry", + target: frm, + date_field: "posting_date", + setters: { + stock_entry_type: "Material Transfer", + purpose: "Material Transfer", + }, + get_query_filters: { + docstatus: 1, + purpose: "Material Transfer", + add_to_transit: 1, + } + }) + }, __("Get Items From")); + } + }, + before_save: function(frm) { frm.doc.items.forEach((item) => { item.uom = item.uom || item.stock_uom; diff --git a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py index 41a3b8916d..0f9001392d 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry_utils.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry_utils.py @@ -117,6 +117,7 @@ def make_stock_entry(**args): args.item = "_Test Item" s.company = args.company or erpnext.get_default_company() + s.add_to_transit = args.add_to_transit or 0 s.purchase_receipt_no = args.purchase_receipt_no s.delivery_note_no = args.delivery_note_no s.sales_invoice_no = args.sales_invoice_no diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py index b574b718fe..38bf0a5f9e 100644 --- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py @@ -17,6 +17,7 @@ from erpnext.stock.doctype.item.test_item import ( from erpnext.stock.doctype.serial_no.serial_no import * # noqa from erpnext.stock.doctype.stock_entry.stock_entry import ( FinishedGoodError, + make_stock_in_entry, move_sample_to_retention_warehouse, ) from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry @@ -160,6 +161,53 @@ class TestStockEntry(FrappeTestCase): self.assertTrue(item_code in items) + def test_add_to_transit_entry(self): + from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse + + item_code = "_Test Transit Item" + company = "_Test Company" + + create_warehouse("Test From Warehouse") + create_warehouse("Test Transit Warehouse") + create_warehouse("Test To Warehouse") + + create_item( + item_code=item_code, + is_stock_item=1, + is_purchase_item=1, + company=company, + ) + + # create inward stock entry + make_stock_entry( + item_code=item_code, + target="Test From Warehouse - _TC", + qty=10, + basic_rate=100, + expense_account="Stock Adjustment - _TC", + cost_center="Main - _TC", + ) + + transit_entry = make_stock_entry( + item_code=item_code, + source="Test From Warehouse - _TC", + target="Test Transit Warehouse - _TC", + add_to_transit=1, + stock_entry_type="Material Transfer", + purpose="Material Transfer", + qty=10, + basic_rate=100, + expense_account="Stock Adjustment - _TC", + cost_center="Main - _TC", + ) + + end_transit_entry = make_stock_in_entry(transit_entry.name) + self.assertEqual(transit_entry.name, end_transit_entry.outgoing_stock_entry) + self.assertEqual(transit_entry.name, end_transit_entry.items[0].against_stock_entry) + self.assertEqual(transit_entry.items[0].name, end_transit_entry.items[0].ste_detail) + + # create add to transit + def test_material_receipt_gl_entry(self): company = frappe.db.get_value("Warehouse", "Stores - TCP1", "company") diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 363dc0a63f..5af144110f 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -236,8 +236,10 @@ def validate_item_details(args, item): validate_end_of_life(item.name, item.end_of_life, item.disabled) - if args.transaction_type == "selling" and cint(item.has_variants): - throw(_("Item {0} is a template, please select one of its variants").format(item.name)) + if cint(item.has_variants): + msg = f"Item {item.name} is a template, please select one of its variants" + + throw(_(msg), title=_("Template Item Selected")) elif args.transaction_type == "buying" and args.doctype != "Material Request": if args.get("is_subcontracted"): diff --git a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json index 3675a4ea08..d77e77440e 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json +++ b/erpnext/subcontracting/doctype/subcontracting_order_item/subcontracting_order_item.json @@ -1,352 +1,353 @@ { - "actions": [], - "autoname": "hash", - "creation": "2022-04-01 19:26:31.475015", - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "item_code", - "item_name", - "bom", - "include_exploded_items", - "column_break_3", - "schedule_date", - "expected_delivery_date", - "description_section", - "description", - "column_break_8", - "image", - "image_view", - "quantity_and_rate_section", - "qty", - "received_qty", - "returned_qty", - "column_break_13", - "stock_uom", - "conversion_factor", - "section_break_16", - "rate", - "amount", - "column_break_19", - "rm_cost_per_qty", - "service_cost_per_qty", - "additional_cost_per_qty", - "warehouse_section", - "warehouse", - "accounting_details_section", - "expense_account", - "manufacture_section", - "manufacturer", - "manufacturer_part_no", - "accounting_dimensions_section", - "cost_center", - "dimension_col_break", - "project", - "section_break_34", - "page_break" - ], - "fields": [ - { - "bold": 1, - "columns": 2, - "fieldname": "item_code", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Item Code", - "options": "Item", - "read_only": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fetch_from": "item_code.item_name", - "fetch_if_empty": 1, - "fieldname": "item_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Item Name", - "print_hide": 1, - "reqd": 1 - }, - { - "fieldname": "column_break_3", - "fieldtype": "Column Break" - }, - { - "bold": 1, - "columns": 2, - "fieldname": "schedule_date", - "fieldtype": "Date", - "label": "Required By", - "print_hide": 1, - "read_only": 1 - }, - { - "allow_on_submit": 1, - "bold": 1, - "fieldname": "expected_delivery_date", - "fieldtype": "Date", - "label": "Expected Delivery Date", - "search_index": 1 - }, - { - "collapsible": 1, - "fieldname": "description_section", - "fieldtype": "Section Break", - "label": "Description" - }, - { - "fetch_from": "item_code.description", - "fetch_if_empty": 1, - "fieldname": "description", - "fieldtype": "Text Editor", - "label": "Description", - "print_width": "300px", - "reqd": 1, - "width": "300px" - }, - { - "fieldname": "column_break_8", - "fieldtype": "Column Break" - }, - { - "fieldname": "image", - "fieldtype": "Attach", - "hidden": 1, - "label": "Image" - }, - { - "fieldname": "image_view", - "fieldtype": "Image", - "label": "Image View", - "options": "image", - "print_hide": 1 - }, - { - "fieldname": "quantity_and_rate_section", - "fieldtype": "Section Break", - "label": "Quantity and Rate" - }, - { - "bold": 1, - "columns": 1, - "default": "1", - "fieldname": "qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Quantity", - "print_width": "60px", - "read_only": 1, - "reqd": 1, - "width": "60px" - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break", - "print_hide": 1 - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "options": "UOM", - "print_width": "100px", - "read_only": 1, - "reqd": 1, - "width": "100px" - }, - { - "default": "1", - "fieldname": "conversion_factor", - "fieldtype": "Float", - "hidden": 1, - "label": "Conversion Factor", - "read_only": 1 - }, - { - "fieldname": "section_break_16", - "fieldtype": "Section Break" - }, - { - "bold": 1, - "columns": 2, - "fetch_from": "item_code.standard_rate", - "fetch_if_empty": 1, - "fieldname": "rate", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Rate", - "options": "currency", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "column_break_19", - "fieldtype": "Column Break" - }, - { - "columns": 2, - "fieldname": "amount", - "fieldtype": "Currency", - "in_list_view": 1, - "label": "Amount", - "options": "currency", - "read_only": 1, - "reqd": 1 - }, - { - "fieldname": "warehouse_section", - "fieldtype": "Section Break", - "label": "Warehouse Details" - }, - { - "fieldname": "warehouse", - "fieldtype": "Link", - "label": "Warehouse", - "options": "Warehouse", - "print_hide": 1, - "reqd": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_details_section", - "fieldtype": "Section Break", - "label": "Accounting Details" - }, - { - "fieldname": "expense_account", - "fieldtype": "Link", - "label": "Expense Account", - "options": "Account", - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "manufacture_section", - "fieldtype": "Section Break", - "label": "Manufacture" - }, - { - "fieldname": "manufacturer", - "fieldtype": "Link", - "label": "Manufacturer", - "options": "Manufacturer" - }, - { - "fieldname": "manufacturer_part_no", - "fieldtype": "Data", - "label": "Manufacturer Part Number" - }, - { - "depends_on": "item_code", - "fetch_from": "item_code.default_bom", - "fieldname": "bom", - "fieldtype": "Link", - "in_list_view": 1, - "label": "BOM", - "options": "BOM", - "print_hide": 1, - "reqd": 1 - }, - { - "default": "0", - "fieldname": "include_exploded_items", - "fieldtype": "Check", - "label": "Include Exploded Items", - "print_hide": 1 - }, - { - "fieldname": "service_cost_per_qty", - "fieldtype": "Currency", - "label": "Service Cost Per Qty", - "read_only": 1, - "reqd": 1 - }, - { - "default": "0", - "fieldname": "additional_cost_per_qty", - "fieldtype": "Currency", - "label": "Additional Cost Per Qty", - "read_only": 1 - }, - { - "fieldname": "rm_cost_per_qty", - "fieldtype": "Currency", - "label": "Raw Material Cost Per Qty", - "no_copy": 1, - "read_only": 1 - }, - { - "allow_on_submit": 1, - "default": "0", - "fieldname": "page_break", - "fieldtype": "Check", - "label": "Page Break", - "no_copy": 1, - "print_hide": 1 - }, - { - "fieldname": "section_break_34", - "fieldtype": "Section Break" - }, - { - "depends_on": "received_qty", - "fieldname": "received_qty", - "fieldtype": "Float", - "label": "Received Qty", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "depends_on": "returned_qty", - "fieldname": "returned_qty", - "fieldtype": "Float", - "label": "Returned Qty", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "accounting_dimensions_section", - "fieldtype": "Section Break", - "label": "Accounting Dimensions" - }, - { - "fieldname": "cost_center", - "fieldtype": "Link", - "label": "Cost Center", - "options": "Cost Center" - }, - { - "fieldname": "dimension_col_break", - "fieldtype": "Column Break" - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" - } - ], - "idx": 1, - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2022-08-15 14:25:45.177703", - "modified_by": "Administrator", - "module": "Subcontracting", - "name": "Subcontracting Order Item", - "naming_rule": "Random", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "search_fields": "item_name", - "sort_field": "modified", - "sort_order": "DESC", - "states": [], - "track_changes": 1 + "actions": [], + "autoname": "hash", + "creation": "2022-04-01 19:26:31.475015", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "bom", + "include_exploded_items", + "column_break_3", + "schedule_date", + "expected_delivery_date", + "description_section", + "description", + "column_break_8", + "image", + "image_view", + "quantity_and_rate_section", + "qty", + "received_qty", + "returned_qty", + "column_break_13", + "stock_uom", + "conversion_factor", + "section_break_16", + "rate", + "amount", + "column_break_19", + "rm_cost_per_qty", + "service_cost_per_qty", + "additional_cost_per_qty", + "warehouse_section", + "warehouse", + "accounting_details_section", + "expense_account", + "manufacture_section", + "manufacturer", + "manufacturer_part_no", + "accounting_dimensions_section", + "cost_center", + "dimension_col_break", + "project", + "section_break_34", + "page_break" + ], + "fields": [ + { + "bold": 1, + "columns": 2, + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "options": "Item", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "item_code.item_name", + "fetch_if_empty": 1, + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "bold": 1, + "columns": 2, + "fieldname": "schedule_date", + "fieldtype": "Date", + "label": "Required By", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "bold": 1, + "fieldname": "expected_delivery_date", + "fieldtype": "Date", + "label": "Expected Delivery Date", + "search_index": 1 + }, + { + "collapsible": 1, + "fieldname": "description_section", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fetch_from": "item_code.description", + "fetch_if_empty": 1, + "fieldname": "description", + "fieldtype": "Text Editor", + "label": "Description", + "print_width": "300px", + "reqd": 1, + "width": "300px" + }, + { + "fieldname": "column_break_8", + "fieldtype": "Column Break" + }, + { + "fieldname": "image", + "fieldtype": "Attach", + "hidden": 1, + "label": "Image" + }, + { + "fieldname": "image_view", + "fieldtype": "Image", + "label": "Image View", + "options": "image", + "print_hide": 1 + }, + { + "fieldname": "quantity_and_rate_section", + "fieldtype": "Section Break", + "label": "Quantity and Rate" + }, + { + "bold": 1, + "columns": 1, + "default": "1", + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Quantity", + "print_width": "60px", + "read_only": 1, + "reqd": 1, + "width": "60px" + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break", + "print_hide": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "print_width": "100px", + "read_only": 1, + "reqd": 1, + "width": "100px" + }, + { + "default": "1", + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "read_only": 1 + }, + { + "fieldname": "section_break_16", + "fieldtype": "Section Break" + }, + { + "bold": 1, + "columns": 2, + "fetch_from": "item_code.standard_rate", + "fetch_if_empty": 1, + "fieldname": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "column_break_19", + "fieldtype": "Column Break" + }, + { + "columns": 2, + "fieldname": "amount", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Amount", + "options": "currency", + "read_only": 1, + "reqd": 1 + }, + { + "fieldname": "warehouse_section", + "fieldtype": "Section Break", + "label": "Warehouse Details" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "label": "Warehouse", + "options": "Warehouse", + "print_hide": 1, + "reqd": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_details_section", + "fieldtype": "Section Break", + "label": "Accounting Details" + }, + { + "fieldname": "expense_account", + "fieldtype": "Link", + "label": "Expense Account", + "options": "Account", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "manufacture_section", + "fieldtype": "Section Break", + "label": "Manufacture" + }, + { + "fieldname": "manufacturer", + "fieldtype": "Link", + "label": "Manufacturer", + "options": "Manufacturer" + }, + { + "fieldname": "manufacturer_part_no", + "fieldtype": "Data", + "label": "Manufacturer Part Number" + }, + { + "depends_on": "item_code", + "fetch_from": "item_code.default_bom", + "fetch_if_empty": 1, + "fieldname": "bom", + "fieldtype": "Link", + "in_list_view": 1, + "label": "BOM", + "options": "BOM", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "include_exploded_items", + "fieldtype": "Check", + "label": "Include Exploded Items", + "print_hide": 1 + }, + { + "fieldname": "service_cost_per_qty", + "fieldtype": "Currency", + "label": "Service Cost Per Qty", + "read_only": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "additional_cost_per_qty", + "fieldtype": "Currency", + "label": "Additional Cost Per Qty", + "read_only": 1 + }, + { + "fieldname": "rm_cost_per_qty", + "fieldtype": "Currency", + "label": "Raw Material Cost Per Qty", + "no_copy": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "label": "Page Break", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "section_break_34", + "fieldtype": "Section Break" + }, + { + "depends_on": "received_qty", + "fieldname": "received_qty", + "fieldtype": "Float", + "label": "Received Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "depends_on": "returned_qty", + "fieldname": "returned_qty", + "fieldtype": "Float", + "label": "Returned Qty", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "accounting_dimensions_section", + "fieldtype": "Section Break", + "label": "Accounting Dimensions" + }, + { + "fieldname": "cost_center", + "fieldtype": "Link", + "label": "Cost Center", + "options": "Cost Center" + }, + { + "fieldname": "dimension_col_break", + "fieldtype": "Column Break" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + } + ], + "idx": 1, + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-01-20 23:25:45.363281", + "modified_by": "Administrator", + "module": "Subcontracting", + "name": "Subcontracting Order Item", + "naming_rule": "Random", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "search_fields": "item_name", + "sort_field": "modified", + "sort_order": "DESC", + "states": [], + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index e8faa4868f..f4fd4de169 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -262,15 +262,17 @@ class SubcontractingReceipt(SubcontractingController): def get_gl_entries(self, warehouse_account=None): from erpnext.accounts.general_ledger import process_gl_map + if not erpnext.is_perpetual_inventory_enabled(self.company): + return [] + gl_entries = [] self.make_item_gl_entries(gl_entries, warehouse_account) return process_gl_map(gl_entries) def make_item_gl_entries(self, gl_entries, warehouse_account=None): - if erpnext.is_perpetual_inventory_enabled(self.company): - stock_rbnb = self.get_company_default("stock_received_but_not_billed") - expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") + stock_rbnb = self.get_company_default("stock_received_but_not_billed") + expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation") warehouse_with_no_account = []