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

1from django.db import models 

2 

3from app.facturapi import FacturapiUserClient 

4from base.models import RandomSlugModel, TimeStampedModel 

5 

6 

7class PractitionerInvoiceConfiguration(RandomSlugModel, TimeStampedModel): 

8 """ 

9 Model to store fiscal data from Practitioner used to create invoices 

10 """ 

11 

12 practitioner = models.OneToOneField( 

13 "practitioners.Practitioner", blank=True, on_delete=models.CASCADE, related_name="invoice_configuration" 

14 ) 

15 

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) 

23 

24 def upload_path(instance, filename): 

25 return f"practitioners/{instance.pk}/certificates/{filename}" 

26 

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) 

29 

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) 

34 

35 class Meta: 

36 ordering = ["practitioner", "created_at"] 

37 verbose_name = "Configuracion de Facturacion" 

38 verbose_name = "Configuraciones de Facturacion" 

39 

40 def __str__(self): 

41 return self.name 

42 

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 } 

51 

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() 

59 

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() 

65 

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) 

70 

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() 

79 

80 def delete_facturapi_organization(self): 

81 if self.facturapi_organization_id: 

82 client = FacturapiUserClient() 

83 client.Organization.delete(self.facturapi_organization_id) 

84 

85 

86class InvoiceUse(RandomSlugModel): 

87 """ 

88 Model for CFDI use 

89 """ 

90 

91 description = models.CharField(max_length=128) 

92 use_key = models.CharField(max_length=4) 

93 

94 class Meta: 

95 ordering = ["use_key"] 

96 

97 def __str__(self): 

98 return f"{self.use_key} - {self.description}" 

99 

100 

101class ProductInvoiceConfiguration(RandomSlugModel): 

102 """ 

103 Model for product invoice configurations 

104 """ 

105 

106 product = models.OneToOneField("products.Product", on_delete=models.CASCADE, related_name="invoice_configuration") 

107 

108 uses = models.ManyToManyField("invoices.InvoiceUse", blank=True) 

109 

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) 

114 

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" 

119 

120 taxability = models.CharField(max_length=2, choices=Taxability.choices, default=Taxability.TAX_DETAIL) 

121 

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 } 

132 

133 

134class Receipt(RandomSlugModel, TimeStampedModel): 

135 """ 

136 Model for Facturapi Receipt 

137 """ 

138 

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") 

141 

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() 

147 

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" 

153 

154 status = models.CharField(max_length=32, choices=StatusChoices.choices, default=StatusChoices.OPEN) 

155 

156 class Meta: 

157 ordering = ["facturapi_id"]