import uuid from django.db import models class VendorConfig(models.Model): """ Dynamic configuration for verification vendors stored in database. Allows toggling active state, priority adjustments, and credential updates via Django admin. """ name = models.CharField(max_length=50, unique=True, help_text="Unique vendor identifier, e.g. codeplus") display_name = models.CharField(max_length=100, help_text="Human-readable name") is_active = models.BooleanField(default=True) priority = models.PositiveIntegerField( default=10, help_text="Waterfall execution order (lower numbers execute first)", ) supported_documents = models.JSONField( default=list, help_text="List of supported doc types: ['pan', 'aadhaar', 'driving_license', 'voter_id', ...]", ) base_url = models.CharField(max_length=255, blank=True, null=True) api_key = models.CharField(max_length=255, blank=True, null=True) extra_credentials = models.JSONField(default=dict, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["priority", "name"] verbose_name = "Vendor Configuration" verbose_name_plural = "Vendor Configurations" def __str__(self): return f"{self.display_name} ({self.name}) - Priority: {self.priority} - {'Active' if self.is_active else 'Disabled'}" class VerificationTransaction(models.Model): """ Master record of every document verification request processed by the engine. """ STATUS_CHOICES = ( ("PENDING", "Pending"), ("SUCCESS", "Success"), ("FAILED", "Failed"), ("ERROR", "Error"), ) reference_id = models.UUIDField(default=uuid.uuid4, unique=True, editable=False, db_index=True) document_type = models.CharField(max_length=50, db_index=True) status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING", db_index=True) successful_vendor = models.CharField(max_length=50, blank=True, null=True) input_payload = models.JSONField(default=dict, blank=True) final_data = models.JSONField(default=dict, blank=True) response_payload = models.JSONField(default=dict, blank=True) attempts_count = models.PositiveIntegerField(default=0) total_latency_ms = models.FloatField(default=0.0) created_at = models.DateTimeField(auto_now_add=True, db_index=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-created_at"] verbose_name = "Verification Transaction" verbose_name_plural = "Verification Transactions" def __str__(self): return f"[{self.status}] {self.document_type} - {self.reference_id} ({self.successful_vendor or 'none'})" class VendorAttemptLog(models.Model): """ Audit log of each individual vendor attempt in the waterfall cascade. Allows inspection of which vendor failed, why, latency, and full payload details. """ transaction = models.ForeignKey( VerificationTransaction, related_name="attempts", on_delete=models.CASCADE, ) attempt_number = models.PositiveIntegerField() vendor_name = models.CharField(max_length=50, db_index=True) status = models.CharField(max_length=20) is_success = models.BooleanField(default=False) data = models.JSONField(default=dict, blank=True) raw_response = models.JSONField(null=True, blank=True) error_code = models.CharField(max_length=100, blank=True, null=True) error_message = models.TextField(blank=True, null=True) latency_ms = models.FloatField(default=0.0) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["attempt_number"] verbose_name = "Vendor Attempt Log" verbose_name_plural = "Vendor Attempt Logs" def __str__(self): return f"Tx: {self.transaction.reference_id} | #{self.attempt_number} {self.vendor_name} -> {self.status}" class PANDetails(models.Model): """ Dedicated table storing granular fields from Detailed PAN verification for analysis, reporting, and KYC intelligence. """ transaction = models.ForeignKey( VerificationTransaction, related_name="pan_details_record", on_delete=models.SET_NULL, null=True, blank=True, help_text="Associated verification transaction", ) document_type = models.CharField(max_length=20, default="PAN", db_index=True) document_id = models.CharField(max_length=20, db_index=True, help_text="10-digit PAN Number (e.g. AVEPV5707G)") name = models.CharField(max_length=255, db_index=True, help_text="Full Name on PAN card") first_name = models.CharField(max_length=150, blank=True, null=True) last_name = models.CharField(max_length=150, blank=True, null=True) category = models.CharField(max_length=10, blank=True, null=True, help_text="PAN holder category (e.g. P for Person/Individual)") date_of_birth = models.CharField(max_length=50, blank=True, null=True, help_text="YYYY-MM-DD or DD/MM/YYYY") gender = models.CharField(max_length=20, blank=True, null=True) masked_aadhaar_number = models.CharField(max_length=30, blank=True, null=True, help_text="e.g. XXXXXXXX5139") aadhaar_linked = models.BooleanField(default=False, help_text="Whether Aadhaar is seeded/linked with PAN") taxpayer_profile = models.BooleanField(default=False, help_text="Active taxpayer profile flag") # Contact Info email = models.CharField(max_length=255, blank=True, null=True) phone = models.CharField(max_length=50, blank=True, null=True) # Address breakdown columns for regional & geographical analytics address_line_1 = models.CharField(max_length=255, blank=True, null=True) street = models.CharField(max_length=255, blank=True, null=True) city = models.CharField(max_length=150, blank=True, null=True, db_index=True) state = models.CharField(max_length=100, blank=True, null=True, db_index=True) pincode = models.CharField(max_length=20, blank=True, null=True, db_index=True) address_data = models.JSONField(default=dict, blank=True, null=True, help_text="Full structured address object") # API tracking & Complete JSON payloads for future data science & audit request_id = models.CharField(max_length=100, blank=True, null=True, db_index=True) vendor_txn_id = models.CharField(max_length=100, blank=True, null=True, db_index=True) full_response = models.JSONField(default=dict, blank=True, null=True, help_text="Entire uncompressed response payload from vendor") created_at = models.DateTimeField(auto_now_add=True, db_index=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "pan_details" ordering = ["-created_at"] verbose_name = "PAN Detail" verbose_name_plural = "PAN Details" def __str__(self): return f"{self.document_id} - {self.name} ({self.city or 'Unknown'}, {self.state or 'Unknown'})" class SellerBankVerification(models.Model): """ Dedicated bank verification record for seller onboarding and settlements, keyed by seller_id as primary key. """ seller_id = models.CharField(max_length=100, primary_key=True, help_text="Unique Seller / Supplier ID") transaction = models.ForeignKey( VerificationTransaction, related_name="seller_bank_verifications", on_delete=models.SET_NULL, null=True, blank=True, help_text="Associated verification transaction", ) account_number = models.CharField(max_length=50) ifsc = models.CharField(max_length=20) account_ifsc = models.CharField(max_length=20, blank=True, null=True) beneficiary_name = models.CharField(max_length=255, blank=True, null=True) is_valid = models.BooleanField(default=False) account_exists = models.BooleanField(default=False) failure_reason = models.TextField(blank=True, null=True) npci_error_code = models.CharField(max_length=100, blank=True, null=True) tranx_id = models.CharField(max_length=100, blank=True, null=True, db_index=True) timestamp = models.CharField(max_length=100, blank=True, null=True) user_consent = models.CharField(max_length=50, default="true") vendor_name = models.CharField(max_length=50, default="codeplus") status = models.CharField(max_length=50, default="PENDING") latency_ms = models.FloatField(default=0.0) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = "seller_bank_verification" verbose_name = "Seller Bank Verification" verbose_name_plural = "Seller Bank Verifications" def __str__(self): return f"Seller {self.seller_id} Bank: {self.beneficiary_name or self.account_number} ({'Valid' if self.is_valid else 'Invalid'})"