mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
feat: repost GL Entries only
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
"label": "Home",
|
"label": "Home",
|
||||||
"link_to": "Home",
|
"link_to": "Home",
|
||||||
"link_type": "Workspace",
|
"link_type": "Workspace",
|
||||||
"modified": "2025-11-18 12:06:56.506311",
|
"modified": "2025-11-20 16:09:28.269913",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"name": "Home",
|
"name": "Home",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
|
|||||||
@@ -4536,6 +4536,60 @@ class TestPurchaseReceipt(IntegrationTestCase):
|
|||||||
if row.account == expense_contra_account:
|
if row.account == expense_contra_account:
|
||||||
self.assertEqual(row.credit, 1000)
|
self.assertEqual(row.credit, 1000)
|
||||||
|
|
||||||
|
def test_repost_gl_entries(self):
|
||||||
|
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||||
|
|
||||||
|
item = "Test Item for Repost GL Entries"
|
||||||
|
make_item(item, {"is_stock_item": 1})
|
||||||
|
company = "_Test Company with perpetual inventory"
|
||||||
|
|
||||||
|
account = "Reposting Adjustment - TCP1"
|
||||||
|
if not frappe.db.exists("Account", account):
|
||||||
|
frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Account",
|
||||||
|
"account_name": "Reposting Adjustment",
|
||||||
|
"parent_account": "Stock Expenses - TCP1",
|
||||||
|
"company": company,
|
||||||
|
"is_group": 0,
|
||||||
|
"account_type": "Expense Account",
|
||||||
|
}
|
||||||
|
).insert()
|
||||||
|
|
||||||
|
se = make_stock_entry(
|
||||||
|
item_code=item,
|
||||||
|
qty=10,
|
||||||
|
rate=100,
|
||||||
|
company=company,
|
||||||
|
target="Stores - TCP1",
|
||||||
|
)
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries(se.doctype, se.name)
|
||||||
|
for row in gl_entries:
|
||||||
|
self.assertTrue(row.account in ["Stock In Hand - TCP1", "Stock Adjustment - TCP1"])
|
||||||
|
|
||||||
|
se.items[0].db_set("expense_account", account)
|
||||||
|
se.reload()
|
||||||
|
|
||||||
|
repost_doc = frappe.get_doc(
|
||||||
|
{
|
||||||
|
"doctype": "Repost Item Valuation",
|
||||||
|
"based_on": "Transaction",
|
||||||
|
"voucher_type": se.doctype,
|
||||||
|
"voucher_no": se.name,
|
||||||
|
"posting_date": se.posting_date,
|
||||||
|
"posting_time": se.posting_time,
|
||||||
|
"company": se.company,
|
||||||
|
"repost_only_accounting_ledgers": 1,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
repost_doc.submit()
|
||||||
|
|
||||||
|
gl_entries = get_gl_entries(se.doctype, se.name)
|
||||||
|
for row in gl_entries:
|
||||||
|
self.assertTrue(row.account in ["Stock In Hand - TCP1", account])
|
||||||
|
|
||||||
|
|
||||||
def prepare_data_for_internal_transfer():
|
def prepare_data_for_internal_transfer():
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ frappe.ui.form.on("Repost Item Valuation", {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger("setup_realtime_progress");
|
if (frm.doc.status !== "Completed") {
|
||||||
|
frm.trigger("setup_realtime_progress");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
based_on: function (frm) {
|
based_on: function (frm) {
|
||||||
@@ -84,7 +86,9 @@ frappe.ui.form.on("Repost Item Valuation", {
|
|||||||
}).addClass("btn-primary");
|
}).addClass("btn-primary");
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger("show_reposting_progress");
|
if (frm.doc.status !== "Completed") {
|
||||||
|
frm.trigger("show_reposting_progress");
|
||||||
|
}
|
||||||
|
|
||||||
if (frm.doc.status === "Queued" && frm.doc.docstatus === 1) {
|
if (frm.doc.status === "Queued" && frm.doc.docstatus === 1) {
|
||||||
frm.trigger("execute_reposting");
|
frm.trigger("execute_reposting");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"actions": [],
|
"actions": [],
|
||||||
|
"allow_import": 1,
|
||||||
"autoname": "hash",
|
"autoname": "hash",
|
||||||
"creation": "2022-01-11 15:03:38.273179",
|
"creation": "2022-01-11 15:03:38.273179",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -16,6 +17,8 @@
|
|||||||
"column_break_5",
|
"column_break_5",
|
||||||
"status",
|
"status",
|
||||||
"company",
|
"company",
|
||||||
|
"reposting_reference",
|
||||||
|
"repost_only_accounting_ledgers",
|
||||||
"allow_negative_stock",
|
"allow_negative_stock",
|
||||||
"via_landed_cost_voucher",
|
"via_landed_cost_voucher",
|
||||||
"allow_zero_rate",
|
"allow_zero_rate",
|
||||||
@@ -228,13 +231,28 @@
|
|||||||
"fieldname": "recreate_stock_ledgers",
|
"fieldname": "recreate_stock_ledgers",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Recreate Stock Ledgers"
|
"label": "Recreate Stock Ledgers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "repost_only_accounting_ledgers",
|
||||||
|
"fieldname": "reposting_reference",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Reposting Reference",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"depends_on": "eval:doc.based_on === \"Transaction\"",
|
||||||
|
"description": "Stock Ledgers won\u2019t be reposted.",
|
||||||
|
"fieldname": "repost_only_accounting_ledgers",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Repost Only Accounting Ledgers"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"grid_page_length": 50,
|
"grid_page_length": 50,
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2025-03-31 12:38:20.566196",
|
"modified": "2025-11-20 18:24:48.808526",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Stock",
|
"module": "Stock",
|
||||||
"name": "Repost Item Valuation",
|
"name": "Repost Item Valuation",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.desk.form.load import get_attachments
|
from frappe.desk.form.load import get_attachments
|
||||||
@@ -48,7 +50,9 @@ class RepostItemValuation(Document):
|
|||||||
posting_date: DF.Date
|
posting_date: DF.Date
|
||||||
posting_time: DF.Time | None
|
posting_time: DF.Time | None
|
||||||
recreate_stock_ledgers: DF.Check
|
recreate_stock_ledgers: DF.Check
|
||||||
|
repost_only_accounting_ledgers: DF.Check
|
||||||
reposting_data_file: DF.Attach | None
|
reposting_data_file: DF.Attach | None
|
||||||
|
reposting_reference: DF.Data | None
|
||||||
status: DF.Literal["Queued", "In Progress", "Completed", "Skipped", "Failed"]
|
status: DF.Literal["Queued", "In Progress", "Completed", "Skipped", "Failed"]
|
||||||
total_reposting_count: DF.Int
|
total_reposting_count: DF.Int
|
||||||
via_landed_cost_voucher: DF.Check
|
via_landed_cost_voucher: DF.Check
|
||||||
@@ -70,6 +74,7 @@ class RepostItemValuation(Document):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
self.reset_repost_only_accounting_ledgers()
|
||||||
self.set_company()
|
self.set_company()
|
||||||
self.validate_period_closing_voucher()
|
self.validate_period_closing_voucher()
|
||||||
self.set_status(write=False)
|
self.set_status(write=False)
|
||||||
@@ -78,6 +83,10 @@ class RepostItemValuation(Document):
|
|||||||
self.reset_recreate_stock_ledgers()
|
self.reset_recreate_stock_ledgers()
|
||||||
self.validate_recreate_stock_ledgers()
|
self.validate_recreate_stock_ledgers()
|
||||||
|
|
||||||
|
def reset_repost_only_accounting_ledgers(self):
|
||||||
|
if self.repost_only_accounting_ledgers and self.based_on != "Transaction":
|
||||||
|
self.repost_only_accounting_ledgers = 0
|
||||||
|
|
||||||
def validate_recreate_stock_ledgers(self):
|
def validate_recreate_stock_ledgers(self):
|
||||||
if not self.recreate_stock_ledgers:
|
if not self.recreate_stock_ledgers:
|
||||||
return
|
return
|
||||||
@@ -242,11 +251,39 @@ class RepostItemValuation(Document):
|
|||||||
self.distinct_item_and_warehouse = None
|
self.distinct_item_and_warehouse = None
|
||||||
self.items_to_be_repost = None
|
self.items_to_be_repost = None
|
||||||
self.gl_reposting_index = 0
|
self.gl_reposting_index = 0
|
||||||
|
self.total_reposting_count = 0
|
||||||
self.clear_attachment()
|
self.clear_attachment()
|
||||||
self.db_update()
|
self.db_update()
|
||||||
|
|
||||||
|
def skipped_similar_reposts(self):
|
||||||
|
repost_entries = frappe.get_all(
|
||||||
|
"Repost Item Valuation",
|
||||||
|
filters={
|
||||||
|
"based_on": "Transaction",
|
||||||
|
"voucher_type": self.voucher_type,
|
||||||
|
"voucher_no": self.voucher_no,
|
||||||
|
"docstatus": 1,
|
||||||
|
"repost_only_accounting_ledgers": 1,
|
||||||
|
"status": "Queued",
|
||||||
|
"reposting_reference": ("is", "set"),
|
||||||
|
},
|
||||||
|
fields=["name", "reposting_reference"],
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry in repost_entries:
|
||||||
|
if (
|
||||||
|
frappe.db.get_value("Repost Item Valuation", entry.reposting_reference, "status")
|
||||||
|
== "Completed"
|
||||||
|
):
|
||||||
|
frappe.db.set_value("Repost Item Valuation", entry.name, "status", "Skipped")
|
||||||
|
|
||||||
def deduplicate_similar_repost(self):
|
def deduplicate_similar_repost(self):
|
||||||
"""Deduplicate similar reposts based on item-warehouse-posting combination."""
|
"""Deduplicate similar reposts based on item-warehouse-posting combination."""
|
||||||
|
|
||||||
|
if self.repost_only_accounting_ledgers:
|
||||||
|
self.skipped_similar_reposts()
|
||||||
|
return
|
||||||
|
|
||||||
if self.based_on != "Item and Warehouse":
|
if self.based_on != "Item and Warehouse":
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -284,6 +321,19 @@ class RepostItemValuation(Document):
|
|||||||
doc.update_stock_ledger(allow_negative_stock=True)
|
doc.update_stock_ledger(allow_negative_stock=True)
|
||||||
|
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def bulk_restart_reposting(names):
|
||||||
|
names = json.loads(names)
|
||||||
|
for name in names:
|
||||||
|
doc = frappe.get_doc("Repost Item Valuation", name)
|
||||||
|
if doc.status != "Failed":
|
||||||
|
continue
|
||||||
|
|
||||||
|
doc.restart_reposting()
|
||||||
|
|
||||||
|
frappe.msgprint(_("Repost Item Valuation restarted for selected failed records."))
|
||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse")
|
frappe.db.add_index("Repost Item Valuation", ["warehouse", "item_code"], "item_warehouse")
|
||||||
|
|
||||||
@@ -304,7 +354,9 @@ def repost(doc):
|
|||||||
if doc.recreate_stock_ledgers:
|
if doc.recreate_stock_ledgers:
|
||||||
doc.recreate_stock_ledger_entries()
|
doc.recreate_stock_ledger_entries()
|
||||||
|
|
||||||
repost_sl_entries(doc)
|
if not doc.repost_only_accounting_ledgers:
|
||||||
|
repost_sl_entries(doc)
|
||||||
|
|
||||||
repost_gl_entries(doc)
|
repost_gl_entries(doc)
|
||||||
|
|
||||||
doc.set_status("Completed")
|
doc.set_status("Completed")
|
||||||
@@ -393,15 +445,34 @@ def repost_gl_entries(doc):
|
|||||||
if not cint(erpnext.is_perpetual_inventory_enabled(doc.company)):
|
if not cint(erpnext.is_perpetual_inventory_enabled(doc.company)):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if doc.repost_only_accounting_ledgers and doc.based_on == "Transaction":
|
||||||
|
transactions = [(doc.voucher_type, doc.voucher_no)]
|
||||||
|
repost_gle_for_stock_vouchers(
|
||||||
|
transactions,
|
||||||
|
doc.posting_date,
|
||||||
|
doc.company,
|
||||||
|
repost_doc=doc,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
# directly modified transactions
|
# directly modified transactions
|
||||||
directly_dependent_transactions = _get_directly_dependent_vouchers(doc)
|
directly_dependent_transactions = _get_directly_dependent_vouchers(doc)
|
||||||
repost_affected_transaction = get_affected_transactions(doc)
|
repost_affected_transaction = get_affected_transactions(doc)
|
||||||
repost_gle_for_stock_vouchers(
|
|
||||||
directly_dependent_transactions + list(repost_affected_transaction),
|
transactions = directly_dependent_transactions + list(repost_affected_transaction)
|
||||||
doc.posting_date,
|
if doc.based_on == "Item and Warehouse" and not doc.repost_only_accounting_ledgers:
|
||||||
doc.company,
|
make_reposting_for_accounting_ledgers(
|
||||||
repost_doc=doc,
|
transactions,
|
||||||
)
|
doc.company,
|
||||||
|
repost_doc=doc,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
repost_gle_for_stock_vouchers(
|
||||||
|
transactions,
|
||||||
|
doc.posting_date,
|
||||||
|
doc.company,
|
||||||
|
repost_doc=doc,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _get_directly_dependent_vouchers(doc):
|
def _get_directly_dependent_vouchers(doc):
|
||||||
@@ -477,14 +548,17 @@ def repost_entries():
|
|||||||
|
|
||||||
for row in riv_entries:
|
for row in riv_entries:
|
||||||
doc = frappe.get_doc("Repost Item Valuation", row.name)
|
doc = frappe.get_doc("Repost Item Valuation", row.name)
|
||||||
|
if (
|
||||||
|
doc.repost_only_accounting_ledgers
|
||||||
|
and doc.reposting_reference
|
||||||
|
and frappe.db.get_value("Repost Item Valuation", doc.reposting_reference, "status") != "Completed"
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
if doc.status in ("Queued", "In Progress"):
|
if doc.status in ("Queued", "In Progress"):
|
||||||
repost(doc)
|
repost(doc)
|
||||||
doc.deduplicate_similar_repost()
|
doc.deduplicate_similar_repost()
|
||||||
|
|
||||||
riv_entries = get_repost_item_valuation_entries()
|
|
||||||
if riv_entries:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def get_repost_item_valuation_entries():
|
def get_repost_item_valuation_entries():
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
@@ -529,3 +603,28 @@ def execute_repost_item_valuation():
|
|||||||
"name",
|
"name",
|
||||||
):
|
):
|
||||||
frappe.get_doc("Scheduled Job Type", name).enqueue(force=True)
|
frappe.get_doc("Scheduled Job Type", name).enqueue(force=True)
|
||||||
|
|
||||||
|
|
||||||
|
def make_reposting_for_accounting_ledgers(transactions, company, repost_doc):
|
||||||
|
for voucher_type, voucher_no in transactions:
|
||||||
|
if frappe.db.exists(
|
||||||
|
"Repost Item Valuation",
|
||||||
|
{
|
||||||
|
"voucher_type": voucher_type,
|
||||||
|
"voucher_no": voucher_no,
|
||||||
|
"docstatus": 1,
|
||||||
|
"reposting_reference": repost_doc.name,
|
||||||
|
"repost_only_accounting_ledgers": 1,
|
||||||
|
"status": "Queued",
|
||||||
|
},
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_repost_doc = frappe.new_doc("Repost Item Valuation")
|
||||||
|
new_repost_doc.company = company
|
||||||
|
new_repost_doc.voucher_type = voucher_type
|
||||||
|
new_repost_doc.voucher_no = voucher_no
|
||||||
|
new_repost_doc.repost_only_accounting_ledgers = 1
|
||||||
|
new_repost_doc.reposting_reference = repost_doc.name
|
||||||
|
new_repost_doc.flags.ignore_permissions = True
|
||||||
|
new_repost_doc.submit()
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
frappe.listview_settings["Repost Item Valuation"] = {
|
||||||
|
add_fields: ["status", "name", "voucher_type", "voucher_no"],
|
||||||
|
get_indicator: function (doc) {
|
||||||
|
if (doc.status === "Completed") {
|
||||||
|
// Closed
|
||||||
|
return [__("Completed"), "green", "status,=,Completed"];
|
||||||
|
} else if (doc.status === "Queued") {
|
||||||
|
// on hold
|
||||||
|
return [__("Queued"), "red", "status,=,Queued"];
|
||||||
|
} else if (doc.status === "In Progress") {
|
||||||
|
// on hold
|
||||||
|
return [__("In Progress"), "orange", "status,=,In Progress"];
|
||||||
|
} else if (doc.status === "Failed") {
|
||||||
|
return [__("Failed"), "red", "status,=,Failed"];
|
||||||
|
} else {
|
||||||
|
return [__(doc.status), "blue", true];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onload: function (listview) {
|
||||||
|
var method =
|
||||||
|
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.bulk_restart_reposting";
|
||||||
|
|
||||||
|
listview.page.add_action_item(__("Restart Failed Entries"), () => {
|
||||||
|
listview.call_for_selected_items(method, { status: "Failed" });
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user