Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Prevent Attachment Deletion #69

Open
wants to merge 4 commits into
base: version-15-hotfix
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ App to hold regional code for Germany, built on top of ERPNext.

![Validate EU VAT ID](docs/vat_check.png)

- Allow deletion of the most recent sales transaction only
- Restrict deletion of attachments to submitted transactions (**ERPNext Germany Settings**)

Applies to **Quotation**, **Sales Order**, **Delivery Note**, **Sales Invoice**, **Request for Quotation**, **Supplier Quotation**, **Purchase Order**, **Purchase Receipt**, **Purchase Invoice**, **Journal Entry**, **Payment Entry** and **Asset**.

- Allow deletion of the most recent sales transaction only (**ERPNext Germany Settings**)

This ensures consecutive numbering of transactions. Applies to **Quotation**, **Sales Order**, **Sales Invoice**.

Expand Down
7 changes: 7 additions & 0 deletions erpnext_germany/boot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import frappe


def boot_session(bootinfo):
bootinfo.sysdefaults.prevent_attachment_deletion = frappe.db.get_single_value(
"ERPNext Germany Settings", "prevent_attachment_deletion"
)
48 changes: 48 additions & 0 deletions erpnext_germany/custom/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import TYPE_CHECKING

import frappe
from frappe import _

if TYPE_CHECKING:
from frappe.core.doctype.file import File

APPLICABLE_DOCTYPES = (
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"Request for Quotation",
"Supplier Quotation",
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice",
"Journal Entry",
"Payment Entry",
"Asset",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with my accounting department and payroll team and identfied DocTypes that should be added:

  • Expense Claim (tax authority requirement due to direct payment of expenses to employee and because invoices may be attached depending on use-case)
  • Business Trip (tax authority requirement due to direct payment of expenses to employee and because invoices may be attached depending on use-case)
  • Asset Depreciation Schedule
  • Asset Repair (due to possible value change)
  • Asset Value Adjustment
  • Asset Capitalization
  • E-Invoice Import
  • POS Invoice
  • Dunning
  • Business Letter
  • Period Closing Voucher
  • Contract (if company collects documents here, likely they want to keep them safe)
  • Blanket Order

)


def on_trash(doc: "File", method: str):
if doc.attached_to_doctype not in APPLICABLE_DOCTYPES:
# Not applicable
return

docstatus = frappe.db.get_value(
doc.attached_to_doctype, doc.attached_to_name, "docstatus"
)
if docstatus != 1:
# Not submitted
return
Comment on lines +33 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would extend this to docstatus 1 & 2 ( if docstatus == 0: ). If a document is cancelled, it does usually not matter if a file is attached or not. It is quite clear, that it is cancelled. On the other hand, it prevents the document from being deleted, which is of course a drawback (but in terms of GoBD better). For the case, that something was attached by accident, I would opt for a grace period of maybe 10 minutes after file attachment. That also allows to quickly fix a wrong upload to a submitted document.

Possible solution for check if file can be deleted in docstatus 2. If the user has the permission to delete the document, he can delete the file.


prevent_attachment_deletion = frappe.db.get_single_value(
"ERPNext Germany Settings", "prevent_attachment_deletion"
)
if prevent_attachment_deletion:
msg = _(
"Attachments to the submitted record {0} of type {1} cannot be deleted."
).format(doc.attached_to_name, _(doc.attached_to_doctype))

if "System Manager" in frappe.get_roles():
msg += " " + _("You can allow deletion via <b>ERPNext Germany Settings</b>.")

frappe.throw(msg, title=_("Attachment Deletion Restricted"))
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"prevent_gaps_in_transaction_naming"
"prevent_gaps_in_transaction_naming",
"prevent_attachment_deletion"
],
"fields": [
{
"default": "1",
"description": "Allow deletion of the most recent record only. Applies to <b>Quotation</b>, <b>Sales Order</b> and <b>Sales Invoice</b>.",
"description": "If enabled, allows deletion of the most recent record only. Applies to <b>Quotation</b>, <b>Sales Order</b> and <b>Sales Invoice</b>.",
"fieldname": "prevent_gaps_in_transaction_naming",
"fieldtype": "Check",
"label": "Prevent Gaps In Transaction Naming"
},
{
"default": "0",
"description": "If enabled, disallows deletion of files attached to a submitted transaction.",
"fieldname": "prevent_attachment_deletion",
"fieldtype": "Check",
"label": "Prevent Attachment Deletion"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2025-02-23 17:38:08.351007",
"modified": "2025-02-26 00:02:15.955896",
"modified_by": "Administrator",
"module": "ERPNext Germany",
"name": "ERPNext Germany Settings",
Expand Down
5 changes: 5 additions & 0 deletions erpnext_germany/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@
"Sales Invoice": {
"on_trash": "erpnext_germany.custom.sales.on_trash",
},
"File": {
"on_trash": "erpnext_germany.custom.file.on_trash",
},
}

# doc_events = {}
Expand Down Expand Up @@ -196,6 +199,8 @@
# "erpnext_germany.auth.validate"
# ]

boot_session = "erpnext_germany.boot.boot_session"

germany_property_setters = {
"Employee": [
("salary_currency", "default", "EUR", "Small Text"),
Expand Down
49 changes: 46 additions & 3 deletions erpnext_germany/locale/de.po
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: ERPNext Germany VERSION\n"
"Report-Msgid-Bugs-To: hallo@alyf.de\n"
"POT-Creation-Date: 2025-02-20 17:53+0053\n"
"POT-Creation-Date: 2025-02-26 00:02+0053\n"
"PO-Revision-Date: 2025-02-20 11:46+0100\n"
"Last-Translator: hallo@alyf.de\n"
"Language: de\n"
Expand Down Expand Up @@ -96,6 +96,14 @@ msgstr "April"
msgid "Arrival or Departure"
msgstr "Ankunft oder Abreise"

#: erpnext_germany/custom/file.py:48
msgid "Attachment Deletion Restricted"
msgstr "Löschen von Anhängen eingeschränkt"

#: erpnext_germany/custom/file.py:41
msgid "Attachments to the submitted record {0} of type {1} cannot be deleted."
msgstr "Anhänge zu der gebuchten Transaktion {0} vom Typ {1} können nicht gelöscht werden."

#: erpnext_germany/erpnext_germany/report/summen__und_saldenliste/summen__und_saldenliste.js:36
msgid "August"
msgstr "August"
Expand Down Expand Up @@ -168,7 +176,7 @@ msgstr "Dienstreise-Region"
msgid "Business Trip Settings"
msgstr "Dienstreise-Einstellungen"

#: erpnext_germany/custom/sales.py:15
#: erpnext_germany/custom/sales.py:18
msgid "Cannot delete this transaction"
msgstr "Diese Transaktion kann nicht gelöscht werden"

Expand Down Expand Up @@ -316,6 +324,13 @@ msgstr "Entfernung"
msgid "ERPNext Germany"
msgstr "ERPNext-Deutschland"

#. Name of a DocType
#. Label of a Link in the Germany Workspace
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
#: erpnext_germany/erpnext_germany/workspace/germany/germany.json
msgid "ERPNext Germany Settings"
msgstr "ERPNext-Deutschland-Einstellungen"

#. Label of a Data field in DocType 'Business Letter'
#: erpnext_germany/erpnext_germany/doctype/business_letter/business_letter.json
msgid "Email"
Expand Down Expand Up @@ -421,6 +436,18 @@ msgstr "Hat Nebenbeschäftigungen"
msgid "Highest School Qualification"
msgstr "Höchster Schulabschluss"

#. Description of the 'Prevent Gaps In Transaction Naming' (Check) field in
#. DocType 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "If enabled, allows deletion of the most recent record only. Applies to <b>Quotation</b>, <b>Sales Order</b> and <b>Sales Invoice</b>."
msgstr "Falls aktiviert, ist nur die neueste Transaktion vom Typ <b>Angebot</b>, <b>Auftrag</b> und <b>Ausgangsrechnung</b> löschbar."

#. Description of the 'Prevent Attachment Deletion' (Check) field in DocType
#. 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "If enabled, disallows deletion of files attached to a submitted transaction."
msgstr "Falls aktiviert, können Anhängen einer gebuchten Transaktion nicht gelöscht werden."

#: erpnext_germany/erpnext_germany/doctype/vat_id_check/vat_id_check_list.js:8
msgid "Invalid"
msgstr "Ungültig"
Expand Down Expand Up @@ -521,7 +548,7 @@ msgstr "November"
msgid "October"
msgstr "Oktober"

#: erpnext_germany/custom/sales.py:12
#: erpnext_germany/custom/sales.py:15
msgid "Only the most recent {0} can be deleted in order to avoid gaps in numbering."
msgstr "Nur die neueste Transaktion vom Typ {0} kann gelöscht werden, um Lücken in der Nummerierung zu vermeiden."

Expand Down Expand Up @@ -566,6 +593,16 @@ msgstr "Geplant"
msgid "Please enter a start and end date of the trip!"
msgstr "Bitte geben Sie das Start- und Enddatum Ihrer Reise ein!"

#. Label of a Check field in DocType 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "Prevent Attachment Deletion"
msgstr "Löschen von Anhängen verhindern"

#. Label of a Check field in DocType 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "Prevent Gaps In Transaction Naming"
msgstr "Lücken in Nummernkreisen verhindern"

#. Label of a Tab Break field in DocType 'Business Letter'
#: erpnext_germany/erpnext_germany/doctype/business_letter/business_letter.json
msgid "Preview"
Expand Down Expand Up @@ -740,6 +777,7 @@ msgstr ""
#: erpnext_germany/erpnext_germany/doctype/business_trip/business_trip.json
#: erpnext_germany/erpnext_germany/doctype/business_trip_region/business_trip_region.json
#: erpnext_germany/erpnext_germany/doctype/business_trip_settings/business_trip_settings.json
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
#: erpnext_germany/erpnext_germany/doctype/religious_denomination/religious_denomination.json
#: erpnext_germany/erpnext_germany/doctype/vat_id_check/vat_id_check.json
msgid "System Manager"
Expand Down Expand Up @@ -900,6 +938,10 @@ msgstr "Ganztägig"
msgid "Working Hours Per Week"
msgstr "Arbeitsstunden pro Woche"

#: erpnext_germany/custom/file.py:46
msgid "You can allow deletion via <b>ERPNext Germany Settings</b>."
msgstr "Sie können das Löschen von Anhängen in den <b>ERPNext Deutschland Einstellungen</b> erlauben."

#: erpnext_germany/public/js/erpnext_germany/business_letter.js:7
msgid "You can use {0} in the Subject and Content fields for dynamic values.<br><br>All address details are available under the <code>address</code> object (e.g., <code>{{ address.city }}</code>), contact details under the <code>contact</code> object (e.g., <code>{{ contact.first_name }}</code>), and any specific information related to the dynamically linked document under the <code>reference</code> object (e.g., <code>{{ reference.some_field }}</code>).<br><br>"
msgstr "Sie können in den Betreff- und Inhalt-Feldern {0} für dynamische Werte verwenden.<br><br>Alle Adressdetails sind unter dem <code>address</code>-Objekt verfügbar (z.B., <code>{{ address.city }}</code>), Kontaktdetails unter dem <code>contact</code>-Objekt (z.B., <code>{{ contact.first_name }}</code>), und jegliche spezifischen Informationen, die mit dem dynamisch verlinkten Dokument zusammenhängen, unter dem <code>reference</code>-Objekt (z.B., <code>{{ reference.some_field }}</code>).<br><br>"
Expand Down Expand Up @@ -927,3 +969,4 @@ msgstr "{} Ungültig"
#: erpnext_germany/erpnext_germany/workspace/germany/germany.json
msgid "{} Open"
msgstr "{} Offen"

50 changes: 46 additions & 4 deletions erpnext_germany/locale/main.pot
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: ERPNext Germany VERSION\n"
"Report-Msgid-Bugs-To: hallo@alyf.de\n"
"POT-Creation-Date: 2025-02-20 17:53+0053\n"
"PO-Revision-Date: 2025-02-20 17:53+0053\n"
"POT-Creation-Date: 2025-02-26 00:02+0053\n"
"PO-Revision-Date: 2025-02-26 00:02+0053\n"
"Last-Translator: hallo@alyf.de\n"
"Language-Team: hallo@alyf.de\n"
"MIME-Version: 1.0\n"
Expand Down Expand Up @@ -94,6 +94,14 @@ msgstr ""
msgid "Arrival or Departure"
msgstr ""

#: erpnext_germany/custom/file.py:48
msgid "Attachment Deletion Restricted"
msgstr ""

#: erpnext_germany/custom/file.py:41
msgid "Attachments to the submitted record {0} of type {1} cannot be deleted."
msgstr ""

#: erpnext_germany/erpnext_germany/report/summen__und_saldenliste/summen__und_saldenliste.js:36
msgid "August"
msgstr ""
Expand Down Expand Up @@ -166,7 +174,7 @@ msgstr ""
msgid "Business Trip Settings"
msgstr ""

#: erpnext_germany/custom/sales.py:15
#: erpnext_germany/custom/sales.py:18
msgid "Cannot delete this transaction"
msgstr ""

Expand Down Expand Up @@ -314,6 +322,13 @@ msgstr ""
msgid "ERPNext Germany"
msgstr ""

#. Name of a DocType
#. Label of a Link in the Germany Workspace
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
#: erpnext_germany/erpnext_germany/workspace/germany/germany.json
msgid "ERPNext Germany Settings"
msgstr ""

#. Label of a Data field in DocType 'Business Letter'
#: erpnext_germany/erpnext_germany/doctype/business_letter/business_letter.json
msgid "Email"
Expand Down Expand Up @@ -419,6 +434,18 @@ msgstr ""
msgid "Highest School Qualification"
msgstr ""

#. Description of the 'Prevent Gaps In Transaction Naming' (Check) field in
#. DocType 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "If enabled, allows deletion of the most recent record only. Applies to <b>Quotation</b>, <b>Sales Order</b> and <b>Sales Invoice</b>."
msgstr ""

#. Description of the 'Prevent Attachment Deletion' (Check) field in DocType
#. 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "If enabled, disallows deletion of files attached to a submitted transaction."
msgstr ""

#: erpnext_germany/erpnext_germany/doctype/vat_id_check/vat_id_check_list.js:8
msgid "Invalid"
msgstr ""
Expand Down Expand Up @@ -519,7 +546,7 @@ msgstr ""
msgid "October"
msgstr ""

#: erpnext_germany/custom/sales.py:12
#: erpnext_germany/custom/sales.py:15
msgid "Only the most recent {0} can be deleted in order to avoid gaps in numbering."
msgstr ""

Expand Down Expand Up @@ -564,6 +591,16 @@ msgstr ""
msgid "Please enter a start and end date of the trip!"
msgstr ""

#. Label of a Check field in DocType 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "Prevent Attachment Deletion"
msgstr ""

#. Label of a Check field in DocType 'ERPNext Germany Settings'
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
msgid "Prevent Gaps In Transaction Naming"
msgstr ""

#. Label of a Tab Break field in DocType 'Business Letter'
#: erpnext_germany/erpnext_germany/doctype/business_letter/business_letter.json
msgid "Preview"
Expand Down Expand Up @@ -738,6 +775,7 @@ msgstr ""
#: erpnext_germany/erpnext_germany/doctype/business_trip/business_trip.json
#: erpnext_germany/erpnext_germany/doctype/business_trip_region/business_trip_region.json
#: erpnext_germany/erpnext_germany/doctype/business_trip_settings/business_trip_settings.json
#: erpnext_germany/erpnext_germany/doctype/erpnext_germany_settings/erpnext_germany_settings.json
#: erpnext_germany/erpnext_germany/doctype/religious_denomination/religious_denomination.json
#: erpnext_germany/erpnext_germany/doctype/vat_id_check/vat_id_check.json
msgid "System Manager"
Expand Down Expand Up @@ -898,6 +936,10 @@ msgstr ""
msgid "Working Hours Per Week"
msgstr ""

#: erpnext_germany/custom/file.py:46
msgid "You can allow deletion via <b>ERPNext Germany Settings</b>."
msgstr ""

#: erpnext_germany/public/js/erpnext_germany/business_letter.js:7
msgid "You can use {0} in the Subject and Content fields for dynamic values.<br><br>All address details are available under the <code>address</code> object (e.g., <code>{{ address.city }}</code>), contact details under the <code>contact</code> object (e.g., <code>{{ contact.first_name }}</code>), and any specific information related to the dynamically linked document under the <code>reference</code> object (e.g., <code>{{ reference.some_field }}</code>).<br><br>"
msgstr ""
Expand Down
1 change: 1 addition & 0 deletions erpnext_germany/public/js/erpnext_germany.bundle.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import "./erpnext_germany/business_letter.js";
import "./erpnext_germany/prevent_attachment_deletion.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
$(document).ready(function () {
$(document).on("form-load", function (event, frm) {
if (!frappe.boot.sysdefaults.prevent_attachment_deletion || frappe.user.has_role("System Manager")) {
return;
}

if (![
"Quotation",
"Sales Order",
"Delivery Note",
"Sales Invoice",
"Request for Quotation",
"Supplier Quotation",
"Purchase Order",
"Purchase Receipt",
"Purchase Invoice",
"Journal Entry",
"Payment Entry",
"Asset",
].includes(frm.doctype)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extend list if file.py is extended

return;
}

frappe.ui.form.on(frm.doctype, {
refresh: () => {
$(".attachment-row .remove-btn").hide();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When uploading a new file in the submitted state, the list of attachment reloads and the "hide" is temporarily gone. Server side check still prevents from delete.

Same principle when uploading a file in draft. One the site is reloaded the icon to "delete" is hidden, so the file cannot be removed client side from the document. Server side does not prevent deletion as intended.

},
});
});
});