Mudanza de repo
This commit is contained in:
0
api/customs/__init__.py
Normal file
0
api/customs/__init__.py
Normal file
65
api/customs/admin.py
Normal file
65
api/customs/admin.py
Normal file
@@ -0,0 +1,65 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
EstadoDeProcesamiento,
|
||||
Pedimento,
|
||||
ProcesamientoPedimento,
|
||||
Servicio,
|
||||
TipoDeProcesamiento,
|
||||
TipoOperacion,
|
||||
EDocument,
|
||||
Importador
|
||||
)
|
||||
|
||||
class TipoOperacionAdmin(admin.ModelAdmin):
|
||||
model = TipoOperacion
|
||||
list_display = ('id', 'tipo')
|
||||
search_fields = ('nombre',)
|
||||
|
||||
class PedimentoAdmin(admin.ModelAdmin):
|
||||
model = Pedimento
|
||||
list_display = ('id', 'pedimento', 'aduana', 'patente')
|
||||
search_fields = ('numero',)
|
||||
list_filter = ('aduana', 'agente_aduanal', 'organizacion')
|
||||
|
||||
class ProcesamientoPedimentoAdmin(admin.ModelAdmin):
|
||||
model = ProcesamientoPedimento
|
||||
list_display = ('id', 'estado', 'pedimento', 'created_at', 'updated_at')
|
||||
search_fields = ('pedimento__pedimento_app', 'organizacion__nombre', 'estado__estado', 'servicio__endpoint')
|
||||
list_filter = ('estado', 'organizacion__nombre')
|
||||
|
||||
class EstadoDeProcesamientoAdmin(admin.ModelAdmin):
|
||||
model = EstadoDeProcesamiento
|
||||
list_display = ('id', 'estado')
|
||||
search_fields = ('estado',)
|
||||
|
||||
class TipoDeProcesamientoAdmin(admin.ModelAdmin):
|
||||
model = TipoDeProcesamiento
|
||||
list_display = ('id', 'tipo')
|
||||
# Solo 'tipo' es campo directo, los demás no existen en el modelo
|
||||
list_filter = ['tipo']
|
||||
search_fields = ('tipo', 'organizacion', 'estado', 'servicio')
|
||||
|
||||
class ServicioAdmin(admin.ModelAdmin):
|
||||
model = Servicio
|
||||
list_display = ('id', 'endpoint', 'descripcion')
|
||||
search_fields = ('endpoint', 'descripcion')
|
||||
|
||||
class EDocumentAdmin(admin.ModelAdmin):
|
||||
model = EDocument
|
||||
list_display = ('id', 'pedimento', 'numero_edocument', 'organizacion')
|
||||
search_fields = ('numero_edocument', 'pedimento', 'pedimento__pedimento_app')
|
||||
list_filter = ['organizacion']
|
||||
|
||||
class ImportadorAdmin(admin.ModelAdmin):
|
||||
model = Importador
|
||||
list_display = ('id', 'nombre', 'rfc')
|
||||
search_fields = ('nombre', 'rfc')
|
||||
|
||||
admin.site.register(TipoOperacion, TipoOperacionAdmin)
|
||||
admin.site.register(Pedimento, PedimentoAdmin)
|
||||
admin.site.register(ProcesamientoPedimento, ProcesamientoPedimentoAdmin)
|
||||
admin.site.register(EstadoDeProcesamiento, EstadoDeProcesamientoAdmin)
|
||||
admin.site.register(TipoDeProcesamiento, TipoDeProcesamientoAdmin)
|
||||
admin.site.register(Servicio, ServicioAdmin)
|
||||
admin.site.register(EDocument, EDocumentAdmin)
|
||||
admin.site.register(Importador)
|
||||
9
api/customs/apps.py
Normal file
9
api/customs/apps.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CustomsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'api.customs'
|
||||
|
||||
def ready(self):
|
||||
import api.customs.signals
|
||||
224
api/customs/migrations/0001_initial.py
Normal file
224
api/customs/migrations/0001_initial.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-14 16:14
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('organization', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Aduana',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('seccion', models.CharField(max_length=10)),
|
||||
('descripcion', models.CharField(max_length=200)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Aduana',
|
||||
'verbose_name_plural': 'Aduanas',
|
||||
'db_table': 'aduana',
|
||||
'ordering': ['seccion'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ClavePedimento',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('clave', models.CharField(max_length=10)),
|
||||
('descripcion', models.CharField(max_length=200)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Clave de Pedimento',
|
||||
'verbose_name_plural': 'Claves de Pedimento',
|
||||
'db_table': 'clave_pedimento',
|
||||
'ordering': ['clave'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EstadoDeProcesamiento',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('estado', models.CharField(max_length=50)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Estado de Procesamiento',
|
||||
'verbose_name_plural': 'Estados de Procesamiento',
|
||||
'db_table': 'estado_de_procesamiento',
|
||||
'ordering': ['estado'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Patente',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('numero', models.CharField(max_length=20)),
|
||||
('descripcion', models.CharField(max_length=200)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Patente',
|
||||
'verbose_name_plural': 'Patentes',
|
||||
'db_table': 'patente',
|
||||
'ordering': ['numero'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Regimen',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('clave', models.CharField(max_length=10)),
|
||||
('descripcion', models.CharField(max_length=200)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Regimen',
|
||||
'verbose_name_plural': 'Regimenes',
|
||||
'db_table': 'regimen',
|
||||
'ordering': ['clave'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Servicio',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('endpoint', models.CharField(max_length=100)),
|
||||
('descripcion', models.TextField(blank=True, null=True)),
|
||||
('hora_inicio', models.TimeField(blank=True, max_length=50, null=True)),
|
||||
('hora_fin', models.TimeField(blank=True, max_length=50, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Servicio',
|
||||
'verbose_name_plural': 'Servicios',
|
||||
'db_table': 'servicio',
|
||||
'ordering': ['endpoint'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TipoDeProcesamiento',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('tipo', models.CharField(max_length=50)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Tipo de Procesamiento',
|
||||
'verbose_name_plural': 'Tipos de Procesamiento',
|
||||
'db_table': 'tipo_de_procesamiento',
|
||||
'ordering': ['tipo'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TipoOperacion',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('tipo', models.CharField(max_length=100)),
|
||||
('descripcion', models.CharField(max_length=200)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Tipo de Operacion',
|
||||
'verbose_name_plural': 'Tipos de Operacion',
|
||||
'db_table': 'tipo_operacion',
|
||||
'ordering': ['tipo'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AgenteAduanal',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('nombre', models.CharField(max_length=100)),
|
||||
('rfc', models.CharField(blank=True, max_length=13, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('id_aduana', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agentes_aduanales', to='customs.aduana')),
|
||||
('id_patente', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='agentes_aduanales', to='customs.patente')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Agente Aduanal',
|
||||
'verbose_name_plural': 'Agentes Aduanales',
|
||||
'db_table': 'agente_aduanal',
|
||||
'ordering': ['nombre'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Pedimento',
|
||||
fields=[
|
||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||
('pedimento', models.CharField(help_text='Número de pedimento aduanal', max_length=20, unique=True)),
|
||||
('patente', models.CharField(blank=True, help_text='Número de patente aduanal', max_length=20, null=True)),
|
||||
('aduana', models.CharField(blank=True, help_text='Clave de la aduana según la clasificación aduanera', max_length=10, null=True)),
|
||||
('regimen', models.CharField(blank=True, help_text='Clave del régimen aduanero según la clasificación aduanera', max_length=10, null=True)),
|
||||
('clave_pedimento', models.CharField(blank=True, help_text='Clave del pedimento según la clasificación aduanera', max_length=10, null=True)),
|
||||
('fecha_inicio', models.DateField(blank=True, help_text='Fecha de inicio del pedimento', null=True)),
|
||||
('fecha_fin', models.DateField(blank=True, help_text='Fecha de fin del pedimento', null=True)),
|
||||
('fecha_pago', models.DateField(blank=True, help_text='Fecha de pago del pedimento', null=True)),
|
||||
('alerta', models.BooleanField(default=False, help_text='Indica si el pedimento tiene una alerta asociada')),
|
||||
('contribuyente', models.CharField(blank=True, help_text='Nombre del contribuyente/importador asociado al pedimento', max_length=100, null=True)),
|
||||
('agente_aduanal', models.CharField(blank=True, help_text='RFC del agente aduanal', max_length=100, null=True)),
|
||||
('curp_apoderado', models.CharField(blank=True, help_text='CURP del apoderado aduanal', max_length=18, null=True)),
|
||||
('importe_total', models.DecimalField(blank=True, decimal_places=2, help_text='Importe total del pedimento', max_digits=10, null=True)),
|
||||
('saldo_disponible', models.DecimalField(blank=True, decimal_places=2, help_text='Saldo disponible del pedimento', max_digits=10, null=True)),
|
||||
('importe_pedimento', models.DecimalField(blank=True, decimal_places=2, help_text='Importe del pedimento', max_digits=10, null=True)),
|
||||
('existe_expediente', models.BooleanField(default=False)),
|
||||
('remesas', models.BooleanField(default=False, help_text='Indica si el pedimento tiene remesas asociadas')),
|
||||
('numero_partidas', models.PositiveIntegerField(blank=True, default=0, help_text='Número de partidas asociadas al pedimento', null=True)),
|
||||
('numero_operacion', models.CharField(blank=True, help_text='Número de operación del pedimento', max_length=20, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación del registro')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='Fecha de última actualización del registro')),
|
||||
('organizacion', models.ForeignKey(help_text='Organización a la que pertenece el pedimento', on_delete=django.db.models.deletion.CASCADE, related_name='pedimentos', to='organization.organizacion')),
|
||||
('tipo_operacion', models.ForeignKey(blank=True, help_text='Tipo de operación del pedimento', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='pedimentos', to='customs.tipooperacion')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Pedimento',
|
||||
'verbose_name_plural': 'Pedimentos',
|
||||
'db_table': 'pedimento',
|
||||
'ordering': ['pedimento'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EDocument',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('numero_edocument', models.CharField(help_text='Número único del e-documento', max_length=20, unique=True)),
|
||||
('clave', models.CharField(blank=True, help_text='Clave del e-documento según la clasificación aduanera', max_length=10, null=True)),
|
||||
('cadena_original', models.TextField(blank=True, help_text='Cadena original del e-documento', null=True)),
|
||||
('sello_digital', models.TextField(blank=True, help_text='Firma digital del e-documento', null=True)),
|
||||
('descripcion', models.CharField(blank=True, help_text='Descripción del documento', max_length=200, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación del documento')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='Fecha de última actualización del documento')),
|
||||
('organizacion', models.ForeignKey(help_text='Organización a la que pertenece el EDocument', on_delete=django.db.models.deletion.CASCADE, related_name='edocuments', to='organization.organizacion')),
|
||||
('pedimento', models.ForeignKey(help_text='Pedimento asociado al documento', on_delete=django.db.models.deletion.CASCADE, related_name='documentos', to='customs.pedimento')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'EDocument',
|
||||
'verbose_name_plural': 'EDocuments',
|
||||
'db_table': 'edocs',
|
||||
'ordering': ['created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProcesamientoPedimento',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('estado', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='procesamientos', to='customs.estadodeprocesamiento')),
|
||||
('organizacion', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='procesamientos', to='organization.organizacion')),
|
||||
('pedimento', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='procesamientos', to='customs.pedimento')),
|
||||
('servicio', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='procesamientos', to='customs.servicio')),
|
||||
('tipo_procesamiento', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='procesamientos', to='customs.tipodeprocesamiento')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Procesamiento de Pedimento',
|
||||
'verbose_name_plural': 'Procesamientos de Pedimento',
|
||||
'db_table': 'procesamiento_pedimento',
|
||||
'ordering': ['created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-15 14:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='agenteaduanal',
|
||||
name='id_aduana',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='agenteaduanal',
|
||||
name='id_patente',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='ClavePedimento',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Regimen',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Aduana',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='AgenteAduanal',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Patente',
|
||||
),
|
||||
]
|
||||
32
api/customs/migrations/0003_cove.py
Normal file
32
api/customs/migrations/0003_cove.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-23 22:12
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0002_remove_agenteaduanal_id_aduana_and_more'),
|
||||
('organization', '0002_remove_organizacion_membretado_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Cove',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('numero_cove', models.CharField(help_text='Número único de la cove', max_length=20, unique=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación de la cove')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='Fecha de última actualización de la cove')),
|
||||
('organizacion', models.ForeignKey(help_text='Organización a la que pertenece la cove', on_delete=django.db.models.deletion.CASCADE, related_name='coves', to='organization.organizacion')),
|
||||
('pedimento', models.ForeignKey(help_text='Pedimento asociado a la cove', on_delete=django.db.models.deletion.CASCADE, related_name='coves', to='customs.pedimento')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Cove',
|
||||
'verbose_name_plural': 'Coves',
|
||||
'db_table': 'coves',
|
||||
'ordering': ['created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.2.3 on 2025-08-12 19:01
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0003_cove'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='pedimento',
|
||||
name='pedimento_app',
|
||||
field=models.CharField(default='', help_text='Número de pedimento en la aplicación', max_length=25),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='pedimento',
|
||||
name='pedimento',
|
||||
field=models.CharField(help_text='Número de pedimento aduanal', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.2.3 on 2025-08-12 19:25
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0004_pedimento_pedimento_app_alter_pedimento_pedimento'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='pedimento',
|
||||
name='pedimento_app',
|
||||
),
|
||||
]
|
||||
19
api/customs/migrations/0006_pedimento_pedimento_app.py
Normal file
19
api/customs/migrations/0006_pedimento_pedimento_app.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.3 on 2025-08-12 19:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0005_remove_pedimento_pedimento_app'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='pedimento',
|
||||
name='pedimento_app',
|
||||
field=models.CharField(default='', help_text='Número de pedimento en la aplicación', max_length=25),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
27
api/customs/migrations/0007_regimen.py
Normal file
27
api/customs/migrations/0007_regimen.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Generated by Django 5.2.3 on 2025-08-15 18:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0006_pedimento_pedimento_app'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Regimen',
|
||||
fields=[
|
||||
('id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('claveped', models.CharField(max_length=4)),
|
||||
('regimenped', models.CharField(max_length=4)),
|
||||
('tipo', models.IntegerField()),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Regimen',
|
||||
'verbose_name_plural': 'Regimenes',
|
||||
'db_table': 'regimen',
|
||||
},
|
||||
),
|
||||
]
|
||||
139
api/customs/migrations/0008_regimen_catalogo.py
Normal file
139
api/customs/migrations/0008_regimen_catalogo.py
Normal file
@@ -0,0 +1,139 @@
|
||||
from django.db import migrations
|
||||
|
||||
def cargar_catalogo_regimen(apps, schema_editor):
|
||||
from api.customs.models import Regimen
|
||||
data = [
|
||||
(1, 'A1', 'EXD', 2),
|
||||
(2, 'A1', 'IMD', 1),
|
||||
(3, 'A2', 'ITE', 1),
|
||||
(4, 'A3', 'IMD', 1),
|
||||
(5, 'A4', 'DFI', 2),
|
||||
(6, 'A4', 'DFI', 1),
|
||||
(7, 'A5', 'DFI', 2),
|
||||
(8, 'A5', 'DFI', 1),
|
||||
(9, 'A6', 'ITR', 1),
|
||||
(10, 'A7', 'ITR', 1),
|
||||
(11, 'A8', 'ITE', 1),
|
||||
(12, 'A9', 'ITR', 1),
|
||||
(13, 'AA', 'ITE', 1),
|
||||
(14, 'AD', 'ITR', 1),
|
||||
(15, 'AF', 'ITE', 1),
|
||||
(16, 'AF', 'ITR', 1),
|
||||
(17, 'AJ', 'ETR', 2),
|
||||
(18, 'AJ', 'ITR', 1),
|
||||
(19, 'BA', 'ETR', 2),
|
||||
(20, 'BA', 'ITR', 1),
|
||||
(21, 'BB', 'EXD', 2),
|
||||
(22, 'BC', 'ITR', 1),
|
||||
(23, 'BD', 'ITR', 1),
|
||||
(24, 'BE', 'ITR', 1),
|
||||
(25, 'BF', 'ETR', 2),
|
||||
(26, 'BH', 'ITR', 1),
|
||||
(27, 'BI', 'ITR', 1),
|
||||
(28, 'BM', 'ETE', 2),
|
||||
(29, 'BO', 'ETE', 2),
|
||||
(30, 'BO', 'ITR', 1),
|
||||
(31, 'BP', 'ITR', 1),
|
||||
(32, 'BR', 'ETR', 2),
|
||||
(33, 'C1', 'IMD', 1),
|
||||
(34, 'C2', 'IMD', 1),
|
||||
(35, 'C3', 'IMD', 1),
|
||||
(36, 'CT', 'EXD', 2),
|
||||
(37, 'D1', 'EXD', 2),
|
||||
(38, 'D1', 'IMD', 1),
|
||||
(39, 'E1', 'ITE', 1),
|
||||
(40, 'E2', 'ITR', 1),
|
||||
(41, 'E3', 'ITE', 1),
|
||||
(42, 'E4', 'ITR', 1),
|
||||
(43, 'F2', 'DFI', 1),
|
||||
(44, 'F3', 'IMD', 1),
|
||||
(45, 'F4', 'EXD', 2),
|
||||
(46, 'F4', 'IMD', 1),
|
||||
(47, 'F5', 'IMD', 1),
|
||||
(48, 'F8', 'DFI', 2),
|
||||
(49, 'F8', 'DFI', 1),
|
||||
(50, 'F9', 'DFI', 2),
|
||||
(51, 'F9', 'DFI', 1),
|
||||
(52, 'G1', 'EXD', 2),
|
||||
(53, 'G1', 'IMD', 1),
|
||||
(54, 'G2', 'IMD', 1),
|
||||
(55, 'G6', 'EXD', 2),
|
||||
(56, 'G7', 'EXD', 2),
|
||||
(57, 'G8', 'RFS', 2),
|
||||
(58, 'G9', 'IMD', 2),
|
||||
(59, 'GC', 'EXD', 2),
|
||||
(60, 'GC', 'IMD', 1),
|
||||
(61, 'H1', 'EXD', 2),
|
||||
(62, 'H1', 'IMD', 1),
|
||||
(63, 'H8', 'EXD', 2),
|
||||
(64, 'H8', 'IMD', 1),
|
||||
(65, 'I1', 'EXD', 2),
|
||||
(66, 'I1', 'IMD', 1),
|
||||
(67, 'IN', 'ITE', 1),
|
||||
(68, 'J1', 'EXD', 2),
|
||||
(69, 'J2', 'EXD', 2),
|
||||
(70, 'J3', 'RFE', 2),
|
||||
(71, 'J4', 'RFS', 2),
|
||||
(72, 'K1', 'EXD', 2),
|
||||
(73, 'K1', 'IMD', 1),
|
||||
(74, 'K2', 'EXD', 2),
|
||||
(75, 'K3', 'EXD', 2),
|
||||
(76, 'L1', 'EXD', 2),
|
||||
(77, 'L1', 'IMD', 1),
|
||||
(78, 'M1', 'RFE', 1),
|
||||
(79, 'M2', 'RFE', 1),
|
||||
(80, 'M3', 'RFS', 1),
|
||||
(81, 'M4', 'RFS', 1),
|
||||
(82, 'M5', 'RFS', 1),
|
||||
(83, 'P1', 'IMD', 1),
|
||||
(84, 'R1', 'ETE', 2),
|
||||
(85, 'R1', 'ETR', 2),
|
||||
(86, 'R1', 'EXD', 2),
|
||||
(87, 'R1', 'IMD', 1),
|
||||
(88, 'R1', 'ITE', 1),
|
||||
(89, 'R1', 'ITR', 1),
|
||||
(90, 'RT', 'EXD', 2),
|
||||
(91, 'S2', 'EXD', 2),
|
||||
(92, 'S2', 'IMD', 1),
|
||||
(93, 'T1', 'EXD', 2),
|
||||
(94, 'T1', 'IMD', 1),
|
||||
(95, 'T3', 'TRA', 1),
|
||||
(96, 'T6', 'TRA', 2),
|
||||
(97, 'T7', 'TRA', 1),
|
||||
(98, 'T9', 'TRA', 1),
|
||||
(99, 'V1', 'EXD', 2),
|
||||
(100, 'V1', 'ITE', 1),
|
||||
(101, 'V1', 'ITR', 1),
|
||||
(102, 'V2', 'EXD', 2),
|
||||
(103, 'V2', 'IMD', 1),
|
||||
(104, 'V3', 'DFI', 2),
|
||||
(105, 'V3', 'DFI', 1),
|
||||
(106, 'V4', 'ETR', 2),
|
||||
(107, 'V4', 'ITR', 1),
|
||||
(108, 'V5', 'EXD', 2),
|
||||
(109, 'V5', 'IMD', 1),
|
||||
(110, 'V6', 'EXD', 2),
|
||||
(111, 'V6', 'IMD', 1),
|
||||
(112, 'V7', 'EXD', 2),
|
||||
(113, 'V7', 'ITR', 1),
|
||||
(114, 'V8', 'DFI', 1),
|
||||
(115, 'V8', 'EXD', 2),
|
||||
(116, 'V9', 'EXD', 2),
|
||||
(117, 'V9', 'IMD', 1),
|
||||
(118, 'VF', 'IMD', 1),
|
||||
(119, 'VU', 'IMD', 1),
|
||||
(120, 'H3', 'ITE', 1),
|
||||
(121, 'H3', 'ITR', 1),
|
||||
]
|
||||
for id, claveped, regimenped, tipo in data:
|
||||
Regimen.objects.create(id=id, claveped=claveped, regimenped=regimenped, tipo=tipo)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0007_regimen'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(cargar_catalogo_regimen),
|
||||
]
|
||||
31
api/customs/migrations/0009_importador.py
Normal file
31
api/customs/migrations/0009_importador.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.2.3 on 2025-08-16 15:54
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0008_regimen_catalogo'),
|
||||
('organization', '0002_remove_organizacion_membretado_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Importador',
|
||||
fields=[
|
||||
('rfc', models.CharField(help_text='RFC del importador', max_length=13, primary_key=True, serialize=False, unique=True)),
|
||||
('nombre', models.CharField(help_text='Nombre del importador', max_length=200)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación del registro')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, help_text='Fecha de última actualización del registro')),
|
||||
('organizacion', models.ForeignKey(help_text='Organización a la que pertenece el importador', on_delete=django.db.models.deletion.CASCADE, related_name='importadores', to='organization.organizacion')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Importador',
|
||||
'verbose_name_plural': 'Importadores',
|
||||
'db_table': 'importador',
|
||||
'ordering': ['rfc'],
|
||||
},
|
||||
),
|
||||
]
|
||||
21
api/customs/migrations/0009b_poblar_importadores.py
Normal file
21
api/customs/migrations/0009b_poblar_importadores.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.db import migrations
|
||||
|
||||
def crear_importadores_desde_pedimentos(apps, schema_editor):
|
||||
Pedimento = apps.get_model('customs', 'Pedimento')
|
||||
Importador = apps.get_model('customs', 'Importador')
|
||||
Organizacion = apps.get_model('organization', 'Organizacion')
|
||||
rfcs_orgs = Pedimento.objects.values_list('contribuyente', 'organizacion_id').distinct()
|
||||
for rfc, org_id in rfcs_orgs:
|
||||
if rfc and not Importador.objects.filter(rfc=rfc).exists():
|
||||
organizacion = Organizacion.objects.get(id=org_id) if org_id else None
|
||||
Importador.objects.create(rfc=rfc, nombre='', organizacion=organizacion)
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0009_importador'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(crear_importadores_desde_pedimentos),
|
||||
]
|
||||
19
api/customs/migrations/0010_alter_pedimento_contribuyente.py
Normal file
19
api/customs/migrations/0010_alter_pedimento_contribuyente.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.3 on 2025-08-16 16:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('customs', '0009b_poblar_importadores'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='pedimento',
|
||||
name='contribuyente',
|
||||
field=models.ForeignKey(blank=True, help_text='Contribuyente asociado al pedimento', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pedimentos', to='customs.importador'),
|
||||
),
|
||||
]
|
||||
0
api/customs/migrations/__init__.py
Normal file
0
api/customs/migrations/__init__.py
Normal file
187
api/customs/models.py
Normal file
187
api/customs/models.py
Normal file
@@ -0,0 +1,187 @@
|
||||
import uuid
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class TipoOperacion(models.Model):
|
||||
tipo = models.CharField(max_length=100)
|
||||
descripcion = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.tipo}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tipo de Operacion"
|
||||
verbose_name_plural = "Tipos de Operacion"
|
||||
db_table = 'tipo_operacion'
|
||||
ordering = ['tipo']
|
||||
|
||||
class Pedimento(models.Model):
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
pedimento = models.CharField(max_length=20, unique=False, help_text="Número de pedimento aduanal")
|
||||
pedimento_app = models.CharField(max_length=25, unique=False, help_text="Número de pedimento en la aplicación")
|
||||
|
||||
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='pedimentos', help_text="Organización a la que pertenece el pedimento")
|
||||
|
||||
patente = models.CharField(max_length=20, blank=True, null=True, help_text="Número de patente aduanal")
|
||||
aduana = models.CharField(max_length=10, blank=True, null=True, help_text="Clave de la aduana según la clasificación aduanera")
|
||||
regimen = models.CharField(max_length=10, blank=True, null=True, help_text="Clave del régimen aduanero según la clasificación aduanera")
|
||||
tipo_operacion = models.ForeignKey('TipoOperacion', on_delete=models.SET_NULL, blank=True, null=True, help_text="Tipo de operación del pedimento", related_name='pedimentos')
|
||||
clave_pedimento = models.CharField(max_length=10, blank=True, null=True, help_text="Clave del pedimento según la clasificación aduanera")
|
||||
|
||||
fecha_inicio = models.DateField(help_text="Fecha de inicio del pedimento", blank=True, null=True)
|
||||
fecha_fin = models.DateField(help_text="Fecha de fin del pedimento", blank=True, null=True)
|
||||
fecha_pago = models.DateField(help_text="Fecha de pago del pedimento", blank=True, null=True)
|
||||
|
||||
alerta = models.BooleanField(default=False, help_text="Indica si el pedimento tiene una alerta asociada")
|
||||
|
||||
contribuyente = models.ForeignKey('Importador', on_delete=models.CASCADE, related_name='pedimentos', help_text="Contribuyente asociado al pedimento", blank=True, null=True)
|
||||
agente_aduanal = models.CharField(max_length=100, blank=True, null=True, help_text="RFC del agente aduanal")
|
||||
|
||||
curp_apoderado = models.CharField(max_length=18, blank=True, null=True, help_text="CURP del apoderado aduanal")
|
||||
|
||||
importe_total = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, help_text="Importe total del pedimento")
|
||||
saldo_disponible = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, help_text="Saldo disponible del pedimento")
|
||||
importe_pedimento = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True, help_text="Importe del pedimento")
|
||||
existe_expediente = models.BooleanField(default=False)
|
||||
remesas = models.BooleanField(default=False, help_text="Indica si el pedimento tiene remesas asociadas")
|
||||
|
||||
numero_partidas = models.PositiveIntegerField(default=0, help_text="Número de partidas asociadas al pedimento", blank=True, null=True)
|
||||
numero_operacion = models.CharField(max_length=20, blank=True, null=True, help_text="Número de operación del pedimento")
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del registro")
|
||||
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del registro")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pedimento}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Pedimento"
|
||||
verbose_name_plural = "Pedimentos"
|
||||
db_table = 'pedimento'
|
||||
ordering = ['pedimento']
|
||||
|
||||
class EDocument(models.Model):
|
||||
pedimento = models.ForeignKey(Pedimento, on_delete=models.CASCADE, related_name='documentos', help_text="Pedimento asociado al documento")
|
||||
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='edocuments', help_text="Organización a la que pertenece el EDocument")
|
||||
numero_edocument = models.CharField(max_length=20, unique=True, help_text="Número único del e-documento")
|
||||
clave = models.CharField(max_length=10, blank=True, null=True, help_text="Clave del e-documento según la clasificación aduanera")
|
||||
cadena_original = models.TextField(blank=True, null=True, help_text="Cadena original del e-documento")
|
||||
sello_digital = models.TextField(blank=True, null=True, help_text="Firma digital del e-documento")
|
||||
descripcion = models.CharField(max_length=200, blank=True, null=True, help_text="Descripción del documento")
|
||||
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del documento")
|
||||
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del documento")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.descripcion} - {self.pedimento.pedimento}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "EDocument"
|
||||
verbose_name_plural = "EDocuments"
|
||||
db_table = 'edocs'
|
||||
ordering = ['created_at']
|
||||
|
||||
class Cove(models.Model):
|
||||
pedimento = models.ForeignKey(Pedimento, on_delete=models.CASCADE, related_name='coves', help_text="Pedimento asociado a la cove")
|
||||
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='coves', help_text="Organización a la que pertenece la cove")
|
||||
numero_cove = models.CharField(max_length=20, unique=True, help_text="Número único de la cove")
|
||||
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación de la cove")
|
||||
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización de la cove")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.numero_cove} - {self.pedimento.pedimento}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Cove"
|
||||
verbose_name_plural = "Coves"
|
||||
db_table = 'coves'
|
||||
ordering = ['created_at']
|
||||
|
||||
class EstadoDeProcesamiento(models.Model):
|
||||
estado = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return self.estado
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Estado de Procesamiento"
|
||||
verbose_name_plural = "Estados de Procesamiento"
|
||||
db_table = 'estado_de_procesamiento'
|
||||
ordering = ['estado']
|
||||
|
||||
class TipoDeProcesamiento(models.Model):
|
||||
tipo = models.CharField(max_length=50)
|
||||
|
||||
def __str__(self):
|
||||
return self.tipo
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tipo de Procesamiento"
|
||||
verbose_name_plural = "Tipos de Procesamiento"
|
||||
db_table = 'tipo_de_procesamiento'
|
||||
ordering = ['tipo']
|
||||
|
||||
class Servicio(models.Model):
|
||||
endpoint = models.CharField(max_length=100)
|
||||
descripcion = models.TextField(blank=True, null=True)
|
||||
hora_inicio = models.TimeField(max_length=50, blank=True, null=True)
|
||||
hora_fin = models.TimeField(max_length=50, blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.endpoint
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Servicio"
|
||||
verbose_name_plural = "Servicios"
|
||||
db_table = 'servicio'
|
||||
ordering = ['endpoint']
|
||||
|
||||
class ProcesamientoPedimento(models.Model):
|
||||
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='procesamientos')
|
||||
estado = models.ForeignKey(EstadoDeProcesamiento, on_delete=models.CASCADE, related_name='procesamientos')
|
||||
tipo_procesamiento = models.ForeignKey(TipoDeProcesamiento, on_delete=models.CASCADE, related_name='procesamientos', blank=True, null=True)
|
||||
pedimento = models.ForeignKey(Pedimento, on_delete=models.CASCADE, related_name='procesamientos')
|
||||
servicio = models.ForeignKey(Servicio, on_delete=models.CASCADE, related_name='procesamientos', blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pedimento.pedimento} - {self.estado.estado}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Procesamiento de Pedimento"
|
||||
verbose_name_plural = "Procesamientos de Pedimento"
|
||||
db_table = 'procesamiento_pedimento'
|
||||
ordering = ['created_at']
|
||||
|
||||
class Regimen(models.Model):
|
||||
id = models.AutoField(primary_key=True)
|
||||
claveped = models.CharField(max_length=4)
|
||||
regimenped = models.CharField(max_length=4)
|
||||
tipo = models.IntegerField()
|
||||
|
||||
class Meta:
|
||||
db_table = 'regimen'
|
||||
verbose_name = 'Regimen'
|
||||
verbose_name_plural = 'Regimenes'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.claveped} - {self.regimenped} - {self.tipo}"
|
||||
|
||||
class Importador(models.Model):
|
||||
rfc = models.CharField(primary_key=True, max_length=13, unique=True, help_text="RFC del importador")
|
||||
nombre = models.CharField(max_length=200, help_text="Nombre del importador")
|
||||
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='importadores', help_text="Organización a la que pertenece el importador")
|
||||
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del registro")
|
||||
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del registro")
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Importador'
|
||||
verbose_name_plural = 'Importadores'
|
||||
db_table = 'importador'
|
||||
ordering = ['rfc']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.rfc} - {self.nombre}"
|
||||
96
api/customs/serializers.py
Normal file
96
api/customs/serializers.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from rest_framework import serializers
|
||||
from api.customs.models import (
|
||||
Pedimento,
|
||||
TipoOperacion,
|
||||
ProcesamientoPedimento,
|
||||
EDocument,
|
||||
Cove,
|
||||
Importador
|
||||
)
|
||||
from django.db import models
|
||||
from api.record.models import Document # Asegúrate de importar el modelo Documento
|
||||
from api.record.serializers import DocumentSerializer
|
||||
from api.vucem.serializers import VucemSerializer
|
||||
|
||||
class PedimentoSerializer(serializers.ModelSerializer):
|
||||
documentos_count = serializers.SerializerMethodField()
|
||||
documentos_peso_total = serializers.SerializerMethodField()
|
||||
|
||||
def get_documentos_count(self, obj):
|
||||
# Si obj es un dict o no tiene 'documents', devuelve 0
|
||||
if isinstance(obj, dict) or not hasattr(obj, 'documents'):
|
||||
return 0
|
||||
return obj.documents.count()
|
||||
|
||||
def get_documentos_peso_total(self, obj):
|
||||
# Si obj es un dict o no tiene 'documents', devuelve 0
|
||||
if isinstance(obj, dict) or not hasattr(obj, 'documents'):
|
||||
return 0
|
||||
return obj.documents.aggregate(total=models.Sum('size'))['total'] or 0
|
||||
class Meta:
|
||||
model = Pedimento
|
||||
fields = '__all__'
|
||||
read_only_fields = (
|
||||
'created_at', 'updated_at', 'organizacion', 'pedimento_app',
|
||||
'documentos_count', 'documentos_peso_total'
|
||||
)
|
||||
|
||||
def to_representation(self, instance):
|
||||
rep = super().to_representation(instance)
|
||||
rep['documentos_count'] = self.get_documentos_count(instance)
|
||||
rep['documentos_peso_total'] = self.get_documentos_peso_total(instance)
|
||||
return rep
|
||||
|
||||
|
||||
|
||||
class TipoOperacionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TipoOperacion
|
||||
fields = '__all__'
|
||||
|
||||
class ProcesamientoPedimentoSerializer(serializers.ModelSerializer):
|
||||
|
||||
organizacion = serializers.PrimaryKeyRelatedField(queryset=ProcesamientoPedimento._meta.get_field('organizacion').related_model.objects.all(), required=False)
|
||||
organizacion_name = serializers.CharField(source='organizacion.nombre', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ProcesamientoPedimento
|
||||
fields = '__all__'
|
||||
read_only_fields = ('created_at', 'updated_at')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
request = self.context.get('request')
|
||||
# Si no es superusuario, hacer organizacion read_only
|
||||
if request and hasattr(request, 'user') and not request.user.is_superuser:
|
||||
self.fields['organizacion'].read_only = True
|
||||
|
||||
def to_representation(self, instance):
|
||||
representation = super().to_representation(instance)
|
||||
representation['pedimento'] = PedimentoSerializer(instance.pedimento).data
|
||||
return representation
|
||||
|
||||
class EDocumentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = EDocument
|
||||
fields = '__all__'
|
||||
read_only_fields = ('created_at', 'updated_at')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Si no es superusuario, hacer organizacion read_only
|
||||
request = self.context.get('request')
|
||||
if request and hasattr(request, 'user') and not request.user.is_superuser:
|
||||
self.fields['organizacion'].read_only = True
|
||||
|
||||
class CoveSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Cove
|
||||
fields = '__all__'
|
||||
read_only_fields = ('created_at', 'updated_at')
|
||||
|
||||
class ImportadorSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Importador
|
||||
fields = '__all__'
|
||||
read_only_fields = ('created_at', 'updated_at')
|
||||
0
api/customs/signals/__init__.py
Normal file
0
api/customs/signals/__init__.py
Normal file
59
api/customs/signals/procesamiento.py
Normal file
59
api/customs/signals/procesamiento.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from django.db import transaction
|
||||
from time import sleep
|
||||
|
||||
from api.customs.models import Pedimento, ProcesamientoPedimento, Cove, EDocument
|
||||
from api.customs.tasks.internal_services import (
|
||||
crear_procesamiento_remesa,
|
||||
crear_procesamiento_partida,
|
||||
crear_procesamiento_cove,
|
||||
crear_procesamiento_acuse_cove,
|
||||
crear_procesamiento_acuse,
|
||||
crear_procesamiento_edocument
|
||||
)
|
||||
from api.customs.tasks.microservice import (
|
||||
ejecutar_pedimento_completo,
|
||||
procesar_pedimento_completo_individual
|
||||
|
||||
)
|
||||
|
||||
@receiver(post_save, sender=Pedimento)
|
||||
def trigger_celery_task_on_create(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
procesar_pedimento_completo_individual.apply_async(args=[instance.id, instance.organizacion.id])
|
||||
|
||||
@receiver(post_save, sender=Pedimento)
|
||||
def trigger_celery_task_on_update(sender, instance, created,**kwargs):
|
||||
if not created:
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
logger.info(f"Pedimento actualizado: {instance.id}, verificando servicios a crear...")
|
||||
sleep(4)
|
||||
def enqueue_tasks():
|
||||
if instance.remesas:
|
||||
logger.info(f"Creando proceso de remesas para pedimento {instance.id}")
|
||||
crear_procesamiento_remesa.apply_async(args=[str(instance.id)])
|
||||
if hasattr(instance, 'numero_partidas') and instance.numero_partidas and instance.numero_partidas > 0:
|
||||
logger.info(f"Creando proceso de partida para pedimento {instance.id}")
|
||||
crear_procesamiento_partida.apply_async(args=[str(instance.id)])
|
||||
|
||||
transaction.on_commit(enqueue_tasks)
|
||||
|
||||
@receiver(post_save, sender=Cove)
|
||||
def trigger_celery_task_on_cove_create(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
logger.info(f"Cove creado: {instance.id}, creando procesamiento...")
|
||||
crear_procesamiento_cove.apply_async(args=[str(instance.pedimento.id)])
|
||||
crear_procesamiento_acuse_cove.apply_async(args=[str(instance.pedimento.id)])
|
||||
|
||||
@receiver(post_save, sender=EDocument)
|
||||
def trigger_celery_task_on_edocument_create(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
logger.info(f"EDocument creado: {instance.id}, creando procesamiento...")
|
||||
crear_procesamiento_edocument.apply_async(args=[str(instance.pedimento.id)])
|
||||
crear_procesamiento_acuse.apply_async(args=[str(instance.pedimento.id)])
|
||||
2
api/customs/tasks/__init__.py
Normal file
2
api/customs/tasks/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .microservice import *
|
||||
from .internal_services import *
|
||||
78
api/customs/tasks/auditoria.py
Normal file
78
api/customs/tasks/auditoria.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from celery import shared_task, group
|
||||
from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocument
|
||||
from core.utils import xml_controller
|
||||
import requests
|
||||
from core.utils import xml_remesas_controller
|
||||
|
||||
def obtener_pedimentos(organizacion_id):
|
||||
return Pedimento.objects.filter(organizacion_id=organizacion_id)
|
||||
|
||||
def extraer_coves(pedimento):
|
||||
remesas = pedimento.documents.filter(document_type=3).first()
|
||||
with open(f'./media/{remesas.archivo}', 'r') as f:
|
||||
xml_content = f.read()
|
||||
|
||||
xml_data = xml_remesas_controller.extract_remesas(xml_content)
|
||||
|
||||
return xml_data
|
||||
|
||||
@shared_task
|
||||
def auditar_procesamiento_remesas(organizacion_id):
|
||||
pedimentos = obtener_pedimentos(organizacion_id)
|
||||
|
||||
for pedimento in pedimentos:
|
||||
if pedimento.remesas:
|
||||
# Tipo 3: Remesa
|
||||
if not pedimento.documents.filter(document_type=3).exists():
|
||||
ProcesamientoPedimento.objects.get_or_create(
|
||||
pedimento=pedimento,
|
||||
servicio_id=5, # ID del servicio de remesas
|
||||
organizacion=organizacion_id
|
||||
)
|
||||
else:
|
||||
xml_data = extraer_coves(pedimento)
|
||||
if xml_data:
|
||||
for remesa in xml_data:
|
||||
Cove.objects.get_or_create(
|
||||
pedimento=pedimento,
|
||||
numero_cove=remesa.get('remesaSA'),
|
||||
organizacion=organizacion_id
|
||||
)
|
||||
|
||||
|
||||
@shared_task
|
||||
def auditar_partidas(organizacion_id):
|
||||
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
||||
for pedimento in pedimentos:
|
||||
partidas_descargadas = pedimento.documents.filter(document_type=1)
|
||||
|
||||
partidas = {str(documento.archivo).split('_')[-1].split('.')[0]: documento.archivo for documento in partidas_descargadas}
|
||||
partidas_faltantes = []
|
||||
|
||||
for i in range(1, pedimento.numero_partidas + 1):
|
||||
if str(i) not in partidas.keys():
|
||||
partidas_faltantes.append(i)
|
||||
# crear servicio individual para cada partida faltante en microservicios
|
||||
|
||||
|
||||
@shared_task
|
||||
def auditar_coves(organizacion_id):
|
||||
# crear servicio individual para cada cove faltante en microservicios
|
||||
pass
|
||||
|
||||
@shared_task
|
||||
def auditar_edocuments(organizacion_id):
|
||||
# crear servicio individual para cada Edocument faltante en microservicios
|
||||
pass
|
||||
|
||||
@shared_task
|
||||
def auditar_acuse_coves(organizacion_id):
|
||||
# crear servicio individual para cada cove faltante en microservicios
|
||||
|
||||
pass
|
||||
|
||||
@shared_task
|
||||
def auditar_acuse_edocuments(organizacion_id):
|
||||
# crear servicio individual para cada acuse de edocument faltante en microservicios
|
||||
pass
|
||||
|
||||
220
api/customs/tasks/internal_services.py
Normal file
220
api/customs/tasks/internal_services.py
Normal file
@@ -0,0 +1,220 @@
|
||||
from celery import shared_task, group
|
||||
from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocument
|
||||
from core.utils import xml_controller
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_remesa(pedimento_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
logger.info(f"[TAREA] crear_procesamiento_remesa para pedimento {pedimento_id}")
|
||||
if pedimento.remesas:
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=5, # ID del servicio de remesas
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento remesa creado para pedimento {pedimento_id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=5,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_partida(pedimento_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
logger.info(f"[TAREA] crear_procesamiento_partida para pedimento {pedimento_id}")
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=4, # ID del servicio de partidas
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento partida creado para pedimento {pedimento_id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=4,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_cove(pedimento_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
logger.info(f"[TAREA] crear_procesamiento_cove para pedimento {pedimento_id}")
|
||||
if pedimento.coves.exists():
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=8, # ID del servicio de Coves
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento cove creado para pedimento {pedimento_id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=8,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_acuse(pedimento_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
logger.info(f"[TAREA] crear_procesamiento_acuse para pedimento {pedimento_id}")
|
||||
if pedimento.coves.exists():
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=6, # ID del servicio de Acuse Cove
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento acuse creado para pedimento {pedimento_id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=6,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_acuse_cove(pedimento_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
logger.info(f"[TAREA] crear_procesamiento_acuse_cove para pedimento {pedimento_id}")
|
||||
if pedimento.coves.exists():
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=9, # ID del servicio de Acuse Cove
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento acuse_cove creado para pedimento {pedimento_id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=9,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_edocument(pedimento_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
logger.info(f"[TAREA] crear_procesamiento_edocument para pedimento {pedimento_id}")
|
||||
if pedimento.documentos.exists():
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=7, # ID del servicio de EDocument
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento edocument creado para pedimento {pedimento_id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=7,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_procesamiento_pedimento_completo(organizacion_id):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
||||
for pedimento in pedimentos:
|
||||
logger.info(f"[TAREA] crear_procesamiento_pedimento_completo para pedimento {pedimento.id}")
|
||||
existe = ProcesamientoPedimento.objects.filter(
|
||||
pedimento=pedimento,
|
||||
servicio_id=3, # ID del servicio de Pedimento Completo
|
||||
organizacion=pedimento.organizacion,
|
||||
estado_id__in=[1, 2, 3, 4]
|
||||
).exists()
|
||||
if not existe:
|
||||
logger.info(f"[TAREA] ProcesamientoPedimento pedimento_completo creado para pedimento {pedimento.id}")
|
||||
ProcesamientoPedimento.objects.create(
|
||||
pedimento=pedimento,
|
||||
estado_id=1, # Estado "pendiente"
|
||||
servicio_id=3,
|
||||
organizacion=pedimento.organizacion
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def crear_servicios(organizacion_id):
|
||||
pedimentos = Pedimento.objects.filter(organizacion=organizacion_id)
|
||||
for pedimento in pedimentos:
|
||||
crear_procesamiento_remesa.apply_async(args=[str(pedimento.id)])
|
||||
crear_procesamiento_partida.apply_async(args=[str(pedimento.id)])
|
||||
crear_procesamiento_cove.apply_async(args=[str(pedimento.id)])
|
||||
crear_procesamiento_acuse.apply_async(args=[str(pedimento.id)])
|
||||
crear_procesamiento_acuse_cove.apply_async(args=[str(pedimento.id)])
|
||||
crear_procesamiento_edocument.apply_async(args=[str(pedimento.id)])
|
||||
|
||||
@shared_task
|
||||
def auditar_pedimento(organizacion_id):
|
||||
|
||||
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
||||
for pedimento in pedimentos:
|
||||
pc = pedimento.documents.filter(document_type__id=2).first()
|
||||
if pc:
|
||||
with open(f'./media/{pc.archivo}', 'r') as f:
|
||||
xml_content = f.read()
|
||||
|
||||
xml_data = xml_controller.extract_data(xml_content)
|
||||
|
||||
pedimento.numero_operacion = xml_data.get('numero_operacion')
|
||||
pedimento.curp_apoderado = xml_data.get('curp_apoderado')
|
||||
pedimento.agente_aduanal = xml_data.get('agente_aduanal')
|
||||
pedimento.numero_partidas = xml_data.get('numero_partidas')
|
||||
pedimento.remesas = xml_data.get('remesas')
|
||||
pedimento.tipo_operacion__id = xml_data.get('tipo_operacion')
|
||||
pedimento.save()
|
||||
|
||||
for edoc in xml_data.get('edocuments', []):
|
||||
EDocument.objects.get_or_create(
|
||||
pedimento=pedimento,
|
||||
organizacion=pedimento.organizacion,
|
||||
clave=edoc.get('clave'),
|
||||
descripcion=edoc.get('descripcion'),
|
||||
numero_edocument=edoc.get('complemento1')
|
||||
)
|
||||
|
||||
from django.db import IntegrityError
|
||||
try:
|
||||
for cove in xml_data.get('coves', []):
|
||||
try:
|
||||
Cove.objects.get_or_create(
|
||||
pedimento=pedimento,
|
||||
organizacion=pedimento.organizacion,
|
||||
numero_cove=cove
|
||||
)
|
||||
except IntegrityError:
|
||||
# Si ya existe por unique, recupera el objeto existente
|
||||
Cove.objects.get(numero_cove=cove)
|
||||
except:
|
||||
# Si ya existe por unique, recupera el objeto existente
|
||||
pass
|
||||
|
||||
@shared_task
|
||||
def crear_todos_los_servicios():
|
||||
from organization.models import Organizacion
|
||||
organizaciones = Organizacion.objects.all()
|
||||
for org in organizaciones:
|
||||
crear_procesamiento_pedimento_completo.apply_async(args=[str(org.id)])
|
||||
crear_servicios.apply_async(args=[str(org.id)])
|
||||
213
api/customs/tasks/microservice.py
Normal file
213
api/customs/tasks/microservice.py
Normal file
@@ -0,0 +1,213 @@
|
||||
|
||||
from celery import shared_task, group
|
||||
from api.customs.models import ProcesamientoPedimento
|
||||
import requests
|
||||
from config.settings import SERVICE_API_URL
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ===================
|
||||
# Pedimento Completo
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_pedimento_completo_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/pedimento_completo",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Pedimento {pedimento_id} procesado correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar el pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_pedimento_completo():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=3)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_pedimento_completo_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
# Validar horario permitido (5:00 a 22:00)
|
||||
ahora = datetime.now().time()
|
||||
if (ahora < datetime.strptime('05:00', '%H:%M').time()) or (ahora >= datetime.strptime('22:00', '%H:%M').time()):
|
||||
print('ejecutar_pedimento_completo: fuera de horario permitido (5:00-22:00). Abortando.')
|
||||
return
|
||||
|
||||
# ===================
|
||||
# Partidas
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_partida_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/partidas",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Partidas del pedimento {pedimento_id} procesadas correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar partidas del pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_partidas_pedimento():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=4)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_partida_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
# Validar horario permitido (5:00 a 22:00)
|
||||
ahora = datetime.now().time()
|
||||
if (ahora < datetime.strptime('05:00', '%H:%M').time()) or (ahora >= datetime.strptime('22:00', '%H:%M').time()):
|
||||
print('ejecutar_partidas_pedimento: fuera de horario permitido (5:00-22:00). Abortando.')
|
||||
return
|
||||
|
||||
# ===================
|
||||
# Remesas
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_remesa_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/remesas",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Remesas del pedimento {pedimento_id} procesadas correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar remesas del pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_remesas():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=5)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_remesa_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
# Validar horario permitido (5:00 a 22:00)
|
||||
ahora = datetime.now().time()
|
||||
if (ahora < datetime.strptime('05:00', '%H:%M').time()) or (ahora >= datetime.strptime('22:00', '%H:%M').time()):
|
||||
print('ejecutar_remesas: fuera de horario permitido (5:00-22:00). Abortando.')
|
||||
return
|
||||
|
||||
# ===================
|
||||
# Acuses
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_acuse_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/acuse",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Acuses del pedimento {pedimento_id} procesadas correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar Acuses del pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_acuse():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=6)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_acuse_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
# Validar horario permitido (5:00 a 22:00)
|
||||
ahora = datetime.now().time()
|
||||
if (ahora < datetime.strptime('05:00', '%H:%M').time()) or (ahora >= datetime.strptime('22:00', '%H:%M').time()):
|
||||
print('ejecutar_acuse: fuera de horario permitido (5:00-22:00). Abortando.')
|
||||
return
|
||||
|
||||
# ===================
|
||||
# Edocuments
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_edoc_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/edocument",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Edocuments del pedimento {pedimento_id} procesadas correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar Edocuments del pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_edocs():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=7)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_edoc_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
|
||||
# ===================
|
||||
# Coves
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_cove_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/coves",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Coves del pedimento {pedimento_id} procesadas correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar Coves del pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_coves():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=8)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_cove_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
# Validar horario permitido (5:00 a 22:00)
|
||||
ahora = datetime.now().time()
|
||||
if (ahora < datetime.strptime('05:00', '%H:%M').time()) or (ahora >= datetime.strptime('22:00', '%H:%M').time()):
|
||||
print('ejecutar_coves: fuera de horario permitido (5:00-22:00). Abortando.')
|
||||
return
|
||||
|
||||
# ===================
|
||||
# Acuse Cove
|
||||
# ===================
|
||||
@shared_task
|
||||
def procesar_acuse_cove_individual(pedimento_id, organizacion_id):
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL}/async/services/acuse-cove",
|
||||
json={"pedimento": str(pedimento_id), "organizacion": str(organizacion_id)}
|
||||
)
|
||||
if response.status_code == 200:
|
||||
print(f"Coves del pedimento {pedimento_id} procesadas correctamente.")
|
||||
else:
|
||||
print(f"Error al procesar Coves del pedimento {pedimento_id}: {response.status_code} - {response.text}")
|
||||
print(f"Disparando evento para procesamiento {pedimento_id}")
|
||||
|
||||
@shared_task
|
||||
def ejecutar_acuseCoves():
|
||||
pendientes = ProcesamientoPedimento.objects.filter(estado=1, servicio=9)
|
||||
batch_size = 20
|
||||
ids = list(pendientes.values_list('pedimento_id', 'organizacion_id'))
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
job = group(procesar_acuse_cove_individual.s(ped_id, org_id) for ped_id, org_id in batch)
|
||||
job.apply_async()
|
||||
# Validar horario permitido (5:00 a 22:00)
|
||||
ahora = datetime.now().time()
|
||||
if (ahora < datetime.strptime('05:00', '%H:%M').time()) or (ahora >= datetime.strptime('22:00', '%H:%M').time()):
|
||||
print('ejecutar_acuseCoves: fuera de horario permitido (5:00-22:00). Abortando.')
|
||||
return
|
||||
|
||||
77
api/customs/tests.py
Normal file
77
api/customs/tests.py
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
from api.organization.models import Organizacion
|
||||
from .models import Pedimento, TipoOperacion, ProcesamientoPedimento, EDocument
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class CustomsViewsTests(APITestCase):
|
||||
def setUp(self):
|
||||
self.org = Organizacion.objects.create(nombre="OrgTest", is_active=True, is_verified=True)
|
||||
self.org2 = Organizacion.objects.create(nombre="OrgTest2", is_active=True, is_verified=True)
|
||||
self.admin = User.objects.create_user(username="admin", password="adminpass", organizacion=self.org)
|
||||
self.admin.groups.create(name="admin")
|
||||
self.superuser = User.objects.create_superuser(username="superuser", password="superpass")
|
||||
self.importador = User.objects.create_user(username="importador", password="importpass", organizacion=self.org2, is_importador=True, rfc="RFC123456789")
|
||||
self.importador.groups.create(name="importador")
|
||||
self.client = APIClient()
|
||||
|
||||
def test_admin_sees_only_own_pedimentos(self):
|
||||
from .models import Pedimento
|
||||
p1 = Pedimento.objects.create(pedimento="P1", organizacion=self.org)
|
||||
p2 = Pedimento.objects.create(pedimento="P2", organizacion=self.org2)
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
url = reverse('Pedimento-list')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
pedimentos = [p['pedimento'] for p in response.data]
|
||||
self.assertIn("P1", pedimentos)
|
||||
self.assertNotIn("P2", pedimentos)
|
||||
|
||||
def test_superuser_sees_all_pedimentos(self):
|
||||
from .models import Pedimento
|
||||
p1 = Pedimento.objects.create(pedimento="P1", organizacion=self.org)
|
||||
p2 = Pedimento.objects.create(pedimento="P2", organizacion=self.org2)
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
url = reverse('Pedimento-list')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
pedimentos = [p['pedimento'] for p in response.data]
|
||||
self.assertIn("P1", pedimentos)
|
||||
self.assertIn("P2", pedimentos)
|
||||
|
||||
def test_importador_cannot_create_pedimento(self):
|
||||
self.client.force_authenticate(user=self.importador)
|
||||
url = reverse('Pedimento-list')
|
||||
data = {
|
||||
"pedimento": "P3",
|
||||
"patente": "1234",
|
||||
"aduana": "001",
|
||||
"regimen": "A1",
|
||||
"clave_pedimento": "A1",
|
||||
"contribuyente": "ImportadorTest"
|
||||
}
|
||||
response = self.client.post(url, data)
|
||||
self.assertNotIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_200_OK])
|
||||
|
||||
def test_list_tipos_operacion(self):
|
||||
url = reverse('TipoOperacion-list')
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_list_procesamientos(self):
|
||||
url = reverse('ProcesamientoPedimento-list')
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_list_edocuments(self):
|
||||
url = reverse('EDocument-list')
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
34
api/customs/urls.py
Normal file
34
api/customs/urls.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# This file defines the URL patterns for the customs app in a Django project.
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
# import necessary viewsets
|
||||
from .views import (
|
||||
ViewSetPedimento,
|
||||
ViewSetTipoOperacion,
|
||||
ViewSetProcesamientoPedimento,
|
||||
ViewSetEDocument,
|
||||
ViewSetCove,
|
||||
ImportadorViewSet
|
||||
)
|
||||
# from .views import YourViewSet # Import your viewsets here
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
# Register your viewsets with the router here
|
||||
# Example:
|
||||
# from .views import MyViewSet
|
||||
# router.register(r'myviewset', MyViewSet, basename='myviewset')
|
||||
|
||||
router.register(r'pedimentos', ViewSetPedimento, basename='Pedimento')
|
||||
router.register(r'tiposoperacion', ViewSetTipoOperacion, basename='TipoOperacion')
|
||||
router.register(r'procesamientopedimentos', ViewSetProcesamientoPedimento, basename='ProcesamientoPedimento')
|
||||
router.register(r'edocuments', ViewSetEDocument, basename='EDocument')
|
||||
router.register(r'coves', ViewSetCove, basename='Cove')
|
||||
router.register(r'importadores', ImportadorViewSet, basename='Importador')
|
||||
|
||||
# Import your viewsets here
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
490
api/customs/views.py
Normal file
490
api/customs/views.py
Normal file
@@ -0,0 +1,490 @@
|
||||
from config.settings import SERVICE_API_URL
|
||||
from django.shortcuts import render
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from core.permissions import (
|
||||
IsSameOrganization,
|
||||
IsSameOrganizationDeveloper,
|
||||
IsSameOrganizationAndAdmin,
|
||||
IsSuperUser
|
||||
)
|
||||
from api.customs.models import (
|
||||
Pedimento,
|
||||
TipoOperacion,
|
||||
ProcesamientoPedimento,
|
||||
EDocument,
|
||||
Cove,
|
||||
Importador
|
||||
)
|
||||
from api.customs.serializers import (
|
||||
PedimentoSerializer,
|
||||
TipoOperacionSerializer,
|
||||
ProcesamientoPedimentoSerializer,
|
||||
EDocumentSerializer,
|
||||
CoveSerializer,
|
||||
ImportadorSerializer
|
||||
|
||||
)
|
||||
from api.logger.mixins import LoggingMixin
|
||||
from mixins.filtrado_organizacion import OrganizacionFiltradaMixin, ProcesosPorOrganizacionMixin
|
||||
import requests
|
||||
|
||||
|
||||
|
||||
class CustomPagination(PageNumberPagination):
|
||||
|
||||
"""
|
||||
Paginación personalizada con parámetros flexibles
|
||||
- Si no se especifica page_size, devuelve todos los resultados (sin paginación)
|
||||
- Si se especifica page_size, usa paginación normal
|
||||
"""
|
||||
page_size = None # Sin paginación por defecto
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 10000 # Límite máximo de seguridad
|
||||
page_query_param = 'page'
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
"""
|
||||
Si no se especifica page_size en los parámetros, devolver None (sin paginación)
|
||||
Si se especifica, usar paginación normal
|
||||
"""
|
||||
# Verificar si se especificó page_size en la query
|
||||
if self.page_size_query_param not in request.query_params:
|
||||
# No hay page_size, devolver None para indicar "sin paginación"
|
||||
return None
|
||||
|
||||
# Hay page_size, usar paginación normal
|
||||
try:
|
||||
page_size = int(request.query_params[self.page_size_query_param])
|
||||
if page_size <= 0:
|
||||
return None
|
||||
# Establecer el page_size temporalmente para esta request
|
||||
self.page_size = min(page_size, self.max_page_size)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
return super().paginate_queryset(queryset, request, view)
|
||||
|
||||
class PedimentoPagination(PageNumberPagination):
|
||||
|
||||
"""
|
||||
Paginación personalizada con parámetros flexibles
|
||||
- Si no se especifica page_size, devuelve todos los resultados (sin paginación)
|
||||
- Si se especifica page_size, usa paginación normal
|
||||
"""
|
||||
page_size = None # Sin paginación por defecto
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 1000 # Límite máximo de seguridad
|
||||
page_query_param = 'page'
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
"""
|
||||
Si no se especifica page_size en los parámetros, devolver None (sin paginación)
|
||||
Si se especifica, usar paginación normal
|
||||
"""
|
||||
# Verificar si se especificó page_size en la query
|
||||
if self.page_size_query_param not in request.query_params:
|
||||
# No hay page_size, devolver None para indicar "sin paginación"
|
||||
return None
|
||||
|
||||
# Hay page_size, usar paginación normal
|
||||
try:
|
||||
page_size = int(request.query_params[self.page_size_query_param])
|
||||
if page_size <= 0:
|
||||
return None
|
||||
# Establecer el page_size temporalmente para esta request
|
||||
self.page_size = min(page_size, self.max_page_size)
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
|
||||
return super().paginate_queryset(queryset, request, view)
|
||||
|
||||
# Create your views here.
|
||||
class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltradaMixin): # Pendiente de permisos de creacion
|
||||
"""
|
||||
ViewSet for Pedimento model.
|
||||
Soporta paginación, filtros y búsqueda.
|
||||
|
||||
Parámetros disponibles:
|
||||
- page: Número de página (solo si se especifica page_size)
|
||||
- page_size: Elementos por página (si NO se especifica, devuelve TODOS los resultados)
|
||||
- search: Búsqueda en pedimento, contribuyente, agente_aduanal
|
||||
- pedimento: Filtro por número de pedimento
|
||||
- existe_expediente: Filtro por expediente (True/False)
|
||||
- contribuyente: Filtro por contribuyente
|
||||
- curp_apoderado: Filtro por curp del apoderado
|
||||
- fecha_pago: Filtro por fecha de pago (YYYY-MM-DD)
|
||||
- patente: Filtro por patente
|
||||
- aduana: Filtro por aduana
|
||||
- tipo_operacion: Filtro por tipo de operación
|
||||
- clave_pedimento: Filtro por clave de pedimento
|
||||
- ordering: Ordenar por campo (ej: -created_at, pedimento)
|
||||
|
||||
Ejemplos:
|
||||
- /pedimentos/ → Devuelve TODOS los pedimentos
|
||||
- /pedimentos/?page_size=10 → Devuelve los primeros 10
|
||||
- /pedimentos/?page_size=10&page=2 → Devuelve los pedimentos 11-20
|
||||
- /pedimentos/?pedimento=12345678 → Filtra por número de pedimento
|
||||
- /pedimentos/?existe_expediente=true → Filtra por expediente existente
|
||||
- /pedimentos/?contribuyente=EMPRESA → Filtra por contribuyente
|
||||
- /pedimentos/?curp_apoderado=XXXX → Filtra por curp apoderado
|
||||
- /pedimentos/?fecha_pago=2025-07-18 → Filtra por fecha de pago
|
||||
"""
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
serializer_class = PedimentoSerializer
|
||||
pagination_class = PedimentoPagination
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
model = Pedimento
|
||||
|
||||
filterset_fields = ['patente', 'aduana', 'tipo_operacion', 'clave_pedimento', 'pedimento', 'existe_expediente', 'contribuyente', 'curp_apoderado', 'fecha_pago', 'pedimento_app']
|
||||
search_fields = ['pedimento', 'pedimento_app', 'agente_aduanal', 'clave_pedimento']
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_queryset_filtrado_por_organizacion() # Tambien filtra por importador
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""
|
||||
Asigna automáticamente la organización del usuario autenticado al crear un pedimento.
|
||||
"""
|
||||
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
|
||||
raise ValueError("Usuario no autenticado o sin organización")
|
||||
data = serializer.validated_data
|
||||
if not data.get('pedimento_app'):
|
||||
fecha_pago = data.get('fecha_pago')
|
||||
aduana = data.get('aduana')
|
||||
patente = data.get('patente')
|
||||
pedimento = data.get('pedimento')
|
||||
if fecha_pago and aduana and patente and pedimento:
|
||||
pedimento_app = f"{str(fecha_pago.year)[-2:]}-{str(aduana).zfill(2)[-2:]}-{str(patente).zfill(4)[-4:]}-{str(pedimento).zfill(7)[-7:]}"
|
||||
serializer.save(organizacion=self.request.user.organizacion, pedimento_app=pedimento_app)
|
||||
return
|
||||
|
||||
try:
|
||||
# Usar el nombre del servicio de Docker Compose en lugar de localhost
|
||||
response = requests.request('POST', f'{SERVICE_API_URL}/services/pedimento', params={},
|
||||
json={
|
||||
'estado': 1,
|
||||
'servicio': 3,
|
||||
'tipo_procesamiento': 2,
|
||||
'pedimento': str(serializer.instance.id),
|
||||
'organizacion': str(self.request.user.organizacion.id),
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
# Verificar si la respuesta fue exitosa
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Servicio FastAPI ejecutado exitosamente: {response.status_code}")
|
||||
print(f"📄 Respuesta: {response.json()}")
|
||||
elif response.status_code == 201:
|
||||
print(f"✅ Recurso creado exitosamente en FastAPI: {response.status_code}")
|
||||
print(f"📄 Respuesta: {response.json()}")
|
||||
else:
|
||||
print(f"⚠️ Servicio FastAPI respondió con error: {response.status_code}")
|
||||
print(f"📄 Respuesta: {response.text}")
|
||||
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
print(f"❌ No se pudo conectar al servicio FastAPI: {e}")
|
||||
print(f"🔧 Verifica que el servicio FastAPI esté corriendo en {SERVICE_API_URL}")
|
||||
except requests.exceptions.Timeout as e:
|
||||
print(f"⏰ Timeout al conectar con el servicio FastAPI: {e}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"🚨 Error de request al servicio FastAPI: {e}")
|
||||
except Exception as e:
|
||||
print(f"💥 Error inesperado al llamar al servicio FastAPI: {e}")
|
||||
|
||||
my_tags = ['Pedimentos']
|
||||
|
||||
class ViewSetTipoOperacion(LoggingMixin, viewsets.ModelViewSet):
|
||||
"""
|
||||
ViewSet for TipoOperacion model.
|
||||
"""
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
|
||||
queryset = TipoOperacion.objects.all()
|
||||
serializer_class = TipoOperacionSerializer
|
||||
pagination_class = CustomPagination
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['tipo']
|
||||
search_fields = ['tipo', 'descripcion']
|
||||
ordering_fields = ['tipo', 'descripcion']
|
||||
ordering = ['tipo']
|
||||
|
||||
my_tags = ['Tipos_Operacion']
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""
|
||||
Asigna automáticamente la organización del usuario autenticado al crear un tipo de operación.
|
||||
"""
|
||||
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
|
||||
raise ValueError("Usuario no autenticado o sin organización")
|
||||
# Solo el supoerusuario puede crear tipos de operación
|
||||
if not self.request.user.is_superuser:
|
||||
raise PermissionDenied("Solo los superusuarios pueden crear tipos de operación")
|
||||
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""
|
||||
Solo el superusuario puede actualizar tipos de operación.
|
||||
"""
|
||||
if not self.request.user.is_superuser:
|
||||
raise PermissionDenied("Solo los superusuarios pueden actualizar tipos de operación")
|
||||
|
||||
serializer.save()
|
||||
|
||||
class ViewSetProcesamientoPedimento(viewsets.ModelViewSet, ProcesosPorOrganizacionMixin):
|
||||
|
||||
"""
|
||||
ViewSet for ProcesamientoPedimento model.
|
||||
Soporta paginación, filtros y búsqueda.
|
||||
|
||||
Parámetros disponibles:
|
||||
- page: Número de página (solo si se especifica page_size)
|
||||
- page_size: Elementos por página (si NO se especifica, devuelve TODOS los resultados)
|
||||
- pedimento: Filtro por pedimento
|
||||
- estado: Filtro por estado
|
||||
- servicio: Filtro por servicio
|
||||
- tipo_procesamiento: Filtro por tipo de procesamiento
|
||||
- ordering: Ordenar por campo (ej: -created_at, -updated_at)
|
||||
|
||||
Ejemplos:
|
||||
- /procesamientopedimentos/ → Devuelve TODOS los procesamientos
|
||||
- /procesamientopedimentos/?page_size=5 → Devuelve los primeros 5
|
||||
"""
|
||||
permission_classes = [IsAuthenticated, IsSuperUser | IsSameOrganizationDeveloper ]
|
||||
serializer_class = ProcesamientoPedimentoSerializer
|
||||
pagination_class = CustomPagination
|
||||
model = ProcesamientoPedimento
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = {
|
||||
'pedimento': ['exact'],
|
||||
'pedimento__pedimento_app': ['exact', 'icontains'],
|
||||
'estado': ['exact'],
|
||||
'servicio': ['exact'],
|
||||
'tipo_procesamiento': ['exact'],
|
||||
}
|
||||
search_fields = ['pedimento__pedimento_app', 'pedimento__pedimento']
|
||||
ordering_fields = ['created_at', 'updated_at']
|
||||
ordering = ['-created_at']
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_queryset_filtrado_por_organizacion()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""
|
||||
Asigna siempre la organización al crear un procesamiento de pedimento.
|
||||
- Para superusuarios: requiere que la organización venga explícitamente en los datos validados.
|
||||
- Para usuarios normales: asigna la organización del usuario autenticado.
|
||||
"""
|
||||
user = self.request.user
|
||||
if not user.is_authenticated:
|
||||
raise ValueError("Usuario no autenticado")
|
||||
|
||||
# Si es superusuario, debe venir la organización en los datos validados
|
||||
if user.is_superuser:
|
||||
organizacion = serializer.validated_data.get('organizacion', None)
|
||||
if not organizacion:
|
||||
raise ValueError("El superusuario debe especificar una organización al crear el procesamiento de pedimento.")
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
# Para usuarios normales, asignar siempre la organización del usuario
|
||||
if not hasattr(user, 'organizacion') or not user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=user.organizacion)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""
|
||||
Permite actualizar un procesamiento de pedimento, pero solo si el usuario es superusuario o pertenece a la misma organización.
|
||||
"""
|
||||
if not self.request.user.is_authenticated:
|
||||
raise ValueError("Usuario no autenticado")
|
||||
|
||||
if self.request.user.is_superuser:
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
# Para usuarios normales, usar siempre su organización
|
||||
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
return
|
||||
|
||||
raise ValueError("Usuario no autenticado o sin permisos para actualizar ProcesamientoPedimento")
|
||||
|
||||
my_tags = ['Procesamientos_Pedimentos']
|
||||
|
||||
class ViewSetEDocument(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
"""
|
||||
ViewSet for EDocument model.
|
||||
"""
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
serializer_class = EDocumentSerializer
|
||||
pagination_class = CustomPagination
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['pedimento', 'numero_edocument', 'organizacion']
|
||||
search_fields = ['numero_edocument', 'descripcion', 'organizacion']
|
||||
ordering_fields = ['created_at', 'updated_at', 'numero_edocument']
|
||||
ordering = ['-created_at']
|
||||
model = EDocument
|
||||
my_tags = ['EDocuments']
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_queryset_filtrado_por_organizacion()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""
|
||||
Asigna automáticamente la organización del usuario autenticado al crear un EDocument.
|
||||
Para superusuarios, permite especificar una organización diferente.
|
||||
"""
|
||||
if not self.request.user.is_authenticated:
|
||||
raise ValueError("Usuario no autenticado")
|
||||
|
||||
# Si es superusuario y se especifica organizacion en los datos validados
|
||||
if self.request.user.is_superuser:
|
||||
# Permitir que el superusuario especifique la organización
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
# Para usuarios normales, usar siempre su organización
|
||||
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
return
|
||||
|
||||
raise ValueError("Usuario no autenticado o sin permisos para crear EDocument")
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""
|
||||
Permite actualizar un EDocument, pero solo si el usuario es superusuario o pertenece a la misma organización.
|
||||
"""
|
||||
if not self.request.user.is_authenticated:
|
||||
raise ValueError("Usuario no autenticado")
|
||||
|
||||
# Si es superusuario, permite actualizar sin restricciones
|
||||
if self.request.user.is_superuser:
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
# Para usuarios normales, usar siempre su organización
|
||||
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
|
||||
raise ValueError("Usuario no autenticado o sin permisos para actualizar EDocument")
|
||||
|
||||
class ViewSetCove(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
"""
|
||||
ViewSet for Cove model.
|
||||
"""
|
||||
permission_classes = [IsAuthenticated & (IsSuperUser |IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper )]
|
||||
serializer_class = CoveSerializer
|
||||
pagination_class = CustomPagination
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['pedimento', 'numero_cove', 'organizacion']
|
||||
search_fields = ['numero_cove', 'descripcion', 'organizacion']
|
||||
ordering_fields = ['created_at', 'updated_at', 'numero_cove']
|
||||
ordering = ['-created_at']
|
||||
model = Cove
|
||||
my_tags = ['Coves']
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_queryset_filtrado_por_organizacion()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""
|
||||
Asigna automáticamente la organización del usuario autenticado al crear un Cove.
|
||||
Para superusuarios, permite especificar una organización diferente.
|
||||
"""
|
||||
if not self.request.user.is_authenticated:
|
||||
raise ValueError("Usuario no autenticado")
|
||||
|
||||
# Si es superusuario y se especifica organizacion en los datos validados
|
||||
if self.request.user.is_superuser:
|
||||
# Permitir que el superusuario especifique la organización
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
if (
|
||||
self.request.user.groups.filter(name='developer').exists()
|
||||
or self.request.user.groups.filter(name='admin').exists()
|
||||
or self.request.user.groups.filter(name='user').exists()
|
||||
) and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
# Para usuarios normales, usar siempre su organización
|
||||
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
return
|
||||
|
||||
raise ValueError("Usuario no autenticado o sin permisos para crear Cove")
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""
|
||||
Permite actualizar un Cove, pero solo si el usuario es superusuario o pertenece a la misma organización.
|
||||
"""
|
||||
if not self.request.user.is_authenticated:
|
||||
raise ValueError("Usuario no autenticado")
|
||||
|
||||
# Si es superusuario, permite actualizar sin restricciones
|
||||
if self.request.user.is_superuser:
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user .groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
# Para usuarios normales, usar siempre su organización
|
||||
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
|
||||
class ImportadorViewSet(viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
"""
|
||||
ViewSet for Importador model.
|
||||
"""
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
serializer_class = ImportadorSerializer
|
||||
pagination_class = CustomPagination
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['rfc', 'nombre', 'organizacion']
|
||||
search_fields = ['rfc', 'nombre']
|
||||
ordering_fields = ['created_at', 'updated_at', 'rfc']
|
||||
ordering = ['-created_at']
|
||||
model = Importador
|
||||
|
||||
def get_queryset(self):
|
||||
return self.get_queryset_filtrado_por_organizacion()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
|
||||
raise ValueError("Usuario no autenticado o sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
|
||||
def perform_update(self, serializer):
|
||||
if not self.request.user.is_authenticated or not hasattr(self.request.user, 'organizacion'):
|
||||
raise ValueError("Usuario no autenticado o sin organización")
|
||||
|
||||
# Si es superusuario, permite actualizar sin restricciones
|
||||
if self.request.user.is_superuser:
|
||||
serializer.save()
|
||||
return
|
||||
|
||||
if (self.request.user.groups.filter(name='developer').exists() or self.request.user.groups.filter(name='admin').exists() or self.request.user.groups.filter(name='user').exists()) and self.request.user.groups.filter(name='Agente Aduanal').exists():
|
||||
# Para usuarios normales, usar siempre su organización
|
||||
if not hasattr(self.request.user, 'organizacion') or not self.request.user.organizacion:
|
||||
raise ValueError("Usuario sin organización")
|
||||
serializer.save(organizacion=self.request.user.organizacion)
|
||||
return
|
||||
|
||||
raise ValueError("Usuario no autenticado o sin permisos para actualizar Importador")
|
||||
|
||||
my_tags = ['Importadores']
|
||||
Reference in New Issue
Block a user