Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ test2.csr

# Created by tests:
/media/

# venv
.venv/
5 changes: 5 additions & 0 deletions django_afip/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ class PointOfSalesAdmin(admin.ModelAdmin):
)


@admin.register(models.ClientVatCondition)
class ClientVatConditionAdmin(admin.ModelAdmin):
search_fields = ("code", "description", "cmp_clase")
list_display = ("code", "description", "cmp_clase")

@admin.register(models.CurrencyType)
class CurrencyTypeAdmin(admin.ModelAdmin):
search_fields = (
Expand Down
65 changes: 65 additions & 0 deletions django_afip/fixtures/clientvatcondition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
- model: afip.clientvatcondition
fields:
code: 16
description: "Monotributo Trabajador Independiente Promovido"
cmp_clase: "A/M/C"

- model: afip.clientvatcondition
fields:
code: 15
description: "IVA No Alcanzado"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 13
description: "Monotributista Social"
cmp_clase: "A/M/C"

- model: afip.clientvatcondition
fields:
code: 10
description: "IVA Liberado – Ley N° 19.640"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 9
description: "Cliente del Exterior"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 8
description: "Proveedor del Exterior"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 7
description: "Sujeto No Categorizado"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 6
description: "Responsable Monotributo"
cmp_clase: "A/M/C"

- model: afip.clientvatcondition
fields:
code: 5
description: "Consumidor Final"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 4
description: "IVA Sujeto Exento"
cmp_clase: "B/C"

- model: afip.clientvatcondition
fields:
code: 1
description: "IVA Responsable Inscripto"
cmp_clase: "A/M/C"
42 changes: 42 additions & 0 deletions django_afip/management/commands/load_client_vat_conditions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from __future__ import annotations

from django.core.management.base import BaseCommand
from django.utils.translation import gettext as _

from django_afip import clients, models, serializers


class Command(BaseCommand):
help = _(
"Retrieves all the ClientVatConditions from the AFIP server and updates it in the DB."
)
requires_migrations_checks = True

def add_arguments(self, parser):
parser.add_argument(
"cuit",
type=int,
help=_("CUIT of the tax payer to be used to authenticate."),
)

def handle(self, *args, **options) -> None:
from django_afip.models import TaxPayer

tax_payer = TaxPayer.objects.get(cuit=options["cuit"])
ticket = tax_payer.get_or_create_ticket("wsfe")

client = clients.get_client("wsfe", sandbox=tax_payer.is_sandboxed)
response = client.service.FEParamGetCondicionIvaReceptor(
serializers.serialize_ticket(ticket),
)

for condition in response.ResultGet.CondicionIvaReceptor:
models.ClientVatCondition.objects.get_or_create(
code=condition.Id,
defaults={
"description": condition.Desc,
"cmp_clase": condition.Cmp_Clase,
},
)
self.stdout.write(self.style.SUCCESS(f"Loaded {condition.Desc}"))
self.stdout.write(self.style.SUCCESS("All done!"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 5.1.6 on 2025-02-26 14:09

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('afip', '0015_alter_taxpayer_logo'),
]

operations = [
migrations.CreateModel(
name='ClientVatCondition',
fields=[
('code', models.IntegerField(primary_key=True, serialize=False, verbose_name='code')),
('description', models.CharField(max_length=48, verbose_name='description')),
('cmp_clase', models.CharField(help_text='Receipt class this VAT condition applies to (A, B, C, or M).', max_length=5, verbose_name='cmp clase')),
],
options={
'verbose_name': 'Client VAT condition',
'verbose_name_plural': 'Client VAT conditions',
'unique_together': {('code', 'cmp_clase')},
},
),
migrations.AddField(
model_name='receipt',
name='client_vat_condition',
field=models.ForeignKey(help_text='The VAT condition of the recipient of this receipt. It should match the receipt type.', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='receipts', to='afip.clientvatcondition', verbose_name='client vat condition'),
),
]
37 changes: 36 additions & 1 deletion django_afip/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
def load_metadata() -> None:
"""Loads metadata from fixtures into the database."""

for model in GenericAfipType.SUBCLASSES:
for model in GenericAfipType.SUBCLASSES + [ClientVatCondition]:
label = model._meta.label.split(".")[1].lower()
management.call_command("loaddata", label, app="afip")

Expand Down Expand Up @@ -351,6 +351,30 @@ class Meta:
verbose_name_plural = _("optional types")


class ClientVatCondition(models.Model):
code = models.IntegerField(
_("code"),
primary_key=True,
)
description = models.CharField(
_("description"),
max_length=48,
)
cmp_clase = models.CharField(
_("cmp clase"),
max_length=5,
help_text=_("Receipt class this VAT condition applies to (A, B, C, or M)."),
)

def __str__(self) -> str:
return self.description

class Meta:
verbose_name = _("Client VAT condition")
verbose_name_plural = _("Client VAT conditions")
unique_together = ("code", "cmp_clase") # Ensure unique combination


class TaxPayer(models.Model):
"""Represents an AFIP TaxPayer.

Expand Down Expand Up @@ -1136,6 +1160,17 @@ class Receipt(models.Model):
help_text=_("The document type of the recipient of this receipt."),
on_delete=models.PROTECT,
)
client_vat_condition = models.ForeignKey(
ClientVatCondition,
verbose_name=_("client vat condition"),
help_text=_(
"The VAT condition of the recipient of this receipt. It should match the receipt type."
),
related_name="receipts",
on_delete=models.PROTECT,
null=True,
)

document_number = models.BigIntegerField(
_("document number"),
help_text=_("The document number of the recipient of this receipt."),
Expand Down
1 change: 1 addition & 0 deletions django_afip/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def serialize_receipt(receipt: Receipt): # noqa: ANN201
ImpTrib=sum(tax.amount for tax in taxes),
MonId=receipt.currency.code,
MonCotiz=receipt.currency_quote,
CondicionIVAReceptorId=receipt.client_vat_condition.code,
)
if int(receipt.concept.code) in (2, 3):
serialized.FchServDesde = serialize_date(receipt.service_start)
Expand Down
3 changes: 3 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ setenv =
DJANGO_SETTINGS_MODULE=testapp.settings

[testenv:mypy]
# This breaks too often due to minor version upgrades of related packages.
# It's unreliable and we can't afford to let it block CI.
ignore_outcome = true
extras = dev
commands = mypy .

Expand Down