diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js index a964965c26..f74562086e 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js @@ -141,7 +141,7 @@ frappe.ui.form.on("Bank Statement Import", { }, show_import_status(frm) { - let import_log = JSON.parse(frm.doc.import_log || "[]"); + let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); let successful_records = import_log.filter((log) => log.success); let failed_records = import_log.filter((log) => !log.success); if (successful_records.length === 0) return; @@ -309,7 +309,7 @@ frappe.ui.form.on("Bank Statement Import", { // method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template', show_import_preview(frm, preview_data) { - let import_log = JSON.parse(frm.doc.import_log || "[]"); + let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); if ( frm.import_preview && @@ -439,7 +439,7 @@ frappe.ui.form.on("Bank Statement Import", { }, show_import_log(frm) { - let import_log = JSON.parse(frm.doc.import_log || "[]"); + let import_log = JSON.parse(frm.doc.statement_import_log || "[]"); let logs = import_log; frm.toggle_display("import_log", false); frm.toggle_display("import_log_section", logs.length > 0); diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json index 7ffff02850..eede3bdc6d 100644 --- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json +++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.json @@ -24,7 +24,7 @@ "section_import_preview", "import_preview", "import_log_section", - "import_log", + "statement_import_log", "show_failed_logs", "import_log_preview", "reference_doctype", @@ -90,12 +90,6 @@ "options": "JSON", "read_only": 1 }, - { - "fieldname": "import_log", - "fieldtype": "Code", - "label": "Import Log", - "options": "JSON" - }, { "fieldname": "import_log_section", "fieldtype": "Section Break", @@ -198,11 +192,17 @@ { "fieldname": "column_break_4", "fieldtype": "Column Break" + }, + { + "fieldname": "statement_import_log", + "fieldtype": "Code", + "label": "Statement Import Log", + "options": "JSON" } ], "hide_toolbar": 1, "links": [], - "modified": "2021-05-12 14:17:37.777246", + "modified": "2022-09-07 11:11:40.293317", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Statement Import", diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html index 82705a9cea..0da44a464e 100644 --- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html +++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html @@ -25,7 +25,7 @@
- +
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py index d4f9aba41d..9149b4d857 100644 --- a/erpnext/controllers/stock_controller.py +++ b/erpnext/controllers/stock_controller.py @@ -700,6 +700,47 @@ class StockController(AccountsController): else: create_repost_item_valuation_entry(args) + def add_gl_entry( + self, + gl_entries, + account, + cost_center, + debit, + credit, + remarks, + against_account, + debit_in_account_currency=None, + credit_in_account_currency=None, + account_currency=None, + project=None, + voucher_detail_no=None, + item=None, + posting_date=None, + ): + + gl_entry = { + "account": account, + "cost_center": cost_center, + "debit": debit, + "credit": credit, + "against": against_account, + "remarks": remarks, + } + + if voucher_detail_no: + gl_entry.update({"voucher_detail_no": voucher_detail_no}) + + if debit_in_account_currency: + gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) + + if credit_in_account_currency: + gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) + + if posting_date: + gl_entry.update({"posting_date": posting_date}) + + gl_entries.append(self.get_gl_dict(gl_entry, item=item)) + def repost_required_for_queue(doc: StockController) -> bool: """check if stock document contains repeated item-warehouse with queue based valuation. diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py index 21504774f6..bbd950ed37 100644 --- a/erpnext/controllers/subcontracting_controller.py +++ b/erpnext/controllers/subcontracting_controller.py @@ -877,6 +877,7 @@ def make_return_stock_entry_for_subcontract( { order_doctype: { "doctype": "Stock Entry", + "field_no_map": ["purchase_order", "subcontracting_order"], }, }, ignore_child_tables=True, diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py index bc503f5440..8490d14528 100644 --- a/erpnext/controllers/tests/test_subcontracting_controller.py +++ b/erpnext/controllers/tests/test_subcontracting_controller.py @@ -897,7 +897,7 @@ def make_stock_transfer_entry(**args): "item_name": row.item_code, "rate": row.rate or 100, "stock_uom": row.stock_uom or "Nos", - "warehouse": row.warehuose or "_Test Warehouse - _TC", + "warehouse": row.warehouse or "_Test Warehouse - _TC", } item_details = args.itemwise_details.get(row.item_code) @@ -1031,9 +1031,9 @@ def get_subcontracting_order(**args): if not args.service_items: service_items = [ { - "warehouse": "_Test Warehouse - _TC", + "warehouse": args.warehouse or "_Test Warehouse - _TC", "item_code": "Subcontracted Service Item 7", - "qty": 5, + "qty": 10, "rate": 100, "fg_item": "Subcontracted Item SA7", "fg_item_qty": 10, @@ -1046,6 +1046,7 @@ def get_subcontracting_order(**args): rm_items=service_items, is_subcontracted=1, supplier_warehouse=args.supplier_warehouse or "_Test Warehouse 1 - _TC", + company=args.company, ) return create_subcontracting_order(po_name=po.name, **args) diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4729add16b..d780213209 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -312,4 +312,5 @@ erpnext.patches.v13_0.fix_number_and_frequency_for_monthly_depreciation erpnext.patches.v14_0.remove_hr_and_payroll_modules # 20-07-2022 erpnext.patches.v14_0.fix_crm_no_of_employees erpnext.patches.v14_0.create_accounting_dimensions_in_subcontracting_doctypes +erpnext.patches.v14_0.fix_subcontracting_receipt_gl_entries erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger diff --git a/erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py b/erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py new file mode 100644 index 0000000000..159c6dc82d --- /dev/null +++ b/erpnext/patches/v14_0/fix_subcontracting_receipt_gl_entries.py @@ -0,0 +1,30 @@ +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +import frappe + +from erpnext.stock.report.stock_and_account_value_comparison.stock_and_account_value_comparison import ( + get_data, +) + + +def execute(): + data = [] + + for company in frappe.db.get_list("Company", pluck="name"): + data += get_data( + frappe._dict( + { + "company": company, + } + ) + ) + + if data: + for d in data: + if d and d.get("voucher_type") == "Subcontracting Receipt": + doc = frappe.new_doc("Repost Item Valuation") + doc.voucher_type = d.get("voucher_type") + doc.voucher_no = d.get("voucher_no") + doc.save() + doc.submit() diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index f2bea5829c..6d64625270 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -226,7 +226,7 @@ $.extend(erpnext.utils, { if (!found) { filters.splice(index, 0, { "fieldname": dimension["fieldname"], - "label": __(dimension["label"]), + "label": __(dimension["doctype"]), "fieldtype": "MultiSelectList", get_data: function(txt) { return frappe.db.get_link_options(dimension["doctype"], txt); diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 84da3cc41d..f85c478a72 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -362,6 +362,12 @@ class PurchaseReceipt(BuyingController): if credit_currency == self.company_currency else flt(d.net_amount, d.precision("net_amount")) ) + + outgoing_amount = d.base_net_amount + if self.is_internal_supplier and d.valuation_rate: + outgoing_amount = d.valuation_rate * d.stock_qty + credit_amount = outgoing_amount + if credit_amount: account = warehouse_account[d.from_warehouse]["account"] if d.from_warehouse else stock_rbnb @@ -369,7 +375,7 @@ class PurchaseReceipt(BuyingController): gl_entries=gl_entries, account=account, cost_center=d.cost_center, - debit=-1 * flt(d.base_net_amount, d.precision("base_net_amount")), + debit=-1 * flt(outgoing_amount, d.precision("base_net_amount")), credit=0.0, remarks=remarks, against_account=warehouse_account_name, @@ -456,7 +462,7 @@ class PurchaseReceipt(BuyingController): # divisional loss adjustment valuation_amount_as_per_doc = ( - flt(d.base_net_amount, d.precision("base_net_amount")) + flt(outgoing_amount, d.precision("base_net_amount")) + flt(d.landed_cost_voucher_amount) + flt(d.rm_supp_cost) + flt(d.item_tax_amount) @@ -631,47 +637,6 @@ class PurchaseReceipt(BuyingController): i += 1 - def add_gl_entry( - self, - gl_entries, - account, - cost_center, - debit, - credit, - remarks, - against_account, - debit_in_account_currency=None, - credit_in_account_currency=None, - account_currency=None, - project=None, - voucher_detail_no=None, - item=None, - posting_date=None, - ): - - gl_entry = { - "account": account, - "cost_center": cost_center, - "debit": debit, - "credit": credit, - "against": against_account, - "remarks": remarks, - } - - if voucher_detail_no: - gl_entry.update({"voucher_detail_no": voucher_detail_no}) - - if debit_in_account_currency: - gl_entry.update({"debit_in_account_currency": debit_in_account_currency}) - - if credit_in_account_currency: - gl_entry.update({"credit_in_account_currency": credit_in_account_currency}) - - if posting_date: - gl_entry.update({"posting_date": posting_date}) - - gl_entries.append(self.get_gl_dict(gl_entry, item=item)) - def get_asset_gl_entry(self, gl_entries): for item in self.get("items"): if item.is_fixed_asset: diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d0d115d96a..b77c3a5134 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -5,6 +5,7 @@ import frappe from frappe.tests.utils import FrappeTestCase, change_settings from frappe.utils import add_days, cint, cstr, flt, today +from pypika import functions as fn import erpnext from erpnext.accounts.doctype.account.test_account import get_inventory_account @@ -1156,6 +1157,125 @@ class TestPurchaseReceipt(FrappeTestCase): if gle.account == account: self.assertEqual(gle.credit, 50) + def test_backdated_transaction_for_internal_transfer(self): + from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + prepare_data_for_internal_transfer() + customer = "_Test Internal Customer 2" + company = "_Test Company with perpetual inventory" + + from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company) + to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company) + item_doc = create_item("Test Internal Transfer Item") + + target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company) + + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -1), + warehouse=from_warehouse, + qty=1, + rate=100, + ) + + dn1 = create_delivery_note( + item_code=item_doc.name, + company=company, + customer=customer, + cost_center="Main - TCP1", + expense_account="Cost of Goods Sold - TCP1", + qty=1, + rate=500, + warehouse=from_warehouse, + target_warehouse=target_warehouse, + ) + + self.assertEqual(dn1.items[0].rate, 100) + + pr1 = make_inter_company_purchase_receipt(dn1.name) + pr1.items[0].warehouse = to_warehouse + self.assertEqual(pr1.items[0].rate, 100) + pr1.submit() + + # Backdated purchase receipt entry, the valuation rate should be updated for DN1 and PR1 + make_purchase_receipt( + item_code=item_doc.name, + company=company, + posting_date=add_days(today(), -2), + warehouse=from_warehouse, + qty=1, + rate=200, + ) + + dn_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Delivery Note", "voucher_no": dn1.name, "warehouse": target_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(dn_value), 200.00) + + pr_value = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_type": "Purchase Receipt", "voucher_no": pr1.name, "warehouse": to_warehouse}, + "stock_value_difference", + ) + + self.assertEqual(abs(pr_value), 200.00) + pr1.load_from_db() + + self.assertEqual(pr1.items[0].valuation_rate, 200) + self.assertEqual(pr1.items[0].rate, 100) + + Gl = frappe.qb.DocType("GL Entry") + + query = ( + frappe.qb.from_(Gl) + .select( + (fn.Sum(Gl.debit) - fn.Sum(Gl.credit)).as_("value"), + ) + .where((Gl.voucher_type == pr1.doctype) & (Gl.voucher_no == pr1.name)) + ).run(as_dict=True) + + self.assertEqual(query[0].value, 0) + + +def prepare_data_for_internal_transfer(): + from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier + from erpnext.selling.doctype.customer.test_customer import create_internal_customer + + company = "_Test Company with perpetual inventory" + + create_internal_customer( + "_Test Internal Customer 2", + company, + company, + ) + + create_internal_supplier( + "_Test Internal Supplier 2", + company, + company, + ) + + if not frappe.db.get_value("Company", company, "unrealized_profit_loss_account"): + account = "Unrealized Profit and Loss - TCP1" + if not frappe.db.exists("Account", account): + frappe.get_doc( + { + "doctype": "Account", + "account_name": "Unrealized Profit and Loss", + "parent_account": "Direct Income - TCP1", + "company": company, + "is_group": 0, + "account_type": "Income Account", + } + ).insert() + + frappe.db.set_value("Company", company, "unrealized_profit_loss_account", account) + def get_sl_entries(voucher_type, voucher_no): return frappe.db.sql( diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js index eae73050b2..d595a80b20 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js @@ -58,6 +58,21 @@ frappe.ui.form.on('Repost Item Valuation', { } frm.trigger('show_reposting_progress'); + + if (frm.doc.status === 'Queued' && frm.doc.docstatus === 1) { + frm.trigger('execute_reposting'); + } + }, + + execute_reposting(frm) { + frm.add_custom_button(__("Start Reposting"), () => { + frappe.call({ + method: 'erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.execute_repost_item_valuation', + callback: function() { + frappe.msgprint(__('Reposting has been started in the background.')); + } + }); + }); }, show_reposting_progress: function(frm) { diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 7c57ecd73e..c4705246b3 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -307,3 +307,9 @@ def in_configured_timeslot(repost_settings=None, current_time=None): return end_time >= now_time >= start_time else: return now_time >= start_time or now_time <= end_time + + +@frappe.whitelist() +def execute_repost_item_valuation(): + """Execute repost item valuation via scheduler.""" + frappe.get_doc("Scheduled Job Type", "repost_item_valuation.repost_entries").enqueue(force=True) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 3524a47c71..50309647de 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -649,21 +649,25 @@ class update_entries_after(object): elif ( sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"] - and sle.actual_qty > 0 + and sle.voucher_detail_no and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier") ): - sle_details = frappe.db.get_value( - "Stock Ledger Entry", - { - "voucher_type": sle.voucher_type, - "voucher_no": sle.voucher_no, - "dependant_sle_voucher_detail_no": sle.voucher_detail_no, - }, - ["stock_value_difference", "actual_qty"], - as_dict=1, + field = ( + "delivery_note_item" if sle.voucher_type == "Purchase Receipt" else "sales_invoice_item" + ) + doctype = ( + "Delivery Note Item" if sle.voucher_type == "Purchase Receipt" else "Sales Invoice Item" + ) + refernce_name = frappe.get_cached_value( + sle.voucher_type + " Item", sle.voucher_detail_no, field ) - rate = abs(sle_details.stock_value_difference / sle.actual_qty) + if refernce_name: + rate = frappe.get_cached_value( + doctype, + refernce_name, + "incoming_rate", + ) else: if sle.voucher_type in ("Purchase Receipt", "Purchase Invoice"): rate_field = "valuation_rate" @@ -745,7 +749,12 @@ class update_entries_after(object): def update_rate_on_purchase_receipt(self, sle, outgoing_rate): if frappe.db.exists(sle.voucher_type + " Item", sle.voucher_detail_no): frappe.db.set_value( - sle.voucher_type + " Item", sle.voucher_detail_no, "base_net_rate", outgoing_rate + sle.voucher_type + " Item", + sle.voucher_detail_no, + { + "base_net_rate": outgoing_rate, + "valuation_rate": outgoing_rate, + }, ) else: frappe.db.set_value( diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js index 065ef39db3..40963f8637 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js +++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.js @@ -107,7 +107,7 @@ frappe.ui.form.on('Subcontracting Order', { get_materials_from_supplier: function (frm) { let sco_rm_details = []; - if (frm.doc.supplied_items && frm.doc.per_received > 0) { + if (frm.doc.status != "Closed" && frm.doc.supplied_items && frm.doc.per_received > 0) { frm.doc.supplied_items.forEach(d => { if (d.total_supplied_qty > 0 && d.total_supplied_qty != d.consumed_qty) { sco_rm_details.push(d.name); diff --git a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py index f4a943f88d..d054ce0f9d 100644 --- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py +++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py @@ -502,6 +502,35 @@ class TestSubcontractingOrder(FrappeTestCase): set_backflush_based_on("BOM") + def test_get_materials_from_supplier(self): + # Create SCO + sco = get_subcontracting_order() + + # Transfer RM + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + # Create SCR (Partial) + scr = make_subcontracting_receipt(sco.name) + scr.items[0].qty -= 5 + scr.save() + scr.submit() + + # Get RM from Supplier + ste = get_materials_from_supplier(sco.name, [d.name for d in sco.supplied_items]) + ste.save() + ste.submit() + + sco.load_from_db() + + self.assertEqual(sco.status, "Closed") + self.assertEqual(sco.supplied_items[0].returned_qty, 5) + def create_subcontracting_order(**args): args = frappe._dict(args) @@ -510,7 +539,7 @@ def create_subcontracting_order(**args): for item in sco.items: item.include_exploded_items = args.get("include_exploded_items", 1) - if args.get("warehouse"): + if args.warehouse: for item in sco.items: item.warehouse = args.warehouse else: diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py index 1da73405e8..cd05b745e6 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py @@ -5,6 +5,8 @@ import frappe from frappe import _ from frappe.utils import cint, flt, getdate, nowdate +import erpnext +from erpnext.accounts.utils import get_account_currency from erpnext.controllers.subcontracting_controller import SubcontractingController @@ -225,6 +227,137 @@ class SubcontractingReceipt(SubcontractingController): if status: frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified) + def get_gl_entries(self, warehouse_account=None): + from erpnext.accounts.general_ledger import process_gl_map + + 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") + + warehouse_with_no_account = [] + + for item in self.items: + if flt(item.rate) and flt(item.qty): + if warehouse_account.get(item.warehouse): + stock_value_diff = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Subcontracting Receipt", + "voucher_no": self.name, + "voucher_detail_no": item.name, + "warehouse": item.warehouse, + "is_cancelled": 0, + }, + "stock_value_difference", + ) + + warehouse_account_name = warehouse_account[item.warehouse]["account"] + warehouse_account_currency = warehouse_account[item.warehouse]["account_currency"] + supplier_warehouse_account = warehouse_account.get(self.supplier_warehouse, {}).get("account") + supplier_warehouse_account_currency = warehouse_account.get(self.supplier_warehouse, {}).get( + "account_currency" + ) + remarks = self.get("remarks") or _("Accounting Entry for Stock") + + # FG Warehouse Account (Debit) + self.add_gl_entry( + gl_entries=gl_entries, + account=warehouse_account_name, + cost_center=item.cost_center, + debit=stock_value_diff, + credit=0.0, + remarks=remarks, + against_account=stock_rbnb, + account_currency=warehouse_account_currency, + item=item, + ) + + # Supplier Warehouse Account (Credit) + if flt(item.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse): + self.add_gl_entry( + gl_entries=gl_entries, + account=supplier_warehouse_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.rm_supp_cost), + remarks=remarks, + against_account=warehouse_account_name, + account_currency=supplier_warehouse_account_currency, + item=item, + ) + + # Expense Account (Credit) + if flt(item.service_cost_per_qty): + self.add_gl_entry( + gl_entries=gl_entries, + account=item.expense_account, + cost_center=item.cost_center, + debit=0.0, + credit=flt(item.service_cost_per_qty) * flt(item.qty), + remarks=remarks, + against_account=warehouse_account_name, + account_currency=get_account_currency(item.expense_account), + item=item, + ) + + # Loss Account (Credit) + divisional_loss = flt(item.amount - stock_value_diff, item.precision("amount")) + + if divisional_loss: + if self.is_return: + loss_account = expenses_included_in_valuation + else: + loss_account = item.expense_account + + self.add_gl_entry( + gl_entries=gl_entries, + account=loss_account, + cost_center=item.cost_center, + debit=divisional_loss, + credit=0.0, + remarks=remarks, + against_account=warehouse_account_name, + account_currency=get_account_currency(loss_account), + project=item.project, + item=item, + ) + elif ( + item.warehouse not in warehouse_with_no_account + or item.rejected_warehouse not in warehouse_with_no_account + ): + warehouse_with_no_account.append(item.warehouse) + + # Additional Costs Expense Accounts (Credit) + for row in self.additional_costs: + credit_amount = ( + flt(row.base_amount) + if (row.base_amount or row.account_currency != self.company_currency) + else flt(row.amount) + ) + + self.add_gl_entry( + gl_entries=gl_entries, + account=row.expense_account, + cost_center=self.cost_center or self.get_company_default("cost_center"), + debit=0.0, + credit=credit_amount, + remarks=remarks, + against_account=None, + ) + + if warehouse_with_no_account: + frappe.msgprint( + _("No accounting entries for the following warehouses") + + ": \n" + + "\n".join(warehouse_with_no_account) + ) + @frappe.whitelist() def make_subcontract_return(source_name, target_doc=None): diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py index a47af52b33..090f1457d9 100644 --- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py +++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py @@ -6,8 +6,10 @@ import copy import frappe from frappe.tests.utils import FrappeTestCase -from frappe.utils import flt +from frappe.utils import cint, flt +import erpnext +from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.controllers.tests.test_subcontracting_controller import ( get_rm_items, @@ -22,6 +24,7 @@ from erpnext.controllers.tests.test_subcontracting_controller import ( set_backflush_based_on, ) from erpnext.stock.doctype.item.test_item import make_item +from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import ( make_subcontracting_receipt, @@ -366,6 +369,103 @@ class TestSubcontractingReceipt(FrappeTestCase): args = frappe._dict(scr_name=scr1.name, qty=-15) self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args) + def test_subcontracting_receipt_no_gl_entry(self): + sco = get_subcontracting_order() + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + scr.append( + "additional_costs", + { + "expense_account": "Expenses Included In Valuation - _TC", + "description": "Test Additional Costs", + "amount": 100, + }, + ) + scr.save() + scr.submit() + + stock_value_difference = frappe.db.get_value( + "Stock Ledger Entry", + { + "voucher_type": "Subcontracting Receipt", + "voucher_no": scr.name, + "item_code": "Subcontracted Item SA7", + "warehouse": "_Test Warehouse - _TC", + }, + "stock_value_difference", + ) + + # Service Cost(100 * 10) + Raw Materials Cost(50 * 10) + Additional Costs(100) = 1600 + self.assertEqual(stock_value_difference, 1600) + self.assertFalse(get_gl_entries("Subcontracting Receipt", scr.name)) + + def test_subcontracting_receipt_gl_entry(self): + sco = get_subcontracting_order( + company="_Test Company with perpetual inventory", + warehouse="Stores - TCP1", + supplier_warehouse="Work In Progress - TCP1", + ) + rm_items = get_rm_items(sco.supplied_items) + itemwise_details = make_stock_in_entry(rm_items=rm_items) + make_stock_transfer_entry( + sco_no=sco.name, + rm_items=rm_items, + itemwise_details=copy.deepcopy(itemwise_details), + ) + + scr = make_subcontracting_receipt(sco.name) + additional_costs_expense_account = "Expenses Included In Valuation - TCP1" + scr.append( + "additional_costs", + { + "expense_account": additional_costs_expense_account, + "description": "Test Additional Costs", + "amount": 100, + "base_amount": 100, + }, + ) + scr.save() + scr.submit() + + self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(scr.company)), 1) + + gl_entries = get_gl_entries("Subcontracting Receipt", scr.name) + + self.assertTrue(gl_entries) + + fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse) + supplier_warehouse_ac = get_inventory_account(scr.company, scr.supplier_warehouse) + expense_account = scr.items[0].expense_account + + if fg_warehouse_ac == supplier_warehouse_ac: + expected_values = { + fg_warehouse_ac: [2100.0, 1000.0], # FG Amount (D), RM Cost (C) + expense_account: [0.0, 1000.0], # Service Cost (C) + additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C) + } + else: + expected_values = { + fg_warehouse_ac: [2100.0, 0.0], # FG Amount (D) + supplier_warehouse_ac: [0.0, 1000.0], # RM Cost (C) + expense_account: [0.0, 1000.0], # Service Cost (C) + additional_costs_expense_account: [0.0, 100.0], # Additional Cost (C) + } + + for gle in gl_entries: + self.assertEqual(expected_values[gle.account][0], gle.debit) + self.assertEqual(expected_values[gle.account][1], gle.credit) + + scr.reload() + scr.cancel() + self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name)) + def make_return_subcontracting_receipt(**args): args = frappe._dict(args)
{{ _("Date") }}