Merge pull request #31918 from s-aga-r/fix/subcontracting-receipt/gl-entries
fix: Subcontracting Receipt GL Entries
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index d4f9aba..9149b4d 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -700,6 +700,47 @@
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/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index bc503f5..8490d14 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -897,7 +897,7 @@
"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 @@
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 @@
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 4729add..d780213 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -312,4 +312,5 @@
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 0000000..159c6dc
--- /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/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 84da3cc..51d914d 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -631,47 +631,6 @@
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/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
index f4a943f..9385568 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/test_subcontracting_order.py
@@ -510,7 +510,7 @@
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 1da7340..cd05b74 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -5,6 +5,8 @@
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 @@
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 a47af52..090f145 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 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 @@
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 @@
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)