Coverage for apps/invoices/models.py: 71%
89 statements
« prev ^ index » next coverage.py v6.4.4, created at 2024-05-23 06:56 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2024-05-23 06:56 -0600
1from django.db import models
3from app.facturapi import FacturapiUserClient
4from base.models import RandomSlugModel, TimeStampedModel
7class PractitionerInvoiceConfiguration(RandomSlugModel, TimeStampedModel):
8 """
9 Model to store fiscal data from Practitioner used to create invoices
10 """
12 practitioner = models.OneToOneField(
13 "practitioners.Practitioner", blank=True, on_delete=models.CASCADE, related_name="invoice_configuration"
14 )
16 name = models.CharField(max_length=100)
17 legal_name = models.CharField(max_length=100)
18 tax_system = models.CharField(max_length=3)
19 zip = models.CharField(max_length=5)
20 street = models.CharField(max_length=256)
21 exterior = models.CharField(max_length=16)
22 interior = models.CharField(max_length=16, null=True, blank=True)
24 def upload_path(instance, filename):
25 return f"practitioners/{instance.pk}/certificates/{filename}"
27 cer_file = models.FileField(upload_to=upload_path, blank=True, null=True)
28 key_file = models.FileField(upload_to=upload_path, blank=True, null=True)
30 facturapi_organization_id = models.CharField(max_length=24, editable=False, null=True)
31 facturapi_secret_key = models.CharField(max_length=50, editable=False, null=True)
32 can_create_invoice = models.BooleanField(default=False)
33 is_production_ready = models.BooleanField(default=False)
35 class Meta:
36 ordering = ["practitioner", "created_at"]
37 verbose_name = "Configuracion de Facturacion"
38 verbose_name = "Configuraciones de Facturacion"
40 def __str__(self):
41 return self.name
43 @property
44 def facturapi_data(self):
45 return {
46 "name": self.name,
47 "legal_name": self.legal_name,
48 "tax_system": self.tax_system,
49 "address": {"zip": self.zip, "street": self.street, "exterior": self.exterior},
50 }
52 def set_facturapi_organization_id(self):
53 client = FacturapiUserClient()
54 if self.facturapi_organization_id:
55 return
56 facturapi_organization_id = client.Organization.create(name=self.name).get("id")
57 self.facturapi_organization_id = facturapi_organization_id
58 self.save()
60 def set_facturapi_secret_key(self):
61 client = FacturapiUserClient()
62 facturapi_secret_key = client.Organization.apikeys(self.facturapi_organization_id)
63 self.facturapi_secret_key = facturapi_secret_key
64 self.save()
66 def update_facturapi_organization_fiscal_data(self):
67 if self.facturapi_organization_id:
68 client = FacturapiUserClient()
69 client.Organization.legal(self.facturapi_organization_id, **self.facturapi_data)
71 def upload_certificates(self, password: str):
72 if self.facturapi_organization_id:
73 client = FacturapiUserClient()
74 is_production_ready = client.Organization.certificate(
75 self.facturapi_organization_id, self.cer_file.path, self.key_file.path, password
76 ).get("is_production_ready", False)
77 self.is_production_ready = is_production_ready
78 self.save()
80 def delete_facturapi_organization(self):
81 if self.facturapi_organization_id:
82 client = FacturapiUserClient()
83 client.Organization.delete(self.facturapi_organization_id)
86class InvoiceUse(RandomSlugModel):
87 """
88 Model for CFDI use
89 """
91 description = models.CharField(max_length=128)
92 use_key = models.CharField(max_length=4)
94 class Meta:
95 ordering = ["use_key"]
97 def __str__(self):
98 return f"{self.use_key} - {self.description}"
101class ProductInvoiceConfiguration(RandomSlugModel):
102 """
103 Model for product invoice configurations
104 """
106 product = models.OneToOneField("products.Product", on_delete=models.CASCADE, related_name="invoice_configuration")
108 uses = models.ManyToManyField("invoices.InvoiceUse", blank=True)
110 description = models.CharField(max_length=128)
111 product_key = models.CharField(max_length=8)
112 unit_key = models.CharField(max_length=8)
113 tax_included = models.BooleanField(default=True)
115 class Taxability(models.TextChoices):
116 NO_TAX = "01", "No tax"
117 TAX_DETAIL = "02", "Tax, detail"
118 TAX_NO_DETAIL = "03", "Tax, no detail"
120 taxability = models.CharField(max_length=2, choices=Taxability.choices, default=Taxability.TAX_DETAIL)
122 @property
123 def facturapi_data(self):
124 return {
125 "description": self.description,
126 "product_key": self.product_key,
127 "tax_included": self.tax_included,
128 "taxability": self.taxability,
129 "taxes": None if self.taxability == self.Taxability.NO_TAX else [],
130 "unit_key": self.unit_key,
131 }
134class Receipt(RandomSlugModel, TimeStampedModel):
135 """
136 Model for Facturapi Receipt
137 """
139 practitioner = models.ForeignKey("practitioners.Practitioner", on_delete=models.PROTECT, related_name="receipts")
140 patient = models.ForeignKey("patients.Patient", on_delete=models.PROTECT, related_name="receipts")
142 facturapi_id = models.CharField(max_length=32)
143 live_mode = models.BooleanField(default=False)
144 date = models.DateField()
145 expires_at = models.DateField()
146 self_invoice_url = models.URLField()
148 class StatusChoices(models.TextChoices):
149 OPEN = "open", "Pendiente"
150 CANCELED = "canceled", "Cancelado"
151 INVOICED_TO_CUSTOMER = "invoiced_to_customer", "Facturado al Cliente"
152 INVOICED_GLOBALLY = "invoiced_globally", "Facturado Global"
154 status = models.CharField(max_length=32, choices=StatusChoices.choices, default=StatusChoices.OPEN)
156 class Meta:
157 ordering = ["facturapi_id"]