Coverage for modules\customs\models\__init__.py: 95%
78 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 16:58 +0800
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-06 16:58 +0800
1from django.db import models
2from decimal import Decimal
3from core.models import TenantScopedModel
6class CustomsEntry(TenantScopedModel):
7 """Customs declaration/entry for imports or exports."""
9 ENTRY_TYPES = [
10 ("IMPORT", "Import Declaration"),
11 ("EXPORT", "Export Declaration"),
12 ("TRANSIT", "Transit Declaration"),
13 ("TEMPORARY", "Temporary Admission"),
14 ]
16 STATUS_CHOICES = [
17 ("DRAFT", "Draft"),
18 ("SUBMITTED", "Submitted"),
19 ("UNDER_REVIEW", "Under Review"),
20 ("APPROVED", "Approved"),
21 ("REJECTED", "Rejected"),
22 ("CLEARED", "Cleared"),
23 ]
25 entry_number = models.CharField(max_length=64, unique=True, db_index=True)
26 entry_type = models.CharField(max_length=16, choices=ENTRY_TYPES)
27 status = models.CharField(max_length=16, choices=STATUS_CHOICES, default="DRAFT")
29 # Client info (re-use CRM Company)
30 client_name = models.CharField(max_length=255)
31 client_reference = models.CharField(max_length=128, blank=True, null=True)
33 # shipment details
34 bl_number = models.CharField(max_length=64, blank=True, null=True) # Bill of Lading
35 container_number = models.CharField(max_length=32, blank=True, null=True)
36 vessel_name = models.CharField(max_length=128, blank=True, null=True)
37 voyage_number = models.CharField(max_length=64, blank=True, null=True)
38 port_of_loading = models.CharField(max_length=128, blank=True, null=True)
39 port_of_discharge = models.CharField(max_length=128, blank=True, null=True)
40 eta = models.DateField(null=True, blank=True)
42 # goods description
43 goods_description = models.TextField(blank=True, null=True)
44 hs_code = models.CharField(max_length=16, blank=True, null=True) # Harmonized System code
45 number_of_packages = models.IntegerField(default=0)
46 gross_weight_kg = models.DecimalField(max_digits=12, decimal_places=2, default=Decimal("0.00"))
48 # values
49 fob_value = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0.00"))
50 freight_cost = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0.00"))
51 insurance_cost = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0.00"))
52 cif_value = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0.00")) # Cost, Insurance, Freight
53 duty_amount = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0.00"))
54 vat_amount = models.DecimalField(max_digits=14, decimal_places=2, default=Decimal("0.00"))
56 # dates
57 entry_date = models.DateField(null=True, blank=True)
58 clearance_date = models.DateField(null=True, blank=True)
59 due_date = models.DateField(null=True, blank=True)
61 # assigned broker
62 assigned_broker = models.CharField(max_length=128, blank=True, null=True)
64 notes = models.TextField(blank=True, null=True)
66 class Meta:
67 ordering = ["-created_at"]
68 verbose_name_plural = "Customs Entries"
70 def __str__(self):
71 return f"{self.entry_number} ({self.get_entry_type_display()})"
74class ComplianceDocument(TenantScopedModel):
75 """Compliance and supporting documents for customs."""
77 DOCUMENT_TYPES = [
78 ("CERTIFICATE", "Certificate of Origin"),
79 ("INVOICE", "Commercial Invoice"),
80 ("PACKING_LIST", "Packing List"),
81 ("BILL_OF_LADING", "Bill of Lading"),
82 ("DANGEROUS_GOODS", "Dangerous Goods Declaration"),
83 ("PHYTOSANITARY", "Phytosanitary Certificate"),
84 ("FUMIGATION", "Fumigation Certificate"),
85 ("OTHER", "Other"),
86 ]
88 entry = models.ForeignKey(
89 CustomsEntry, on_delete=models.CASCADE, related_name="documents"
90 )
91 document_type = models.CharField(max_length=24, choices=DOCUMENT_TYPES)
92 document_number = models.CharField(max_length=64, blank=True, null=True)
93 issue_date = models.DateField(null=True, blank=True)
94 expiry_date = models.DateField(null=True, blank=True)
95 file_url = models.URLField(blank=True, null=True)
96 notes = models.TextField(blank=True, null=True)
98 class Meta:
99 ordering = ["-created_at"]
101 def __str__(self):
102 return f"{self.get_document_type_display()} - {self.entry}"
105class DutyRate(TenantScopedModel):
106 """Predefined duty rates by HS code."""
108 hs_code = models.CharField(max_length=16, unique=True, db_index=True)
109 description = models.TextField(blank=True, null=True)
110 duty_rate_percent = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
111 vat_rate_percent = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
112 additional_duty_percent = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
113 country_of_origin = models.CharField(max_length=64, blank=True, null=True)
114 effective_from = models.DateField(null=True, blank=True)
115 effective_to = models.DateField(null=True, blank=True)
116 is_active = models.BooleanField(default=True)
118 class Meta:
119 ordering = ["hs_code"]
121 def __str__(self):
122 return f"HS {self.hs_code} - {self.duty_rate_percent}%"
125class BrokerLicense(TenantScopedModel):
126 """Customs broker licenses and certifications."""
128 broker_name = models.CharField(max_length=128)
129 license_number = models.CharField(max_length=64, unique=True)
130 issuing_authority = models.CharField(max_length=128)
131 license_type = models.CharField(max_length=64, blank=True, null=True)
132 issue_date = models.DateField()
133 expiry_date = models.DateField()
134 is_active = models.BooleanField(default=True)
135 notes = models.TextField(blank=True, null=True)
137 class Meta:
138 ordering = ["expiry_date"]
140 def __str__(self):
141 return f"{self.broker_name} - {self.license_number}"