mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-29 14:48:26 +00:00
fix: clear linked comments, versions and attachments with old logs
Repost Item Valuation and BOM Update Log cleared old logs with a raw delete on the parent table, orphaning timeline comments, versions, attachments and other reference records. Fixes #57237
This commit is contained in:
@@ -6,9 +6,7 @@ from typing import Any
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder import DocType, Interval
|
from frappe.utils import add_days, cint, cstr, date_diff, now, today
|
||||||
from frappe.query_builder.functions import Now
|
|
||||||
from frappe.utils import cint, cstr, date_diff, today
|
|
||||||
|
|
||||||
from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
|
from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
|
||||||
get_leaf_boms,
|
get_leaf_boms,
|
||||||
@@ -17,6 +15,7 @@ from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
|
|||||||
replace_bom,
|
replace_bom,
|
||||||
set_values_in_log,
|
set_values_in_log,
|
||||||
)
|
)
|
||||||
|
from erpnext.utilities import clear_logs_with_references
|
||||||
|
|
||||||
|
|
||||||
class BOMMissingError(frappe.ValidationError):
|
class BOMMissingError(frappe.ValidationError):
|
||||||
@@ -48,10 +47,12 @@ class BOMUpdateLog(Document):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_old_logs(days=None):
|
def clear_old_logs(days=None):
|
||||||
days = days or 90
|
days = days or 90
|
||||||
table = DocType("BOM Update Log")
|
clear_logs_with_references(
|
||||||
frappe.db.delete(
|
"BOM Update Log",
|
||||||
table,
|
{
|
||||||
filters=((table.creation < (Now() - Interval(days=days))) & (table.update_type == "Update Cost")),
|
"creation": ("<", add_days(now(), -days)),
|
||||||
|
"update_type": "Update Cost",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
|||||||
@@ -6,9 +6,18 @@ from frappe import _
|
|||||||
from frappe.desk.form.load import get_attachments
|
from frappe.desk.form.load import get_attachments
|
||||||
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
|
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.query_builder import DocType, Interval
|
from frappe.query_builder import DocType
|
||||||
from frappe.query_builder.functions import CombineDatetime, Max, Now
|
from frappe.query_builder.functions import CombineDatetime, Max
|
||||||
from frappe.utils import cint, get_datetime, get_link_to_form, get_weekday, getdate, now, nowtime
|
from frappe.utils import (
|
||||||
|
add_days,
|
||||||
|
cint,
|
||||||
|
get_datetime,
|
||||||
|
get_link_to_form,
|
||||||
|
get_weekday,
|
||||||
|
getdate,
|
||||||
|
now,
|
||||||
|
nowtime,
|
||||||
|
)
|
||||||
from frappe.utils.user import get_users_with_role
|
from frappe.utils.user import get_users_with_role
|
||||||
from rq.timeouts import JobTimeoutException
|
from rq.timeouts import JobTimeoutException
|
||||||
|
|
||||||
@@ -22,6 +31,7 @@ from erpnext.stock.stock_ledger import (
|
|||||||
repost_future_sle,
|
repost_future_sle,
|
||||||
)
|
)
|
||||||
from erpnext.stock.utils import get_combine_datetime
|
from erpnext.stock.utils import get_combine_datetime
|
||||||
|
from erpnext.utilities import clear_logs_with_references
|
||||||
|
|
||||||
RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
|
RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
|
||||||
|
|
||||||
@@ -65,13 +75,12 @@ class RepostItemValuation(Document):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def clear_old_logs(days=None):
|
def clear_old_logs(days=None):
|
||||||
days = days or 90
|
days = days or 90
|
||||||
table = DocType("Repost Item Valuation")
|
clear_logs_with_references(
|
||||||
frappe.db.delete(
|
"Repost Item Valuation",
|
||||||
table,
|
{
|
||||||
filters=(
|
"creation": ("<", add_days(now(), -days)),
|
||||||
(table.creation < (Now() - Interval(days=days)))
|
"status": ("in", ["Completed", "Skipped"]),
|
||||||
& (table.status.isin(["Completed", "Skipped"]))
|
},
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_discard(self):
|
def on_discard(self):
|
||||||
|
|||||||
@@ -104,6 +104,15 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin):
|
|||||||
repost_doc.creation = add_days(now(), days=-i * 10)
|
repost_doc.creation = add_days(now(), days=-i * 10)
|
||||||
repost_doc.db_update_all()
|
repost_doc.db_update_all()
|
||||||
|
|
||||||
|
repost_doc.add_comment("Comment", "test comment")
|
||||||
|
frappe.new_doc(
|
||||||
|
"File",
|
||||||
|
file_name="test_clear_old_logs.txt",
|
||||||
|
content="test",
|
||||||
|
attached_to_doctype=repost_doc.doctype,
|
||||||
|
attached_to_name=repost_doc.name,
|
||||||
|
).insert(ignore_permissions=True)
|
||||||
|
|
||||||
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
|
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
|
||||||
self.assertGreater(len(logs), 10)
|
self.assertGreater(len(logs), 10)
|
||||||
|
|
||||||
@@ -114,6 +123,15 @@ class TestRepostItemValuation(ERPNextTestSuite, StockTestMixin):
|
|||||||
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
|
logs = frappe.get_all("Repost Item Valuation", filters={"status": "Skipped"})
|
||||||
self.assertEqual(len(logs), 0)
|
self.assertEqual(len(logs), 0)
|
||||||
|
|
||||||
|
orphan_reference = {"reference_doctype": repost_doc.doctype, "reference_name": repost_doc.name}
|
||||||
|
self.assertFalse(frappe.get_all("Comment", filters=orphan_reference))
|
||||||
|
self.assertFalse(
|
||||||
|
frappe.get_all(
|
||||||
|
"File",
|
||||||
|
filters={"attached_to_doctype": repost_doc.doctype, "attached_to_name": repost_doc.name},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_create_item_wise_repost_item_valuation_entries(self):
|
def test_create_item_wise_repost_item_valuation_entries(self):
|
||||||
pr = make_purchase_receipt(
|
pr = make_purchase_receipt(
|
||||||
company="_Test Company with perpetual inventory",
|
company="_Test Company with perpetual inventory",
|
||||||
|
|||||||
@@ -4,10 +4,37 @@ from contextlib import contextmanager
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cstr
|
from frappe.utils import create_batch, cstr
|
||||||
|
|
||||||
from erpnext.utilities.activation import get_level
|
from erpnext.utilities.activation import get_level
|
||||||
|
|
||||||
|
LOG_REFERENCE_FIELDS = {
|
||||||
|
"Comment": ("reference_doctype", "reference_name"),
|
||||||
|
"Version": ("ref_doctype", "docname"),
|
||||||
|
"ToDo": ("reference_type", "reference_name"),
|
||||||
|
"DocShare": ("share_doctype", "share_name"),
|
||||||
|
"View Log": ("reference_doctype", "reference_name"),
|
||||||
|
"Document Follow": ("ref_doctype", "ref_docname"),
|
||||||
|
"Notification Log": ("document_type", "document_name"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def clear_logs_with_references(doctype, filters):
|
||||||
|
names = frappe.get_all(doctype, filters=filters, pluck="name")
|
||||||
|
for batch in create_batch(names, 1000):
|
||||||
|
attached_files = frappe.get_all(
|
||||||
|
"File",
|
||||||
|
filters={"attached_to_doctype": doctype, "attached_to_name": ("in", batch)},
|
||||||
|
pluck="name",
|
||||||
|
)
|
||||||
|
if attached_files:
|
||||||
|
frappe.delete_doc("File", attached_files, ignore_permissions=True)
|
||||||
|
|
||||||
|
for reference_doctype, (doctype_field, name_field) in LOG_REFERENCE_FIELDS.items():
|
||||||
|
frappe.db.delete(reference_doctype, {doctype_field: doctype, name_field: ("in", batch)})
|
||||||
|
|
||||||
|
frappe.db.delete(doctype, {"name": ("in", batch)})
|
||||||
|
|
||||||
|
|
||||||
def update_doctypes():
|
def update_doctypes():
|
||||||
df = frappe.qb.DocType("DocField")
|
df = frappe.qb.DocType("DocField")
|
||||||
|
|||||||
Reference in New Issue
Block a user