Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
71aedfd
fix: add frappe-dependencies for Frappe Cloud compatibility
Feb 4, 2026
11231b5
fix: prevent modifications to transmitted edocuments
maasanto Feb 11, 2026
b4ff792
fix: match buyer company by tax ID instead of name
maasanto Feb 11, 2026
025391d
fix: format terminal_statuses tuple for ruff compliance
maasanto Feb 12, 2026
618039c
fix: make status field read-only
maasanto Feb 12, 2026
9562017
fix: resolve missing item, tax account and payment schedule in PEPPOL…
maasanto Feb 12, 2026
7935456
Merge pull request #35 from prilk-consulting/feat/matching-dialog-in-…
pritambiswal Feb 13, 2026
4026a6f
Merge pull request #36 from maasanto/fix-protect-transmitted-edocuments
pritambiswal Feb 13, 2026
2838814
Merge pull request #37 from maasanto/fix-buyer-tax-id-matching
pritambiswal Feb 13, 2026
f38d7a3
Merge pull request #38 from maasanto/fix/peppol-parser-missing-values
pritambiswal Feb 13, 2026
9c588ec
fix: PEPPOL generator validation fixes and credit note sign handling
Feb 26, 2026
d4b8f42
Merge pull request #39 from prilk-consulting/fix/peppol-credit-note-a…
pritambiswal Feb 26, 2026
6150684
chore: add apt deploy dependencies for lxml
maasanto Mar 22, 2026
0dee95f
Merge pull request #40 from maasanto/chore/apt-deploy-deps
pritambiswal Apr 2, 2026
6bbe5f6
fix: handle negative qty, use tax templates, and fix company detection
Apr 3, 2026
12f49f4
fix: strip HTML from item description in XML and prevent edocument copy
Apr 7, 2026
90be39f
Merge pull request #42 from prilk-consulting/fix/incoming-negative-qt…
pritambiswal Apr 7, 2026
1e38d84
Merge pull request #43 from prilk-consulting/fix/html-description-and…
pritambiswal Apr 7, 2026
d615552
fix: isolate preview CSS in iframe and negate credit note quantities
Apr 9, 2026
b8d3f50
Merge pull request #44 from prilk-consulting/fix/preview-iframe-css-i…
pritambiswal May 11, 2026
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
31 changes: 19 additions & 12 deletions edocument/edocument/doctype/edocument/edocument.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ frappe.ui.form.on("EDocument", {
});

function setup_action_buttons(frm) {
// Generate XML - for outgoing documents
if (frm.doc.edocument_source_document && frm.doc.edocument_profile) {
const is_transmitted = frm.doc.status === "Transmission Successful";

// Generate XML - for outgoing documents (not already transmitted)
if (frm.doc.edocument_source_document && frm.doc.edocument_profile && !is_transmitted) {
frm.add_custom_button(
__("Generate XML"),
() => {
Expand All @@ -35,7 +37,11 @@ function setup_action_buttons(frm) {
if (!r.message && !frm.doc.xml_file) return;

frm.add_custom_button(__("Preview EDocument"), () => show_preview(frm), __("Actions"));
frm.add_custom_button(__("Validate XML"), () => validate_xml(frm), __("Actions"));

if (!is_transmitted) {
frm.add_custom_button(__("Validate XML"), () => validate_xml(frm), __("Actions"));
}

frm.add_custom_button(__("Match Document"), () => match_document(frm), __("Actions"));
frm.add_custom_button(
__("Create Document"),
Expand All @@ -62,15 +68,16 @@ function show_preview(frm) {
freeze_message: __("Generating preview..."),
callback: (r) => {
if (!r.message) return;
frm.get_field("edocument_preview")?.set_value(r.message);
frm.get_field("edocument_preview")?.$wrapper?.css({
width: "100%",
padding: "15px",
background: "#fff",
border: "1px solid #e0e0e0",
borderRadius: "4px",
marginTop: "10px",
});
const field = frm.get_field("edocument_preview");
if (!field) return;

// Render preview in an iframe to isolate its CSS from ERPNext
const iframe = `<iframe srcdoc="${r.message.replace(/"/g, "&quot;")}"
style="width:100%;height:600px;border:1px solid #e0e0e0;border-radius:4px;background:#fff;"
frameborder="0">
</iframe>`;
field.set_value(iframe);
field.$wrapper?.css({ width: "100%", marginTop: "10px" });
},
});
}
Expand Down
3 changes: 2 additions & 1 deletion edocument/edocument/doctype/edocument/edocument.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Status",
"options": "\nValidation Successful\nValidation Failed\nMatching Successful\nMatching Failed\nTransmission Successful\nTransmission Failed"
"options": "\nValidation Successful\nValidation Failed\nMatching Successful\nMatching Failed\nTransmission Successful\nTransmission Failed",
"read_only": 1
},
{
"fieldname": "country",
Expand Down
30 changes: 23 additions & 7 deletions edocument/edocument/doctype/edocument/edocument.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ def generate_xml(self):
Note: This method only generates XML - validation is handled separately.
This is the public method called from the button.
"""
if self.status == "Transmission Successful":
frappe.throw(_("Cannot regenerate XML for an already transmitted document."))

try:
file_name = self._generate_xml_internal()
# Save the document to persist status and error field changes
Expand Down Expand Up @@ -219,6 +222,9 @@ def validate_xml(self):
Updates the status and error fields based on validation results.
This is the public method called from the button.
"""
if self.status == "Transmission Successful":
frappe.throw(_("Cannot re-validate an already transmitted document."))

try:
self._validate_xml_internal()
# Save the document to persist status and error field changes
Expand Down Expand Up @@ -271,16 +277,16 @@ def before_save(self):
)

# Auto-detect EDocument fields (company, etc.) from XML using profile-specific detector
if self.edocument_profile and has_xml and not self.company:
if self.edocument_profile and has_xml:
try:
xml_bytes = self._get_xml_from_attached_files()
if xml_bytes:
from edocument.edocument.detector import get_edocument_fields

detected_fields = get_edocument_fields(xml_bytes, self.edocument_profile)
# Set detected fields on the document
# Set detected fields — override defaults from Frappe
for field, value in detected_fields.items():
if hasattr(self, field) and not getattr(self, field):
if hasattr(self, field) and value:
setattr(self, field, value)
except Exception as e:
frappe.log_error(
Expand All @@ -301,7 +307,17 @@ def before_save(self):
f"Error during automatic XML generation for EDocument {self.name}: {error_msg}"
)

if self.edocument_profile:
# Don't overwrite status for documents that reached a terminal state
# (already transmitted or matched) — re-validation would reset status
# back to "Validation Successful" and re-enable the Send button.
terminal_statuses = (
"Transmission Successful",
"Transmission Failed",
"Matching Successful",
"Matching Failed",
)

if self.edocument_profile and self.status not in terminal_statuses:
# Validate XML automatically
try:
self._validate_xml_internal()
Expand Down Expand Up @@ -347,16 +363,16 @@ def on_update(self):
)

# Auto-detect EDocument fields (company, etc.) from XML using profile-specific detector
if self.edocument_profile and has_xml and not self.company:
if self.edocument_profile and has_xml:
try:
xml_bytes = self._get_xml_from_attached_files()
if xml_bytes:
from edocument.edocument.detector import get_edocument_fields

detected_fields = get_edocument_fields(xml_bytes, self.edocument_profile)
# Update detected fields directly in database
# Update detected fields — override defaults from Frappe
for field, value in detected_fields.items():
if hasattr(self, field) and not getattr(self, field):
if hasattr(self, field) and value:
self.db_set(field, value, update_modified=False)
except Exception as e:
frappe.log_error(
Expand Down
Loading
Loading