feat!: remove the "Transaction Log" DocType and a related report (#33844)

This commit is contained in:
Raffael Meyer 2025-08-31 20:02:43 +02:00 committed by GitHub
parent 7ac00507ae
commit 6aed5b91d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 1 additions and 428 deletions

View file

@ -1,16 +0,0 @@
# Transaction Log Changelog
## v1.0.0
Initial version
The line hash summarizes:
- The index of the row
- The timestamp
- The document raw data
The chain hash summarizes:
- The previous line hash
- The current line hash
## v1.0.1
Modification of the timestamp fieldtype from "Time" to "Datetime"

View file

@ -1,44 +0,0 @@
# Copyright (c) 2018, Frappe Technologies and Contributors
# License: MIT. See LICENSE
import hashlib
import frappe
from frappe.tests import IntegrationTestCase
class TestTransactionLog(IntegrationTestCase):
def test_validate_chaining(self):
frappe.get_doc(
{
"doctype": "Transaction Log",
"reference_doctype": "Test Doctype",
"document_name": "Test Document 1",
"data": "first_data",
}
).insert(ignore_permissions=True)
second_log = frappe.get_doc(
{
"doctype": "Transaction Log",
"reference_doctype": "Test Doctype",
"document_name": "Test Document 2",
"data": "second_data",
}
).insert(ignore_permissions=True)
third_log = frappe.get_doc(
{
"doctype": "Transaction Log",
"reference_doctype": "Test Doctype",
"document_name": "Test Document 3",
"data": "third_data",
}
).insert(ignore_permissions=True)
sha = hashlib.sha256()
sha.update(
frappe.safe_encode(str(third_log.transaction_hash))
+ frappe.safe_encode(str(second_log.chaining_hash))
)
self.assertEqual(sha.hexdigest(), third_log.chaining_hash)

View file

@ -1,4 +0,0 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on("Transaction Log", {});

View file

@ -1,124 +0,0 @@
{
"actions": [],
"creation": "2018-02-06 11:48:51.270524",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"row_index",
"section_break_2",
"reference_doctype",
"document_name",
"column_break_5",
"timestamp",
"checksum_version",
"section_break_8",
"previous_hash",
"transaction_hash",
"chaining_hash",
"data",
"amended_from"
],
"fields": [
{
"fieldname": "row_index",
"fieldtype": "Data",
"label": "Row Index",
"read_only": 1
},
{
"fieldname": "section_break_2",
"fieldtype": "Section Break"
},
{
"fieldname": "reference_doctype",
"fieldtype": "Data",
"label": "Reference Document Type",
"read_only": 1
},
{
"fieldname": "document_name",
"fieldtype": "Data",
"label": "Document Name",
"read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "timestamp",
"fieldtype": "Datetime",
"label": "Timestamp",
"read_only": 1
},
{
"fieldname": "checksum_version",
"fieldtype": "Data",
"label": "Checksum Version",
"read_only": 1
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
},
{
"fieldname": "previous_hash",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Previous Hash",
"read_only": 1
},
{
"fieldname": "transaction_hash",
"fieldtype": "Small Text",
"label": "Transaction Hash",
"read_only": 1
},
{
"fieldname": "chaining_hash",
"fieldtype": "Small Text",
"hidden": 1,
"label": "Chaining Hash",
"read_only": 1
},
{
"fieldname": "data",
"fieldtype": "Long Text",
"hidden": 1,
"label": "Data",
"read_only": 1
},
{
"fieldname": "amended_from",
"fieldtype": "Link",
"label": "Amended From",
"no_copy": 1,
"options": "Transaction Log",
"print_hide": 1,
"read_only": 1
}
],
"in_create": 1,
"links": [],
"modified": "2024-03-23 16:03:59.373102",
"modified_by": "Administrator",
"module": "Core",
"name": "Transaction Log",
"owner": "Administrator",
"permissions": [
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View file

@ -1,86 +0,0 @@
# Copyright (c) 2021, Frappe Technologies and contributors
# License: MIT. See LICENSE
import hashlib
import frappe
from frappe.model.document import Document
from frappe.query_builder import DocType
from frappe.utils import cint, now_datetime
class TransactionLog(Document):
# begin: auto-generated types
# This code is auto-generated. Do not modify anything in this block.
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from frappe.types import DF
amended_from: DF.Link | None
chaining_hash: DF.SmallText | None
checksum_version: DF.Data | None
data: DF.LongText | None
document_name: DF.Data | None
previous_hash: DF.SmallText | None
reference_doctype: DF.Data | None
row_index: DF.Data | None
timestamp: DF.Datetime | None
transaction_hash: DF.SmallText | None
# end: auto-generated types
def before_insert(self):
index = get_current_index()
self.row_index = index
self.timestamp = now_datetime()
if index != 1:
prev_hash = frappe.get_all(
"Transaction Log", filters={"row_index": str(index - 1)}, pluck="chaining_hash", limit=1
)
if prev_hash:
self.previous_hash = prev_hash[0]
else:
self.previous_hash = "Indexing broken"
else:
self.previous_hash = self.hash_line()
self.transaction_hash = self.hash_line()
self.chaining_hash = self.hash_chain()
self.checksum_version = "v1.0.1"
def hash_line(self):
sha = hashlib.sha256()
sha.update(
frappe.safe_encode(str(self.row_index))
+ frappe.safe_encode(str(self.timestamp))
+ frappe.safe_encode(str(self.data))
)
return sha.hexdigest()
def hash_chain(self):
sha = hashlib.sha256()
sha.update(
frappe.safe_encode(str(self.transaction_hash)) + frappe.safe_encode(str(self.previous_hash))
)
return sha.hexdigest()
def get_current_index():
series = DocType("Series")
current = (
frappe.qb.from_(series).where(series.name == "TRANSACTLOG").for_update().select("current")
).run()
if current and current[0][0] is not None:
current = current[0][0]
frappe.db.sql(
"""UPDATE `tabSeries`
SET `current` = `current` + 1
where `name` = 'TRANSACTLOG'"""
)
current = cint(current) + 1
else:
frappe.db.sql("INSERT INTO `tabSeries` (name, current) VALUES ('TRANSACTLOG', 1)")
current = 1
return current

View file

@ -22,7 +22,7 @@ from frappe.permissions import (
)
from frappe.utils.user import get_users_with_role as _get_user_with_role
not_allowed_in_permission_manager = ["DocType", "Patch Log", "Module Def", "Transaction Log"]
not_allowed_in_permission_manager = ["DocType", "Patch Log", "Module Def"]
@frappe.whitelist()

View file

@ -1,10 +0,0 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.query_reports["Transaction Log Report"] = {
onload: function (query_report) {
query_report.add_make_chart_button = function () {
//
};
},
};

View file

@ -1,26 +0,0 @@
{
"add_total_row": 0,
"creation": "2018-03-15 18:37:48.783779",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 0,
"is_standard": "Yes",
"modified": "2018-12-27 18:10:29.785415",
"modified_by": "Administrator",
"module": "Core",
"name": "Transaction Log Report",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Transaction Log",
"report_name": "Transaction Log Report",
"report_type": "Script Report",
"roles": [
{
"role": "Administrator"
},
{
"role": "System Manager"
}
]
}

View file

@ -1,117 +0,0 @@
# Copyright (c) 2021, Frappe Technologies and contributors
# License: MIT. See LICENSE
import hashlib
import frappe
from frappe import _
from frappe.utils import format_datetime
def execute(filters=None):
columns, data = get_columns(filters), get_data(filters)
return columns, data
def get_data(filters=None):
result = []
logs = frappe.get_all("Transaction Log", fields=["*"], order_by="creation desc")
for l in logs:
row_index = int(l.row_index)
if row_index > 1:
previous_hash = frappe.get_all(
"Transaction Log",
fields=["chaining_hash"],
filters={"row_index": row_index - 1},
)
if not previous_hash:
integrity = False
else:
integrity = check_data_integrity(
l.chaining_hash, l.transaction_hash, l.previous_hash, previous_hash[0]["chaining_hash"]
)
result.append(
[
_(str(integrity)),
_(l.reference_doctype),
l.document_name,
l.owner,
l.modified_by,
format_datetime(l.timestamp, "YYYYMMDDHHmmss"),
]
)
else:
result.append(
[
_("First Transaction"),
_(l.reference_doctype),
l.document_name,
l.owner,
l.modified_by,
format_datetime(l.timestamp, "YYYYMMDDHHmmss"),
]
)
return result
def check_data_integrity(chaining_hash, transaction_hash, registered_previous_hash, previous_hash):
if registered_previous_hash != previous_hash:
return False
calculated_chaining_hash = calculate_chain(transaction_hash, previous_hash)
if calculated_chaining_hash != chaining_hash:
return False
else:
return True
def calculate_chain(transaction_hash, previous_hash):
sha = hashlib.sha256()
sha.update(transaction_hash.encode("utf-8") + previous_hash.encode("utf-8"))
return sha.hexdigest()
def get_columns(filters=None):
return [
{
"label": _("Chain Integrity"),
"fieldname": "chain_integrity",
"fieldtype": "Data",
"width": 150,
},
{
"label": _("Reference Doctype"),
"fieldname": "reference_doctype",
"fieldtype": "Data",
"width": 150,
},
{
"label": _("Reference Name"),
"fieldname": "reference_name",
"fieldtype": "Data",
"width": 150,
},
{
"label": _("Owner"),
"fieldname": "owner",
"fieldtype": "Data",
"width": 100,
},
{
"label": _("Modified By"),
"fieldname": "modified_by",
"fieldtype": "Data",
"width": 100,
},
{
"label": _("Timestamp"),
"fieldname": "timestamp",
"fieldtype": "Data",
"width": 100,
},
]