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

1from django.db import models 

2from decimal import Decimal 

3from core.models import TenantScopedModel 

4 

5 

6class CustomsEntry(TenantScopedModel): 

7 """Customs declaration/entry for imports or exports.""" 

8 

9 ENTRY_TYPES = [ 

10 ("IMPORT", "Import Declaration"), 

11 ("EXPORT", "Export Declaration"), 

12 ("TRANSIT", "Transit Declaration"), 

13 ("TEMPORARY", "Temporary Admission"), 

14 ] 

15 

16 STATUS_CHOICES = [ 

17 ("DRAFT", "Draft"), 

18 ("SUBMITTED", "Submitted"), 

19 ("UNDER_REVIEW", "Under Review"), 

20 ("APPROVED", "Approved"), 

21 ("REJECTED", "Rejected"), 

22 ("CLEARED", "Cleared"), 

23 ] 

24 

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

28 

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) 

32 

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) 

41 

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

47 

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

55 

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) 

60 

61 # assigned broker 

62 assigned_broker = models.CharField(max_length=128, blank=True, null=True) 

63 

64 notes = models.TextField(blank=True, null=True) 

65 

66 class Meta: 

67 ordering = ["-created_at"] 

68 verbose_name_plural = "Customs Entries" 

69 

70 def __str__(self): 

71 return f"{self.entry_number} ({self.get_entry_type_display()})" 

72 

73 

74class ComplianceDocument(TenantScopedModel): 

75 """Compliance and supporting documents for customs.""" 

76 

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 ] 

87 

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) 

97 

98 class Meta: 

99 ordering = ["-created_at"] 

100 

101 def __str__(self): 

102 return f"{self.get_document_type_display()} - {self.entry}" 

103 

104 

105class DutyRate(TenantScopedModel): 

106 """Predefined duty rates by HS code.""" 

107 

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) 

117 

118 class Meta: 

119 ordering = ["hs_code"] 

120 

121 def __str__(self): 

122 return f"HS {self.hs_code} - {self.duty_rate_percent}%" 

123 

124 

125class BrokerLicense(TenantScopedModel): 

126 """Customs broker licenses and certifications.""" 

127 

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) 

136 

137 class Meta: 

138 ordering = ["expiry_date"] 

139 

140 def __str__(self): 

141 return f"{self.broker_name} - {self.license_number}"