fiscalapi 4.0.0__tar.gz → 4.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fiscalapi
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: Genera facturas CFDI válidas ante el SAT consumiendo el API de https://www.fiscalapi.com
5
5
  Home-page: https://www.fiscalapi.com
6
6
  Author: Fiscalapi
@@ -0,0 +1,88 @@
1
+ # fiscalapi/__init__.py
2
+
3
+ # Re-exportar modelos de common_models
4
+ from .models.common_models import (
5
+ ApiResponse,
6
+ PagedList,
7
+ ValidationFailure,
8
+ BaseDto,
9
+ CatalogDto,
10
+ FiscalApiSettings,
11
+ )
12
+
13
+ # Re-exportar modelos de fiscalapi_models
14
+ from .models.fiscalapi_models import (
15
+ ProductTax,
16
+ Product,
17
+ Person,
18
+ TaxFile,
19
+ TaxCredential,
20
+ InvoiceIssuer,
21
+ InvoiceRecipient,
22
+ ItemTax,
23
+ InvoiceItem,
24
+ GlobalInformation,
25
+ RelatedInvoice,
26
+ PaidInvoiceTax,
27
+ PaidInvoice,
28
+ InvoicePayment,
29
+ InvoiceResponse,
30
+ Invoice,
31
+ CancelInvoiceRequest,
32
+ CancelInvoiceResponse,
33
+ CreatePdfRequest,
34
+ FileResponse,
35
+ SendInvoiceRequest,
36
+ )
37
+
38
+ # Re-exportar servicios
39
+ from .services.catalog_service import CatalogService
40
+ from .services.invoice_service import InvoiceService
41
+ from .services.people_service import PeopleService
42
+ from .services.product_service import ProductService
43
+ from .services.tax_file_servive import TaxFileService # Asegúrate de que coincida con tu nombre real (tax_file_service o tax_file_servive)
44
+
45
+ # Re-exportar la clase FiscalApiClient
46
+ # (asumiendo que la definición está en fiscalapi/services/fiscalapi_client.py)
47
+ from .services.fiscalapi_client import FiscalApiClient
48
+
49
+ __all__ = [
50
+ # Modelos
51
+ "ApiResponse",
52
+ "PagedList",
53
+ "ValidationFailure",
54
+ "BaseDto",
55
+ "CatalogDto",
56
+ "FiscalApiSettings",
57
+ "ProductTax",
58
+ "Product",
59
+ "Person",
60
+ "TaxFile",
61
+ "TaxCredential",
62
+ "InvoiceIssuer",
63
+ "InvoiceRecipient",
64
+ "ItemTax",
65
+ "InvoiceItem",
66
+ "GlobalInformation",
67
+ "RelatedInvoice",
68
+ "PaidInvoiceTax",
69
+ "PaidInvoice",
70
+ "InvoicePayment",
71
+ "InvoiceResponse",
72
+ "Invoice",
73
+ "CancelInvoiceRequest",
74
+ "CancelInvoiceResponse",
75
+ "CreatePdfRequest",
76
+ "FileResponse",
77
+ "SendInvoiceRequest",
78
+
79
+ # Servicios
80
+ "CatalogService",
81
+ "InvoiceService",
82
+ "PeopleService",
83
+ "ProductService",
84
+ "TaxFileService",
85
+
86
+ # Cliente principal
87
+ "FiscalApiClient",
88
+ ]
File without changes
@@ -0,0 +1,68 @@
1
+ from datetime import datetime
2
+ from typing import Any, Generic, List, Optional, TypeVar
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+ from pydantic.alias_generators import to_snake
5
+
6
+ T = TypeVar('T', bound=BaseModel)
7
+
8
+ class ApiResponse(BaseModel, Generic[T]):
9
+ succeeded: bool = Field(alias="succeeded")
10
+ message: Optional[str] = Field(alias="message")
11
+ details: Optional[str] = Field(alias="details")
12
+ data: Optional[T] = Field(None, alias="data")
13
+
14
+ model_config = ConfigDict(
15
+ populate_by_name=True,
16
+ alias_generator=to_snake
17
+ )
18
+
19
+
20
+ class PagedList(BaseModel, Generic[T]):
21
+ """Modelo para la estructura de la lista paginada."""
22
+ items: List[T] = Field(default=[], alias="items", description="Lista de elementos paginados")
23
+ page_number: int = Field(alias="pageNumber", description="Número de página actual")
24
+ total_pages: int = Field(alias="totalPages", description="Cantidad total de páginas")
25
+ total_count: int = Field(alias="totalCount", description="Cantidad total de elementos")
26
+ has_previous_page: bool = Field(alias="hasPreviousPage", description="Indica si hay una página anterior")
27
+ has_next_page: bool = Field(alias="hasNextPage", description="Indica si hay una página siguiente")
28
+
29
+
30
+ class ValidationFailure(BaseModel):
31
+ """Modelo para errores de validación."""
32
+ propertyName: str
33
+ errorMessage: str
34
+ attemptedValue: Optional[Any] = None
35
+ customState: Optional[Any] = None
36
+ severity: Optional[int] = None
37
+ errorCode: Optional[str] = None
38
+ formattedMessagePlaceholderValues: Optional[dict] = None
39
+
40
+
41
+ class BaseDto(BaseModel):
42
+ """Modelo base para DTOs."""
43
+ id: Optional[str] = Field(default=None, alias="id")
44
+ created_at: Optional[datetime] = Field(default=None, alias="createdAt")
45
+ updated_at: Optional[datetime] = Field(default=None, alias="updatedAt")
46
+
47
+ model_config = ConfigDict(populate_by_name=True)
48
+
49
+ class CatalogDto(BaseDto):
50
+ """Modelo para catálogos."""
51
+ description: str = Field(alias="description")
52
+
53
+ model_config = ConfigDict(populate_by_name=True)
54
+
55
+
56
+ class FiscalApiSettings(BaseModel):
57
+ """
58
+ Objeto que contiene la configuración necesaria para interactuar con Fiscalapi.
59
+ """
60
+ api_url: str = Field(..., description="URL base de la api.")
61
+ api_key: str = Field(..., description="Api Key")
62
+ tenant: str = Field(..., description="Tenant Key.")
63
+ api_version: str = Field("v4", description="Versión de la api.")
64
+ time_zone: str = Field("America/Mexico_City", description="Zona horaria ")
65
+
66
+ class Config:
67
+ title = "FiscalApi Settings"
68
+ description = "Configuración para Fiscalapi"
@@ -0,0 +1,323 @@
1
+ from decimal import Decimal
2
+ from pydantic import ConfigDict, EmailStr, Field
3
+ from fiscalapi.models.common_models import BaseDto, CatalogDto
4
+ from typing import Dict, List, Literal, Optional
5
+ from datetime import datetime
6
+
7
+ # products models
8
+
9
+ class ProductTax(BaseDto):
10
+ """Modelo impuesto de producto."""
11
+
12
+ rate: Decimal = Field(ge=0, le=1, alias="rate", description="Tasa de impuesto")
13
+
14
+ tax_id: Optional[Literal["001", "002", "003"]] = Field(default=None, alias="taxId", description="Impuesto")
15
+ tax: Optional[CatalogDto] = Field(default=None, alias="tax", description="Impuesto expandido")
16
+
17
+ tax_flag_id: Optional[Literal["T", "R"]] = Field(default=None, alias="taxFlagId", description="Traslado o Retención")
18
+ tax_flag: Optional[CatalogDto] = Field(default=None, alias="taxFlag", description="Traslado o Retención expandido")
19
+
20
+ tax_type_id: Optional[Literal["Tasa", "Cuota", "Exento"]] = Field(default=None, alias="taxTypeId", description="Tipo de impuesto")
21
+ tax_type: Optional[CatalogDto] = Field(default=None, alias="taxType", description="Tipo de impuesto expandido")
22
+
23
+
24
+ model_config = ConfigDict(
25
+ populate_by_name=True,
26
+ json_encoders={Decimal: str}
27
+ )
28
+
29
+ class Product(BaseDto):
30
+ """Modelo producto."""
31
+ description: str = Field(alias="description")
32
+ unit_price: Decimal = Field(alias="unitPrice")
33
+
34
+ sat_unit_measurement_id: Optional[str] = Field(default="H87", alias="satUnitMeasurementId", description="Unidad de medida SAT")
35
+ sat_unit_measurement: Optional[CatalogDto] = Field(default=None, alias="satUnitMeasurement", description="Unidad de medida SAT expandida")
36
+
37
+ sat_tax_object_id: Optional[str] = Field(default="02", alias="satTaxObjectId", description="Objeto de impuesto SAT")
38
+ sat_tax_object: Optional[CatalogDto] = Field(default=None, alias="satTaxObject", description="Objeto de impuesto SAT expandido")
39
+
40
+ sat_product_code_id: Optional[str] = Field(default="01010101", alias="satProductCodeId", description="Código de producto SAT")
41
+ sat_product_code: Optional[CatalogDto] = Field(default=None, alias="satProductCode", description="Código de producto SAT expandido")
42
+
43
+ product_taxes: Optional[list[ProductTax]] = Field(default=None, alias="productTaxes", description="Impuestos del producto")
44
+
45
+
46
+
47
+ model_config = ConfigDict(
48
+ populate_by_name=True,
49
+ json_encoders={Decimal: str}
50
+ )
51
+
52
+ # people models
53
+
54
+ class Person(BaseDto):
55
+ """Modelo persona en FiscalAPI."""
56
+
57
+ legal_name: Optional[str] = Field(default=None, alias="legalName", description="Razón social de la persona sin régimen de capital.")
58
+ email: Optional[EmailStr] = Field(default=None, alias="email", description="Correo electrónico de la persona.")
59
+ password: Optional[str] = Field(default=None, alias="password", description="Contraseña para acceder al dashboard.")
60
+ capital_regime: Optional[str] = Field(default=None, alias="CapitalRegime", description="Régimen de capital de la persona.")
61
+ sat_tax_regime_id: Optional[Literal["601", "603", "605", "606", "607", "608", "610", "611", "612", "614", "615", "616", "620", "621", "622", "623", "624", "625", "626"]] = Field(default=None, alias="satTaxRegimeId", description="Código del régimen fiscal del emisor.")
62
+ sat_tax_regime: Optional[CatalogDto] = Field(default=None, alias="satTaxRegime", description="Código del régimen fiscal expandido.")
63
+ sat_cfdi_use_id: Optional[Literal["G01", "G02", "G03", "I01", "I02", "I03", "I04", "I05", "I06", "I07", "I08", "D01", "D02", "D03", "D04", "D05", "D06", "D07", "D08", "D09", "D10", "S01", "CP01", "CN01"]] = Field(default=None, alias="satCfdiUseId", description="Código de uso del CFDI.")
64
+ sat_cfdi_use: Optional[CatalogDto] = Field(default=None, alias="cfdiUse", description="Código de uso del CFDI expandido.")
65
+ user_type_id: Optional[Literal["T","C", "U"]] = Field(default=None, alias="userTypeId", description="Tipo de persona.")
66
+ user_type: Optional[CatalogDto] = Field(default=None, alias="userType", description="Tipo de persona expandido.")
67
+ tin: Optional[str] = Field(default=None, alias="tin", description="RFC del emisor (Tax Identification Number).")
68
+ zip_code: Optional[str] = Field(default=None, alias="zipCode", description="Código postal del emisor.")
69
+ base64_photo: Optional[str] = Field(default=None, alias="base64Photo", description="Foto de perfil en formato base64.")
70
+ tax_password: Optional[str] = Field(default=None, alias="taxPassword", description="Contraseña de los certificados CSD del emisor.")
71
+ available_balance: Optional[Decimal] = Field(default=None, alias="availableBalance", description="Saldo disponible en la cuenta.")
72
+ committed_balance: Optional[Decimal] = Field(default=None, alias="committedBalance", description="Saldo en tránsito.")
73
+ tenant_id: Optional[str] = Field(default=None, alias="tenantId", description="ID del tenant al que pertenece el emisor.")
74
+ tenant: Optional[CatalogDto] = Field(default=None, alias="tenant", description="Tenant expandido.")
75
+
76
+ model_config = ConfigDict(
77
+ populate_by_name=True,
78
+ json_encoders={Decimal: str}
79
+ )
80
+
81
+
82
+ class TaxFile(BaseDto):
83
+ """Modelo TaxFile que representa un componente de un par CSD: certificado (.cer) o llave privada (.key)."""
84
+
85
+ person_id: Optional[str] = Field(default=None, alias="personId", description="Id de la persona propietaria del certificado.")
86
+ tin: Optional[str] = Field(default=None, alias="tin", description="RFC del propietario del certificado. Debe coincidir con el RFC del certificado.")
87
+ base64_file: Optional[str] = Field(default=None, alias="base64File", description="Archivo certificado o llave privada en formato base64.")
88
+ file_type: Literal[0, 1] = Field(default=None, alias="fileType", description="Tipo de archivo: 0 para certificado, 1 para llave privada.")
89
+ password: Optional[str] = Field(default=None, alias="password", description="Contraseña de la llave privada.")
90
+ valid_from: Optional[datetime] = Field(default=None, alias="validFrom", description="Fecha de inicio de vigencia del certificado o llave privada.")
91
+ valid_to: Optional[datetime] = Field(default=None, alias="validTo", description="Fecha de fin de vigencia del certificado o llave privada.")
92
+ sequence: Optional[int] = Field(default=None, alias="sequence", description="Numero de secuencia que identifica el par entre certificado y llave privada.")
93
+
94
+ model_config = ConfigDict(
95
+ populate_by_name=True
96
+ )
97
+
98
+
99
+ # invoices models
100
+
101
+
102
+ class TaxCredential(BaseDto):
103
+ """Modelo para los sellos del emisor (archivos .cer y .key)."""
104
+ base64_file: str = Field(..., alias="base64File", description="Archivo en formato base64.")
105
+ file_type: Literal[0, 1] = Field(..., alias="fileType", description="Tipo de archivo: 0 para certificado, 1 para llave privada.")
106
+ password: str = Field(..., alias="password", description="Contraseña del archivo .key independientemente de si es un archivo .cer o .key.")
107
+
108
+ class InvoiceIssuer(BaseDto):
109
+ """Modelo para el emisor de la factura."""
110
+ id: Optional[str] = Field(default=None, alias="id", description="ID de la persona (emisora) en fiscalapi.")
111
+ tin: Optional[str] = Field(default=None, alias="tin", description="RFC del emisor (Tax Identification Number).")
112
+ legal_name: Optional[str] = Field(default=None, alias="legalName", description="Razón social del emisor sin regimen de capital.")
113
+ tax_regime_code: Optional[str] = Field(default=None, alias="taxRegimeCode", description="Código del régimen fiscal del emisor.")
114
+ tax_credentials: Optional[List[TaxCredential]] = Field(default=None, alias="taxCredentials", description="Sellos del emisor (archivos .cer y .key).")
115
+
116
+ class InvoiceRecipient(BaseDto):
117
+ """Modelo para el receptor de la factura."""
118
+ id: Optional[str] = Field(default=None, alias="id", description="ID de la persona (receptora) en fiscalapi.")
119
+ tin: Optional[str] = Field(default=None, alias="tin", description="RFC del receptor (Tax Identification Number).")
120
+ legal_name: Optional[str] = Field(default=None, alias="legalName", description="Razón social del receptor sin regimen de capital.")
121
+ tax_regime_code: Optional[str] = Field(default=None, alias="taxRegimeCode", description="Código del régimen fiscal del receptor.")
122
+ cfdi_use_code: Optional[str] = Field(default=None, alias="cfdiUseCode", description="Código del uso CFDI.")
123
+ zip_code: Optional[str] = Field(default=None, alias="zipCode", description="Código postal del receptor. Debe coincidir con el código postal de su constancia de residencia fiscal.")
124
+ email: Optional[str] = Field(default=None, description="Correo electrónico del receptor.")
125
+
126
+ class ItemTax(BaseDto):
127
+ """Modelo para los impuestos aplicables a un producto o servicio."""
128
+ tax_code: str = Field(..., alias="taxCode", description="Código del impuesto.")
129
+ tax_type_code: str = Field(..., alias="taxTypeCode", description="Tipo de factor.")
130
+ tax_rate: Decimal = Field(..., alias="taxRate", description="Tasa del impuesto.")
131
+ tax_flag_code: Optional[Literal["T", "R"]] = Field(default=None, alias="taxFlagCode", description="Código que indica la naturaleza del impuesto. (T)raslado o (R)etención.")
132
+
133
+ model_config = ConfigDict(
134
+ populate_by_name=True,
135
+ json_encoders={Decimal: str}
136
+ )
137
+
138
+
139
+ class InvoiceItem(BaseDto):
140
+ """Modelo para los conceptos de la factura (productos o servicios)."""
141
+ id: Optional[str] = Field(default=None, alias="id", description="ID del producto en fiscalapi.")
142
+ item_code: Optional[str] = Field(default=None, alias="itemCode", description="Código SAT del producto o servicio.")
143
+ quantity: Decimal = Field(..., alias="quantity", description="Cantidad del producto o servicio.")
144
+ discount: Optional[Decimal] = Field(default=None, alias="discount", description="Cantidad monetaria del descuento aplicado.")
145
+ unit_of_measurement_code: Optional[str] = Field(default=None, alias="unitOfMeasurementCode", description="Código SAT de la unidad de medida.")
146
+ description: Optional[str] = Field(default=None,alias="description", description="Descripción del producto o servicio.")
147
+ unit_price: Optional[Decimal] = Field(default=None, alias="unitPrice", description="Precio unitario del producto o servicio.")
148
+ tax_object_code: Optional[str] = Field(default=None, alias="taxObjectCode", description="Código SAT de obligaciones de impuesto.")
149
+ item_sku: Optional[str] = Field(default=None, alias="itemSku", description="SKU o clave del sistema externo.")
150
+ item_taxes: Optional[List[ItemTax]] = Field(default=None, alias="itemTaxes", description="Impuestos aplicables al producto o servicio.")
151
+
152
+ model_config = ConfigDict(
153
+ populate_by_name=True,
154
+ json_encoders={Decimal: str}
155
+ )
156
+
157
+ class GlobalInformation(BaseDto):
158
+ """Modelo para la información global de la factura global."""
159
+ periodicity_code: str = Field(..., alias="periodicityCode", description="Código SAT de la periodicidad de la factura global.")
160
+ month_code: str = Field(..., alias="monthCode", description="Código SAT del mes de la factura global.")
161
+ year: int = Field(..., description="Año de la factura global a 4 dígitos.")
162
+
163
+ class RelatedInvoice(BaseDto):
164
+ """Modelo para representar la relacion entre la factura actual y otras facturas previas."""
165
+ relationship_type_code: str = Field(..., alias="relationshipTypeCode", description="Código de la relación de la factura relacionada.")
166
+ uuid: str = Field(..., description="UUID de la factura relacionada.")
167
+
168
+ class PaidInvoiceTax(BaseDto):
169
+ """Modelo para los impuestos aplicables a la factura pagada."""
170
+ tax_code: str = Field(..., alias="taxCode", description="Código del impuesto.")
171
+ tax_type_code: str = Field(..., alias="taxTypeCode", description="Tipo de factor.")
172
+ tax_rate: Decimal = Field(..., alias="taxRate", description="Tasa del impuesto.")
173
+ tax_flag_code: Optional[Literal["T", "R"]] = Field(default=None, alias="taxFlagCode", description="Código que indica la naturaleza del impuesto. (T)raslado o (R)etención.")
174
+
175
+ model_config = ConfigDict(
176
+ populate_by_name=True,
177
+ json_encoders={Decimal: str}
178
+ )
179
+
180
+ class PaidInvoice(BaseDto):
181
+ """Modelo para las facturas pagadas con el pago recibido."""
182
+ uuid: str = Field(..., alias="uuid", description="UUID de la factura pagada.")
183
+ series: str = Field(..., alias="series", description="Serie de la factura pagada.")
184
+
185
+ partiality_number: int = Field(..., alias="partialityNumber", description="Número de parcialidad.")
186
+ sub_total: Decimal = Field(..., alias="subTotal", description="Subtotal de la factura pagada.")
187
+ previous_balance: Decimal = Field(..., alias="previousBalance", description="Saldo anterior de la factura pagada.")
188
+ payment_amount: Decimal = Field(..., alias="paymentAmount", description="Monto pagado de la factura.")
189
+ remaining_balance: Decimal = Field(..., alias="remainingBalance", description="Saldo restante de la factura pagada.")
190
+
191
+ number: str = Field(..., alias="number", description="Folio de la factura pagada.")
192
+ currency_code: str = Field(default="MXN", alias="currencyCode", description="Código de la moneda utilizada en la factura pagada.")
193
+ tax_object_code: str = Field(..., alias="taxObjectCode", description="Código de obligaciones de impuesto.")
194
+ equivalence: Optional[Decimal] = Field(default=1, description="Equivalencia de la moneda. Este campo es obligatorio cuando la moneda del documento relacionado (PaidInvoice.CurrencyCode) difiere de la moneda en que se realiza el pago ( InvoicePayment.CurrencyCode).")
195
+ paid_invoice_taxes: List[PaidInvoiceTax] = Field(..., alias="paidInvoiceTaxes", description="Impuestos aplicables a la factura pagada.")
196
+
197
+ model_config = ConfigDict(
198
+ populate_by_name=True,
199
+ json_encoders={Decimal: str}
200
+ )
201
+
202
+
203
+
204
+ class InvoicePayment(BaseDto):
205
+ """Modelo para los pagos recibidos para liquidar la factura."""
206
+ payment_date: str = Field(..., alias="paymentDate", description="Fecha de pago.")
207
+ payment_form_code: str = Field(..., alias="paymentFormCode", description="Código de la forma de pago.")
208
+
209
+ currency_code: Literal ["MXN", "USD", "EUR"] = Field(default="MXN", alias="currencyCode", description="Código de la moneda utilizada en el pago.")
210
+ exchange_rate: Optional[Decimal] = Field(default=1, alias="exchangeRate", description="Tipo de cambio FIX conforme a la moneda registrada en la factura. Si la moneda es MXN, el tipo de cambio debe ser 1..")
211
+ amount: Decimal = Field(..., description="Monto del pago.")
212
+ source_bank_tin: str = Field(..., alias="sourceBankTin", description="RFC del banco origen. (Rfc del banco emisor del pago)")
213
+ source_bank_account: str = Field(..., alias="sourceBankAccount", description="Cuenta bancaria origen. (Cuenta bancaria del banco emisor del pago)")
214
+ target_bank_tin: str = Field(..., alias="targetBankTin", description="RFC del banco destino. (Rfc del banco receptor del pago)")
215
+ target_bank_account: str = Field(..., alias="targetBankAccount", description="Cuenta bancaria destino (Cuenta bancaria del banco receptor del pago)")
216
+ paid_invoices: List[PaidInvoice] = Field(..., alias="paidInvoices", description="Facturas pagadas con el pago recibido.")
217
+
218
+ model_config = ConfigDict(
219
+ populate_by_name=True,
220
+ json_encoders={Decimal: str}
221
+ )
222
+
223
+
224
+
225
+ class InvoiceResponse(BaseDto):
226
+ """Modelo para la respuesta del SAT después del timbrado de la factura."""
227
+ id: Optional[str] = Field(default=None, description="ID de la respuesta.")
228
+ invoice_id: Optional[str] = Field(default=None, alias="invoiceId", description="ID de la factura a la que pertenece la respuesta.")
229
+ invoice_uuid: Optional[str] = Field(default=None, alias="invoiceUuid", description="Folio Fiscal (UUID) proporcionado por el SAT tras el timbrado de la factura.")
230
+ invoice_certificate_number: Optional[str] = Field(default=None, alias="invoiceCertificateNumber", description="Número de certificado del emisor.")
231
+ invoice_base64_sello: Optional[str] = Field(default=None, alias="invoiceBase64Sello", description="Sello digital del CFDI en formato Base64.")
232
+ invoice_signature_date: Optional[datetime] = Field(default=None, alias="invoiceSignatureDate", description="Fecha y hora de la firma electrónica del CFDI por parte del emisor.")
233
+ invoice_base64_qr_code: Optional[str] = Field(default=None, alias="invoiceBase64QrCode", description="Imagen del código QR en formato Base64.")
234
+ invoice_base64: Optional[str] = Field(default=None, alias="invoiceBase64", description="XML de la factura en formato Base64.")
235
+ sat_base64_sello: Optional[str] = Field(default=None, alias="satBase64Sello", description="Sello digital del SAT en formato Base64.")
236
+ sat_base64_original_string: Optional[str] = Field(default=None, alias="satBase64OriginalString", description="Cadena original de la factura codificada en Base64.")
237
+ sat_certificate_number: Optional[str] = Field(default=None, alias="satCertificateNumber", description="Número de certificado del SAT.")
238
+
239
+ model_config = ConfigDict(
240
+ populate_by_name=True,
241
+ json_encoders={datetime: lambda v: v.isoformat()}
242
+ )
243
+
244
+
245
+ class Invoice(BaseDto):
246
+ """Modelo para la factura."""
247
+ version_code: Optional[str] = Field(default="4.0", alias="versionCode", description="Código de la versión de la factura.")
248
+ series: str = Field(..., description="Número de serie que utiliza el contribuyente para control interno.")
249
+ date: datetime = Field(..., description="Fecha y hora de expedición del comprobante fiscal.")
250
+ payment_form_code: Optional[str] = Field(default=None, alias="paymentFormCode", description="Código de la forma de pago.")
251
+ currency_code: Literal["MXN", "USD", "EUR", "XXX"] = Field(default="MXN", alias="currencyCode", description="Código de la moneda utilizada.")
252
+ type_code: Optional[Literal["I", "E", "T", "N", "P"]] = Field(default="I", alias="typeCode", description="Código de tipo de factura.")
253
+ expedition_zip_code: str = Field(..., alias="expeditionZipCode", description="Código postal del emisor.")
254
+ export_code: Optional[Literal["01", "02", "03", "04"]] = Field(default="01", alias="exportCode", description="Código que identifica si la factura ampara una operación de exportación.")
255
+ payment_method_code: Optional[Literal["PUE", "PPD"]] = Field(default=None, alias="paymentMethodCode", description="Código de método para la factura de pago.")
256
+ exchange_rate: Optional[Decimal] = Field(default=1, alias="exchangeRate", description="Tipo de cambio FIX.")
257
+ issuer: Optional[InvoiceIssuer] = Field(..., description="El emisor de la factura.")
258
+ recipient: Optional[InvoiceRecipient] = Field(..., description="El receptor de la factura.")
259
+ items: Optional[List[InvoiceItem]] = Field(default=[], description="Conceptos de la factura (productos o servicios).")
260
+ global_information: Optional[GlobalInformation] = Field(default=None, alias="globalInformation", description="Información global de la factura.")
261
+ related_invoices: Optional[List[RelatedInvoice]] = Field(default=None, alias="relatedInvoices", description="Facturas relacionadas.")
262
+ payments: Optional[List[InvoicePayment]] = Field(default=None, description="Pago o pagos recibidos para liquidar la factura cuando la factura es un complemento de pago.")
263
+ responses: Optional[List[InvoiceResponse]] = Field(default=None, description="Respuestas del SAT. Contiene la información de timbrado de la factura.")
264
+
265
+
266
+ model_config = ConfigDict(
267
+ populate_by_name=True,
268
+ json_encoders={Decimal: str}
269
+ )
270
+
271
+
272
+
273
+ class CancelInvoiceRequest(BaseDto):
274
+ """Modelo de cancelación de factura."""
275
+ id: Optional[str] = Field(default=None, alias="id", description="ID de la factura a cancelar. Obligatorio cuando se cancela por referencias.")
276
+ invoice_uuid: Optional[str] = Field(default=None, alias="invoiceUuid", description="UUID de la factura a cancelar. Obligatorio cuando se cancela por valores.")
277
+ tin: Optional[str] = Field(default=None, alias="tin", description="RFC del emisor de la factura. Obligatorio cuando se cancela por valores.")
278
+ cancellation_reason_code: Literal["01", "02", "03", "04"] = Field(..., alias="cancellationReasonCode", description="Código del motivo de cancelación.")
279
+ replacement_uuid: Optional[str] = Field(default=None, alias="replacementUuid", description="UUID de la factura de reemplazo. Obligatorio si el motivo de cancelación es '01'.")
280
+ tax_credentials: Optional[List[TaxCredential]] = Field(default=None, alias="taxCredentials", description="Sellos del emisor. Obligatorio cuando se cancela por valores.")
281
+
282
+ class Config:
283
+ populate_by_name = True
284
+
285
+ class CancelInvoiceResponse(BaseDto):
286
+ """Modelo de respuesta para la cancelación de factura."""
287
+ base64_cancellation_acknowledgement: str = Field(default=None, alias="base64CancellationAcknowledgement", description="Acuse de cancelación en formato base64. Contiene el XML del acuse de cancelación del SAT codificado en base64.")
288
+ invoice_uuids: Optional[Dict[str, str]] = Field(default=None, alias="invoiceUuids", description="Diccionario de UUIDs de facturas con su respectivo código de estatus de cancelación. La llave es el UUID de la factura y el valor es el código de estatus.")
289
+
290
+ class Config:
291
+ populate_by_name = True
292
+
293
+
294
+ class CreatePdfRequest(BaseDto):
295
+ """Modelo para la generación de PDF de una factura."""
296
+ invoice_id: str = Field(..., alias="invoiceId", description="ID de la factura para la cual se generará el PDF.")
297
+ band_color: Optional[str] = Field(default=None, alias="bandColor", description="Color de la banda del PDF en formato hexadecimal. Ejemplo: '#FFA500'.")
298
+ font_color: Optional[str] = Field(default=None, alias="fontColor", description="Color de la fuente del texto sobre la banda en formato hexadecimal. Ejemplo: '#FFFFFF'.")
299
+ base64_logo: Optional[str] = Field(default=None, alias="base64Logo", description="Logotipo en formato base64 que se mostrará en el PDF.")
300
+
301
+ class Config:
302
+ populate_by_name = True
303
+
304
+ class FileResponse(BaseDto):
305
+ """Modelo de respuesta para la generación de PDF o recuperación de XML."""
306
+ base64_file: Optional[str] = Field(default=None, alias="base64File", description="Contenido del archivo en formato base64.")
307
+ file_name: Optional[str] = Field(default=None, alias="fileName", description="Nombre del archivo generado.")
308
+ file_extension: Optional[str] = Field(default=None, alias="fileExtension", description="Extensión del archivo. Ejemplo: '.pdf'.")
309
+
310
+ class Config:
311
+ populate_by_name = True
312
+
313
+
314
+ class SendInvoiceRequest(BaseDto):
315
+ """Modelo para el envío de facturas por correo electrónico."""
316
+ invoice_id: str = Field(..., alias="invoiceId", description="ID de la factura para la cual se enviará el PDF.")
317
+ to_email: str = Field(..., alias="toEmail", description="Correo electrónico del destinatario.")
318
+ band_color: Optional[str] = Field(default=None, alias="bandColor", description="Color de la banda del PDF en formato hexadecimal. Ejemplo: '#FFA500'.")
319
+ font_color: Optional[str] = Field(default=None, alias="fontColor", description="Color de la fuente del texto sobre la banda en formato hexadecimal. Ejemplo: '#FFFFFF'.")
320
+ base64_logo: Optional[str] = Field(default=None, alias="base64Logo", description="Logotipo en formato base64 que se mostrará en el PDF.")
321
+
322
+ class Config:
323
+ populate_by_name = True
File without changes
@@ -0,0 +1,218 @@
1
+ import urllib3
2
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
3
+ from typing import Type, TypeVar, get_args, get_origin
4
+ import certifi
5
+ from pydantic import BaseModel
6
+ import requests
7
+ from fiscalapi.models.common_models import ApiResponse, FiscalApiSettings, ValidationFailure
8
+
9
+ T = TypeVar('T')
10
+
11
+
12
+ class BaseService:
13
+ def __init__(self, settings: FiscalApiSettings):
14
+ self.settings = settings
15
+ self.api_version = settings.api_version
16
+ self.base_url = settings.api_url
17
+ self.api_key = settings.api_key
18
+
19
+ def _get_headers(self) -> dict:
20
+ return {
21
+ "Content-Type": "application/json",
22
+ "X-TENANT-KEY": self.settings.tenant,
23
+ "X-API-KEY": self.settings.api_key,
24
+ "X-TIME-ZONE": self.settings.time_zone,
25
+ }
26
+
27
+ def _request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
28
+ url = f"{self.base_url}/api/{self.api_version}/{endpoint}"
29
+ headers = self._get_headers()
30
+
31
+ if "headers" in kwargs:
32
+ headers.update(kwargs.pop("headers"))
33
+
34
+ # Disable certificate validation (for development only!)
35
+ # kwargs.setdefault("verify", False)
36
+
37
+
38
+ # print payload request
39
+ print("***Payload Request:", kwargs.get("json"))
40
+
41
+ # print line breaks
42
+ print("\n\n")
43
+
44
+
45
+ # *** DEV ONLY: Disable SSL verification for localhost ***
46
+ if "localhost" in url or "127.0.0.1" in url:
47
+ kwargs["verify"] = False
48
+ else:
49
+ # Use the default cert store
50
+ kwargs["verify"] = certifi.where()
51
+
52
+
53
+ # send request
54
+ response = requests.request(method=method, url=url, headers=headers, **kwargs)
55
+
56
+ # print payload response
57
+ print("***Payload Response:", response.text)
58
+
59
+ # print line breaks
60
+ print("\n\n")
61
+
62
+ return response
63
+
64
+ # def _process_response(self, response: requests.Response, response_model: Type[T]) -> ApiResponse[T]:
65
+ # status_code = response.status_code
66
+ # raw_content = response.text
67
+
68
+ # try:
69
+ # response_data = response.json()
70
+ # except ValueError:
71
+ # return ApiResponse[T](
72
+ # succeeded=False,
73
+ # http_status_code=status_code,
74
+ # message="Error processing server response",
75
+ # details=raw_content,
76
+ # data=None
77
+ # )
78
+
79
+ # if 200 <= status_code < 300:
80
+
81
+ # if issubclass(response_model, BaseModel) and isinstance(response_data["data"], dict):
82
+ # response_data["data"] = response_model.model_validate(response_data["data"])
83
+
84
+ # return ApiResponse[T].model_validate(response_data)
85
+
86
+ # try:
87
+ # generic_error = ApiResponse[object].model_validate(response_data)
88
+ # except Exception:
89
+ # return ApiResponse[T](
90
+ # succeeded=False,
91
+ # http_status_code=status_code,
92
+ # message="Error processing server error response",
93
+ # details=raw_content,
94
+ # data=None
95
+ # )
96
+
97
+ # if status_code == 400 and isinstance(response_data.get("data"), list):
98
+ # try:
99
+ # failures = [ValidationFailure.model_validate(item) for item in response_data["data"]]
100
+ # if failures:
101
+ # details_str = "; ".join(f"{f.propertyName}: {f.errorMessage}" for f in failures)
102
+ # return ApiResponse[T](
103
+ # succeeded=False,
104
+ # http_status_code=400,
105
+ # message=generic_error.message,
106
+ # details=details_str,
107
+ # data=None
108
+ # )
109
+ # except Exception:
110
+ # pass
111
+
112
+ # return ApiResponse[T](
113
+ # succeeded=False,
114
+ # http_status_code=status_code,
115
+ # message=generic_error.message or f"HTTP Error {status_code}",
116
+ # details=generic_error.details or raw_content,
117
+ # data=None
118
+ # )
119
+
120
+
121
+ def _process_response(self, response: requests.Response, response_model: Type[T]) -> ApiResponse[T]:
122
+ status_code = response.status_code
123
+ raw_content = response.text
124
+
125
+ try:
126
+ response_data = response.json()
127
+ except ValueError:
128
+ return ApiResponse[T](
129
+ succeeded=False,
130
+ http_status_code=status_code,
131
+ message="Error processing server response",
132
+ details=raw_content,
133
+ data=None
134
+ )
135
+
136
+ if 200 <= status_code < 300:
137
+ # -- Manejo de data con modelos pydantic o lista de modelos pydantic
138
+ origin = get_origin(response_model)
139
+ if origin == list:
140
+ # Significa que es algo como list[TaxFile]
141
+ (model_type,) = get_args(response_model)
142
+ # Validar cada elemento de la lista si data es una lista
143
+ if issubclass(model_type, BaseModel) and isinstance(response_data["data"], list):
144
+ response_data["data"] = [
145
+ model_type.model_validate(item)
146
+ for item in response_data["data"]
147
+ ]
148
+ else:
149
+ # Manejo de caso donde response_model es un modelo Pydantic "simple"
150
+ if (
151
+ isinstance(response_model, type)
152
+ and issubclass(response_model, BaseModel)
153
+ and isinstance(response_data["data"], dict)
154
+ ):
155
+ response_data["data"] = response_model.model_validate(response_data["data"])
156
+
157
+ # Finalmente se parsea la respuesta como ApiResponse[T]
158
+ return ApiResponse[T].model_validate(response_data)
159
+
160
+ # -- Manejo de respuestas de error --
161
+ try:
162
+ generic_error = ApiResponse[object].model_validate(response_data)
163
+ except Exception:
164
+ return ApiResponse[T](
165
+ succeeded=False,
166
+ http_status_code=status_code,
167
+ message="Error processing server error response",
168
+ details=raw_content,
169
+ data=None
170
+ )
171
+
172
+ if status_code == 400 and isinstance(response_data.get("data"), list):
173
+ try:
174
+ failures = [ValidationFailure.model_validate(item) for item in response_data["data"]]
175
+ if failures:
176
+ details_str = "; ".join(f"{f.propertyName}: {f.errorMessage}" for f in failures)
177
+ return ApiResponse[T](
178
+ succeeded=False,
179
+ http_status_code=400,
180
+ message=generic_error.message,
181
+ details=details_str,
182
+ data=None
183
+ )
184
+ except Exception:
185
+ pass
186
+
187
+ return ApiResponse[T](
188
+ succeeded=False,
189
+ http_status_code=status_code,
190
+ message=generic_error.message or f"HTTP Error {status_code}",
191
+ details=generic_error.details or raw_content,
192
+ data=None
193
+ )
194
+
195
+
196
+
197
+ # def send_request(self, method: str, endpoint: str, response_model: Type[T], **kwargs) -> ApiResponse[T]:
198
+ # payload = kwargs.pop("payload", None)
199
+ # if payload is not None and isinstance(payload, BaseModel):
200
+ # # Excluir propiedades con valor None
201
+ # kwargs["json"] = payload.model_dump(mode="json", by_alias=True, exclude_none=True)
202
+
203
+ # response = self._request(method, endpoint, **kwargs)
204
+ # return self._process_response(response, response_model)
205
+
206
+ def send_request(self, method: str, endpoint: str, response_model: Type[T], details: bool = False, **kwargs) -> ApiResponse[T]:
207
+ if details:
208
+ endpoint += "?details=true"
209
+
210
+ payload = kwargs.pop("payload", None)
211
+ if payload is not None and isinstance(payload, BaseModel):
212
+ # Excluir propiedades con valor None
213
+ kwargs["json"] = payload.model_dump(mode="json", by_alias=True, exclude_none=True)
214
+
215
+ response = self._request(method, endpoint, **kwargs)
216
+ return self._process_response(response, response_model)
217
+
218
+
@@ -0,0 +1,20 @@
1
+ from fiscalapi.models.common_models import ApiResponse, CatalogDto, PagedList
2
+ from fiscalapi.services.base_service import BaseService
3
+
4
+
5
+ class CatalogService(BaseService):
6
+
7
+ # get list of catalogs available in fiscalapi
8
+ def get_list(self) -> ApiResponse[list[str]]:
9
+ endpoint = "catalogs"
10
+ return self.send_request("GET", endpoint, list[str])
11
+
12
+ # get record by catalog name and its id
13
+ def get_record_by_id(self, catalog_name: str, id: str) -> ApiResponse[CatalogDto]:
14
+ endpoint = f"catalogs/{catalog_name}/key/{id}"
15
+ return self.send_request("GET", endpoint, CatalogDto)
16
+
17
+ # Search whithin a catalog by catalog name and search text
18
+ def search_catalog(self, catalog_name: str, search_text: str, page_number: int = 1, page_size: int = 50) -> ApiResponse[PagedList[CatalogDto]]:
19
+ endpoint = f"catalogs/{catalog_name}/{search_text}?pageNumber={page_number}&pageSize={page_size}"
20
+ return self.send_request("GET", endpoint, PagedList[CatalogDto])
@@ -0,0 +1,18 @@
1
+ from fiscalapi.models.common_models import FiscalApiSettings
2
+ from fiscalapi.services.catalog_service import CatalogService
3
+ from fiscalapi.services.invoice_service import InvoiceService
4
+ from fiscalapi.services.people_service import PeopleService
5
+ from fiscalapi.services.product_service import ProductService
6
+ from fiscalapi.services.tax_file_servive import TaxFileService
7
+
8
+
9
+
10
+ class FiscalApiClient:
11
+
12
+ def __init__(self, settings: FiscalApiSettings):
13
+ self.products = ProductService(settings)
14
+ self.people = PeopleService(settings)
15
+ self.tax_files = TaxFileService(settings)
16
+ self.catalogs = CatalogService(settings)
17
+ self.invoices = InvoiceService(settings)
18
+ self.settings = settings
@@ -0,0 +1,66 @@
1
+ from fiscalapi.models.common_models import ApiResponse, PagedList
2
+ from fiscalapi.models.fiscalapi_models import CancelInvoiceRequest, CancelInvoiceResponse, CreatePdfRequest, FileResponse, Invoice, SendInvoiceRequest
3
+ from fiscalapi.services.base_service import BaseService
4
+
5
+
6
+ class InvoiceService(BaseService):
7
+
8
+ # get paged list of invoices
9
+ def get_list(self, page_number: int, page_size: int) -> ApiResponse[PagedList[Invoice]]:
10
+ endpoint = f"invoices?pageNumber={page_number}&pageSize={page_size}"
11
+ return self.send_request("GET", endpoint, PagedList[Invoice])
12
+
13
+ # get invoice by id
14
+ def get_by_id(self, invoice_id: int, details: bool = False) -> ApiResponse[Invoice]:
15
+ endpoint = f"invoices/{invoice_id}"
16
+ return self.send_request("GET", endpoint, Invoice, details=details)
17
+
18
+
19
+ # helper method to determine the endpoint based on invoice type
20
+ def _get_endpoint_by_type(self, type_code: str) -> str:
21
+ if type_code == "I":
22
+ return "invoices/income"
23
+ elif type_code == "E":
24
+ return "invoices/credit-note"
25
+ elif type_code == "P":
26
+ return "invoices/payment"
27
+ else:
28
+ raise ValueError(f"Unsupported invoice type: {type_code}")
29
+
30
+ # create invoice
31
+ def create(self, invoice: Invoice) -> ApiResponse[Invoice]:
32
+ if invoice is None:
33
+ raise ValueError("request_model cannot be null")
34
+
35
+ endpoint = self._get_endpoint_by_type(invoice.type_code)
36
+ return self.send_request("POST", endpoint, Invoice, payload=invoice)
37
+
38
+
39
+ # cancel invoice
40
+ def cancel(self, cancel_invoice_request: CancelInvoiceRequest) -> ApiResponse[CancelInvoiceResponse]:
41
+ if cancel_invoice_request is None:
42
+ raise ValueError("request_model cannot be null")
43
+
44
+ endpoint = "invoices"
45
+ return self.send_request("DELETE", endpoint, CancelInvoiceResponse, payload=cancel_invoice_request)
46
+
47
+ # create invoice's pdf
48
+ def get_pdf(self, create_pdf_request:CreatePdfRequest) -> ApiResponse[FileResponse]:
49
+ if create_pdf_request is None:
50
+ raise ValueError("request_model cannot be null")
51
+
52
+ endpoint = "invoices/pdf"
53
+ return self.send_request("POST", endpoint, FileResponse, payload=create_pdf_request)
54
+
55
+
56
+ # send invoice by email
57
+
58
+ def send(self, send_invoice_request : SendInvoiceRequest):
59
+ if not send_invoice_request:
60
+ raise ValueError("Invalid request")
61
+
62
+ endpoint = "invoices/send"
63
+
64
+ return self.send_request("POST", endpoint, bool, payload=send_invoice_request)
65
+
66
+
@@ -0,0 +1,32 @@
1
+ from fiscalapi.models.common_models import ApiResponse, PagedList
2
+ from fiscalapi.models.fiscalapi_models import Person, Product
3
+ from fiscalapi.services.base_service import BaseService
4
+
5
+
6
+ class PeopleService(BaseService):
7
+
8
+ # get paged list of people
9
+ def get_list(self, page_number: int, page_size: int) -> ApiResponse[PagedList[Person]]:
10
+ endpoint = f"people?pageNumber={page_number}&pageSize={page_size}"
11
+ return self.send_request("GET", endpoint, PagedList[Person])
12
+
13
+ # get person by id
14
+ def get_by_id(self, person_id: int) -> ApiResponse[Person]:
15
+ endpoint = f"people/{person_id}"
16
+ return self.send_request("GET", endpoint, Person)
17
+
18
+ # create person
19
+ def create(self, person: Person) -> ApiResponse[Person]:
20
+ endpoint = "people"
21
+ return self.send_request("POST", endpoint, Person, payload=person)
22
+
23
+ # update person
24
+ def update(self, person: Person) -> ApiResponse[Person]:
25
+ endpoint = f"people/{person.id}"
26
+ return self.send_request("PUT", endpoint, Person, payload=person)
27
+
28
+ # delete person
29
+ def delete(self, person_id: str) -> ApiResponse[bool]:
30
+ endpoint = f"people/{person_id}"
31
+ return self.send_request("DELETE", endpoint, bool)
32
+
@@ -0,0 +1,31 @@
1
+ from fiscalapi.models.common_models import ApiResponse, PagedList
2
+ from fiscalapi.models.fiscalapi_models import Product
3
+ from fiscalapi.services.base_service import BaseService
4
+
5
+
6
+ class ProductService(BaseService):
7
+
8
+ # get paged list of products
9
+ def get_list(self, page_number: int, page_size: int) -> ApiResponse[PagedList[Product]]:
10
+ endpoint = f"products?pageNumber={page_number}&pageSize={page_size}"
11
+ return self.send_request("GET", endpoint, PagedList[Product])
12
+
13
+ # get product by id
14
+ def get_by_id(self, product_id: int) -> ApiResponse[Product]:
15
+ endpoint = f"products/{product_id}"
16
+ return self.send_request("GET", endpoint, Product)
17
+
18
+ # create product
19
+ def create(self, product: Product) -> ApiResponse[Product]:
20
+ endpoint = "products"
21
+ return self.send_request("POST", endpoint, Product, payload=product)
22
+
23
+ # update product
24
+ def update(self, product: Product) -> ApiResponse[Product]:
25
+ endpoint = f"products/{product.id}"
26
+ return self.send_request("PUT", endpoint, Product, payload=product)
27
+
28
+ # delete product
29
+ def delete(self, product_id: str) -> ApiResponse[bool]:
30
+ endpoint = f"products/{product_id}"
31
+ return self.send_request("DELETE", endpoint, bool)
@@ -0,0 +1,41 @@
1
+ from fiscalapi.models.common_models import ApiResponse, PagedList
2
+ from fiscalapi.models.fiscalapi_models import TaxFile
3
+ from fiscalapi.services.base_service import BaseService
4
+
5
+ class TaxFileService(BaseService):
6
+
7
+ # get paged list of tax files
8
+ def get_list(self, page_number: int, page_size: int) -> ApiResponse[PagedList[TaxFile]]:
9
+ endpoint = f"tax-files?pageNumber={page_number}&pageSize={page_size}"
10
+ return self.send_request("GET", endpoint, PagedList[TaxFile])
11
+
12
+ # get tax file by id
13
+ def get_by_id(self, tax_file_id: int) -> ApiResponse[TaxFile]:
14
+ endpoint = f"tax-files/{tax_file_id}"
15
+ return self.send_request("GET", endpoint, TaxFile)
16
+
17
+
18
+ # create tax file (upload tax file)
19
+ def create(self, tax_file: TaxFile) -> ApiResponse[TaxFile]:
20
+ endpoint = "tax-files"
21
+ return self.send_request("POST", endpoint, TaxFile, payload=tax_file)
22
+
23
+
24
+ # delete tax file
25
+ def delete(self, tax_file_id: str) -> ApiResponse[bool]:
26
+ endpoint = f"tax-files/{tax_file_id}"
27
+ return self.send_request("DELETE", endpoint, bool)
28
+
29
+ # get default tax files for a given person)
30
+ # obtiene el último par de certificados válidos y vigente de una persona. Es decir sus certificados por defecto.
31
+ def get_default_values(self, person_id: int) -> ApiResponse[list[TaxFile]]:
32
+ endpoint = f"tax-files/{person_id}/default-values"
33
+ return self.send_request("GET", endpoint, list[TaxFile])
34
+
35
+
36
+ # get default references for a given person
37
+ # obtiene el último par de ids de certificados válidos y vigente de una persona. Es decir sus certificados por defecto (solo los ids)
38
+ def get_default_references(self, person_id: int) -> ApiResponse[list[TaxFile]]:
39
+ endpoint = f"tax-files/{person_id}/default-references"
40
+ return self.send_request("GET", endpoint, list[TaxFile])
41
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: fiscalapi
3
- Version: 4.0.0
3
+ Version: 4.0.2
4
4
  Summary: Genera facturas CFDI válidas ante el SAT consumiendo el API de https://www.fiscalapi.com
5
5
  Home-page: https://www.fiscalapi.com
6
6
  Author: Fiscalapi
@@ -0,0 +1,20 @@
1
+ LICENSE.txt
2
+ README.md
3
+ setup.py
4
+ fiscalapi/__init__.py
5
+ fiscalapi.egg-info/PKG-INFO
6
+ fiscalapi.egg-info/SOURCES.txt
7
+ fiscalapi.egg-info/dependency_links.txt
8
+ fiscalapi.egg-info/requires.txt
9
+ fiscalapi.egg-info/top_level.txt
10
+ fiscalapi/models/__init__.py
11
+ fiscalapi/models/common_models.py
12
+ fiscalapi/models/fiscalapi_models.py
13
+ fiscalapi/services/__init__.py
14
+ fiscalapi/services/base_service.py
15
+ fiscalapi/services/catalog_service.py
16
+ fiscalapi/services/fiscalapi_client.py
17
+ fiscalapi/services/invoice_service.py
18
+ fiscalapi/services/people_service.py
19
+ fiscalapi/services/product_service.py
20
+ fiscalapi/services/tax_file_servive.py
@@ -0,0 +1 @@
1
+ fiscalapi
@@ -2,7 +2,7 @@
2
2
  import os
3
3
  from setuptools import setup, find_packages
4
4
 
5
- VERSION = "4.0.0"
5
+ VERSION = "4.0.2"
6
6
  # Descripción breve basada en el .csproj
7
7
  DESCRIPTION = "Genera facturas CFDI válidas ante el SAT consumiendo el API de https://www.fiscalapi.com"
8
8
 
@@ -1,8 +0,0 @@
1
- LICENSE.txt
2
- README.md
3
- setup.py
4
- fiscalapi.egg-info/PKG-INFO
5
- fiscalapi.egg-info/SOURCES.txt
6
- fiscalapi.egg-info/dependency_links.txt
7
- fiscalapi.egg-info/requires.txt
8
- fiscalapi.egg-info/top_level.txt
File without changes
File without changes
File without changes