Skip to content

Commit

Permalink
feat(payment entry): add cancel cfdi flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Ahuahuachi committed Aug 30, 2024
1 parent 043fdaf commit ea0f4c9
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 21 deletions.
12 changes: 6 additions & 6 deletions erpnext_mexico_compliance/fixtures/custom_field.json
Original file line number Diff line number Diff line change
Expand Up @@ -1712,7 +1712,7 @@
"label": "Cancellation reason",
"length": 0,
"link_filters": null,
"mandatory_depends_on": "eval:doc.docstatus == 1",
"mandatory_depends_on": "",
"modified": "2024-08-17 18:57:42.717062",
"module": "ERPNext Mexico Compliance",
"name": "Sales Invoice-cancellation_reason",
Expand Down Expand Up @@ -1905,7 +1905,7 @@
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
Expand All @@ -1925,7 +1925,7 @@
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"ignore_xss_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
Expand Down Expand Up @@ -1961,7 +1961,7 @@
},
{
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
Expand All @@ -1981,7 +1981,7 @@
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"ignore_xss_filter": 1,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
Expand Down Expand Up @@ -2015,4 +2015,4 @@
"unique": 0,
"width": null
}
]
]
98 changes: 98 additions & 0 deletions erpnext_mexico_compliance/overrides/payment_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class PaymentEntry(CommonController, payment_entry.PaymentEntry):

mx_payment_mode: DF.Data
mx_stamped_xml: DF.HTMLEditor
cancellation_reason: DF.Link
substitute_payment_entry: DF.Link
cancellation_acknowledgement: DF.HTMLEditor

@property
def company_address(self) -> str:
Expand Down Expand Up @@ -171,6 +174,10 @@ def stamp_cfdi(self, certificate: str):

self.mx_stamped_xml = data
self.save()

self.attach_pdf()
self.attach_xml()

return message

@frappe.whitelist()
Expand Down Expand Up @@ -215,6 +222,97 @@ def attach_xml(self) -> Document:
xml = self.mx_stamped_xml
return attach_file(file_name, xml, self.doctype, self.name, is_private=1)

@property
def requires_relationship(self) -> int:
"""Indicates whether a relationship with another payment entry is required for the
cancellation reason.
Returns:
int: 1 if a relationship is required, 0 otherwise.
"""
if not self.cancellation_reason:
return 0
reason = frappe.get_doc("Cancellation Reason", self.cancellation_reason)
return reason.requires_relationship

@property
def cfdi_uuid(self) -> str | None:
"""CFDI UUID from the stamped XML.
Returns:
str | None: The CFDI UUID if the stamped XML is available, otherwise None.
"""
if not self.mx_stamped_xml:
return None
cfdi = cfdi40.CFDI.from_string(self.mx_stamped_xml.encode("utf-8"))
return cfdi.get("Complemento", {}).get("TimbreFiscalDigital", {}).get("UUID")

def validate_cancel_reason(self):
"""Validates whether a cancellation reason is provided before cancelling a payment entry.
This function checks if a cancellation reason is set for the current payment entry.
If no cancellation reason is found, it throws an error with a corresponding message.
"""
if not self.cancellation_reason:
msg = _("A Cancellation Reason is required.")
title = _("Invalid Cancellation Reason")
frappe.throw(msg, title=title)

def validate_substitute_payment_entry(self):
"""Validates whether a substitute payment entry is required before cancelling a payment
entry.
This function checks if a substitute payment entry is set for the current payment entry,
and if the cancellation reason requires a substitute payment entry.
If no substitute payment entry is found when required, it throws an error with a
corresponding message.
"""
reason = frappe.get_doc("Cancellation Reason", self.cancellation_reason)
if reason.requires_relationship and not self.substitute_payment_entry:
msg = _("The reason of cancellation {} requires a substitute invoice")
msg = msg.format(self.cancellation_reason)
frappe.throw(msg)

@frappe.whitelist()
def cancel_cfdi(self, certificate: str):
"""Cancels the CFDI document of this payment entry.
Args:
certificate (str): The name of the Digital Signing Certificate to use for cancellation.
Raises:
WSClientException: If an error occurs during the cancellation process.
Returns:
str: A message indicating the result of the cancellation operation.
"""
self.validate_cancel_reason()
self.validate_substitute_payment_entry()

cfdi = cfdi40.CFDI.from_string(self.mx_stamped_xml.encode("utf-8"))
ws = get_ws_client()

substitute = (
frappe.get_doc("Payment Entry", self.substitute_payment_entry)
if self.substitute_payment_entry
else None
)

try:
ack, message = ws.cancel(
certificate,
cfdi,
self.cancellation_reason,
substitute.cfdi_uuid if substitute else None,
)
except WSClientException as e:
frappe.throw(str(e), title=_("CFDI Web Service Error"))

self.cancellation_acknowledgement = ack
self.save()

return message


def get_installment_number(
doctype: str, docname: str, payment_entry_name: str
Expand Down
14 changes: 7 additions & 7 deletions erpnext_mexico_compliance/overrides/sales_invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def stamp_cfdi(self, certificate: str):
frappe.throw(str(e), title=_("CFDI Web Service Error"))

self.mx_stamped_xml = data
self.db_update()
self.save()

self.attach_pdf()
self.attach_xml()
Expand Down Expand Up @@ -307,7 +307,7 @@ def requires_relationship(self) -> int:

@property
def cfdi_uuid(self) -> str | None:
"""Retrieves the CFDI UUID from the stamped XML.
"""CFDI UUID from the stamped XML.
Returns:
str | None: The CFDI UUID if the stamped XML is available, otherwise None.
Expand All @@ -324,7 +324,7 @@ def validate_cancel_reason(self):
If no cancellation reason is found, it throws an error with a corresponding message.
"""
if not self.cancellation_reason:
msg = _("A Cancellation Reason is required to cancel this sales invoice.")
msg = _("A Cancellation Reason is required.")
title = _("Invalid Cancellation Reason")
frappe.throw(msg, title=title)

Expand All @@ -342,8 +342,8 @@ def validate_substitute_invoice(self):
frappe.throw(msg)

@frappe.whitelist()
def cancel_cfdi(self, certificate):
"""Cancels a CFDI document.
def cancel_cfdi(self, certificate: str):
"""Cancels the CFDI document.
Args:
certificate (str): The name of the Digital Signing Certificate to use for cancellation.
Expand All @@ -369,13 +369,13 @@ def cancel_cfdi(self, certificate):
certificate,
cfdi,
self.cancellation_reason,
self.substitute_invoice.cfdi_uuid if substitute_invoice else None,
substitute_invoice.cfdi_uuid if substitute_invoice else None,
)
except WSClientException as e:
frappe.throw(str(e), title=_("CFDI Web Service Error"))

self.cancellation_acknowledgement = acknowledgement
self.db_update()
self.save()

return message

Expand Down
55 changes: 52 additions & 3 deletions erpnext_mexico_compliance/public/js/payment_entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ async function attachFile(frm, ext) {
});
frm.reload_doc();
} catch (error) {
const { responseJSON } = error;
frappe.throw(responseJSON ? responseJSON.exception : error);
const { message } = error;
frappe.throw(message ? message : error);
}
}

Expand Down Expand Up @@ -68,6 +68,53 @@ async function addAttachXmlButton(frm) {
}
}

function cancel(frm) {
const {
mx_stamped_xml,
cancellation_reason,
requires_relationship,
substitute_payment_entry,
} = frm.doc;

if (mx_stamped_xml) {
if (!cancellation_reason) {
const msg = __(
"A Cancellation Reason is required to cancel this sales invoice."
);
frappe.throw(msg);
}
}

if (requires_relationship && !substitute_payment_entry) {
const msg = __(
"The Cancellation Reason requires a substitute payment entry."
);
frappe.throw(msg);
}

frappe.prompt(
[
{
label: __("Digital Signing Certificate"),
fieldname: "certificate",
fieldtype: "Link",
options: "Digital Signing Certificate",
filters: { company: frm.doc.company },
},
],
async ({ certificate }) => {
const { message: cfdi_msg } = await frm.call("cancel_cfdi", {
certificate,
});
frappe.show_alert({ message: cfdi_msg, indicator: "green" });
const { message: cancelled } = await frm.call("cancel");
frappe.show_alert({ message: cancelled, indicator: "green" });
frm.reload_doc();
},
__("Select a Certificate to sign the CFDI")
);
}

function refresh(frm) {
const { docstatus, mx_stamped_xml } = frm.doc;

Expand All @@ -78,7 +125,9 @@ function refresh(frm) {

switch (docstatus) {
case 1:
if (!mx_stamped_xml) {
if (mx_stamped_xml) {
frm.page.set_secondary_action(__("Cancel"), () => cancel(frm));
} else {
frm.add_custom_button(__("Stamp CFDI"), () => stampCfdi(frm));
}
break;
Expand Down
10 changes: 5 additions & 5 deletions erpnext_mexico_compliance/public/js/sales_invoice.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ async function attachFile(frm, ext) {
});
frm.reload_doc();
} catch (error) {
const { responseJSON } = error;
frappe.throw(responseJSON ? responseJSON.exception : error);
const { message } = error;
frappe.throw(message ? message : error);
}
}

Expand Down Expand Up @@ -73,7 +73,7 @@ function cancel(frm) {
mx_stamped_xml,
cancellation_reason,
requires_relationship,
cancellation_related_invoices,
substitute_invoice,
} = frm.doc;

if (mx_stamped_xml) {
Expand All @@ -84,8 +84,8 @@ function cancel(frm) {
frappe.throw(msg);
}

if (requires_relationship && cancellation_related_invoices === 0) {
const msg = __("The Cancellation Reason requires a related invoice.");
if (requires_relationship && !substitute_invoice) {
const msg = __("The Cancellation Reason requires a substitute invoice.");
frappe.throw(msg);
}

Expand Down

0 comments on commit ea0f4c9

Please sign in to comment.