Skip to content

Commit

Permalink
Merge pull request #5 from TI-Sin-Problemas/feat/cfdi-stamping
Browse files Browse the repository at this point in the history
feat: add cfdi stamping for Sales Invoice and Payment Entry
  • Loading branch information
Ahuahuachi authored Aug 30, 2024
2 parents bf13cf6 + 43339c4 commit 7f53140
Show file tree
Hide file tree
Showing 71 changed files with 7,300 additions and 729 deletions.
11 changes: 7 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,20 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- name: Install MariaDB Client
run: sudo apt-get install mariadb-client-10.6

- name: Setup
run: |
pip install frappe-bench
bench init --frappe-branch version-14 --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mysql --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
bench init --frappe-branch version-15 --skip-redis-config-generation --skip-assets --python "$(which python)" ~/frappe-bench
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- name: Install
working-directory: /home/runner/frappe-bench
run: |
bench get-app erpnext --branch version-14
bench get-app erpnext --branch version-15
bench get-app erpnext_mexico_compliance $GITHUB_WORKSPACE
bench setup requirements --dev
bench new-site --db-root-password root --admin-password admin test_site
Expand Down
12 changes: 12 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"tabWidth": 2,
"useTabs": false,
"overrides": [
{
"files": "*.json",
"options": {
"tabWidth": 1
}
}
]
}
75 changes: 75 additions & 0 deletions erpnext_mexico_compliance/controllers/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
Copyright (c) 2024, TI Sin Problemas and contributors
For license information, please see license.txt
"""

import abc

import frappe
from frappe.model.naming import NamingSeries
from satcfdi.cfdi import CFDI
from satcfdi.create.cfd import cfdi40

from ..erpnext_mexico_compliance.doctype.digital_signing_certificate.digital_signing_certificate import (
DigitalSigningCertificate,
)


class CommonController(abc.ABC):
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from frappe.types import DF

name: DF.Data
naming_series: DF.Data

@property
def cfdi_series(self) -> str:
"""CFDI Series code"""
prefix = str(NamingSeries(self.naming_series).get_prefix())
return prefix if prefix[-1].isalnum() else prefix[:-1]

@property
def cfdi_folio(self) -> str:
"""CFDI Folio number"""
prefix = str(NamingSeries(self.naming_series).get_prefix())
return str(int(self.name.replace(prefix, "")))

@abc.abstractmethod
def get_cfdi_voucher(self, csd: DigitalSigningCertificate) -> cfdi40.Comprobante:
"""Generates a CFDI voucher using the provided digital signing certificate.
Args:
csd (DigitalSigningCertificate): The digital signing certificate.
Returns:
cfdi40.Comprobante: The generated CFDI voucher.
"""
raise NotImplementedError("cfdi_voucher method is not implemented")

def sign_cfdi(self, certificate: str) -> CFDI:
"""Signs a CFDI document with the provided digital signing certificate.
Args:
certificate (str): The name of the Digital Signing Certificate to use for signing.
Returns:
CFDI: The signed and processed CFDI document.
"""
csd = frappe.get_doc("Digital Signing Certificate", certificate)
voucher = self.get_cfdi_voucher(csd)
voucher.sign(csd.signer)
return voucher.process()

@abc.abstractmethod
def stamp_cfdi(self, certificate: str):
"""Stamps a CFDI document with the provided digital signing certificate.
Args:
certificate (str): The name of the Digital Signing Certificate to use for signing.
Returns:
CFDI: A message indicating the result of the stamping operation.
"""
raise NotImplementedError("stamp_cfdi method is not implemented")
38 changes: 38 additions & 0 deletions erpnext_mexico_compliance/controllers/queries.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Copyright (c) 2024, TI Sin Problemas and contributors
For license information, please see license.txt"""

import frappe
from erpnext.controllers.queries import get_fields
from frappe.desk.reportview import get_filters_cond


@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def cfdi_use_query(doctype, txt, searchfield, start, page_len, filters: dict):
doctype = "SAT CFDI Use"
customer = filters.pop("customer", None)
cfdi_use_table = "`tabSAT CFDI Use`"
tax_regime_table = "`tabSAT CFDI Use Tax Regime`"

if customer:
customer = frappe.get_doc("Customer", customer)
filters.update({"tax_regime": customer.mx_tax_regime})

fields = [f"{cfdi_use_table}.{f}" for f in get_fields(doctype, [])]
filters_cond = get_filters_cond(doctype, filters, [])

item_list = frappe.db.sql(
f"""select {", ".join(fields)} from {cfdi_use_table}
left join {tax_regime_table} on {tax_regime_table}.parent = {cfdi_use_table}.name
where
{cfdi_use_table}.enabled = 1
and (
{cfdi_use_table}.key_name like "%{txt}%"
or {cfdi_use_table}.{searchfield} like "%{txt}%"
)
{filters_cond}
order by {cfdi_use_table}.{searchfield} asc
limit {page_len} offset {start}
"""
)
return item_list
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024, TI Sin Problemas and contributors
// For license information, please see license.txt

// frappe.ui.form.on("Cancellation Reason", {
// refresh(frm) {

// },
// });
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"actions": [],
"allow_import": 1,
"allow_rename": 1,
"autoname": "field:code",
"creation": "2024-08-17 16:16:03.891107",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"code",
"description",
"requires_relationship"
],
"fields": [
{
"allow_in_quick_entry": 1,
"fieldname": "code",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Code",
"length": 2,
"reqd": 1,
"unique": 1
},
{
"allow_in_quick_entry": 1,
"fieldname": "description",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Description",
"reqd": 1
},
{
"default": "0",
"description": "Check this field if the cancelled invoice needs to have another related invoice",
"fieldname": "requires_relationship",
"fieldtype": "Check",
"label": "Requires relationship"
}
],
"hide_toolbar": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2024-08-17 18:07:26.590842",
"modified_by": "Administrator",
"module": "ERPNext Mexico Compliance",
"name": "Cancellation Reason",
"naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"show_title_field_in_link": 1,
"sort_field": "description",
"sort_order": "DESC",
"states": [],
"title_field": "description"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Copyright (c) 2024, TI Sin Problemas and contributors
For license information, please see license.txt"""

# import frappe
from frappe.model.document import Document


class CancellationReason(Document):
"""Invoice Cancellation Reason"""

# 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

code: DF.Data
description: DF.Data
requires_relationship: DF.Check
# end: auto-generated types
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, TI Sin Problemas and Contributors
# See license.txt

# import frappe
from frappe.tests.utils import FrappeTestCase


class TestCancellationReason(FrappeTestCase):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2024, TI Sin Problemas and contributors
// For license information, please see license.txt

frappe.ui.form.on("CFDI Stamping Settings", {
// refresh(frm) {},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2024-08-09 14:55:18.675674",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"web_service_section",
"api_key",
"test_mode",
"column_break_zjrm",
"available_credits"
],
"fields": [
{
"fieldname": "web_service_section",
"fieldtype": "Section Break",
"label": "Web Service"
},
{
"fieldname": "api_key",
"fieldtype": "Password",
"label": "API Key"
},
{
"default": "1",
"fieldname": "test_mode",
"fieldtype": "Check",
"label": "Test mode"
},
{
"fieldname": "column_break_zjrm",
"fieldtype": "Column Break"
},
{
"depends_on": "api_key",
"description": "1 credit is consumed every time a CFDI gets stamped",
"fieldname": "available_credits",
"fieldtype": "Int",
"is_virtual": 1,
"label": "Available credits"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2024-08-13 01:44:43.369481",
"modified_by": "Administrator",
"module": "ERPNext Mexico Compliance",
"name": "CFDI Stamping Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"states": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) 2024, TI Sin Problemas and contributors
# For license information, please see license.txt

import frappe
from erpnext_mexico_compliance import ws_client
from frappe import _
from frappe.model.document import Document


class CFDIStampingSettings(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

api_key: DF.Password | None
available_credits: DF.Int
test_mode: DF.Check
# end: auto-generated types

def get_api_key(self) -> str:
"""Retrieves the API key from the CFDI Stamping Settings document.
Returns:
str: The API key.
"""
return self.get_password("api_key")

@property
def available_credits(self) -> int:
"""Retrieves the available credits from the CFDI Web Service.
Returns:
int: The number of available credits.
"""
if self.api_key:
client = ws_client.get_ws_client()
try:
available_credits = client.get_available_credits()
except ws_client.WSClientException as exception:
frappe.throw(str(exception), title=_("CFDI Web Service Error"))
else:
available_credits = 0

return available_credits
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2024, TI Sin Problemas and Contributors
# See license.txt

# import frappe
from frappe.tests.utils import FrappeTestCase


class TestCFDIStampingSettings(FrappeTestCase):
pass
Loading

0 comments on commit 7f53140

Please sign in to comment.