2026-08-09 06:12:43 +00:00
|
|
|
from django.db import models
|
|
|
|
|
from django.contrib.auth.models import User
|
|
|
|
|
|
2026-08-18 06:00:22 +00:00
|
|
|
class Category(models.Model):
|
|
|
|
|
name = models.CharField(max_length=100, unique=True)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.name
|
|
|
|
|
|
2026-08-09 06:12:43 +00:00
|
|
|
class SupplierProfile(models.Model):
|
2026-08-12 07:51:11 +00:00
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
('unverified', 'Unverified'),
|
|
|
|
|
('phone_verified', 'Phone Verified'),
|
|
|
|
|
('email_verified', 'Email Verified'),
|
|
|
|
|
('documents_submitted', 'Documents Submitted'),
|
|
|
|
|
('pending_approval', 'Pending Approval'),
|
|
|
|
|
('approved', 'Approved'),
|
|
|
|
|
('rejected', 'Rejected'),
|
|
|
|
|
('suspended', 'Suspended'),
|
|
|
|
|
]
|
2026-08-18 06:00:22 +00:00
|
|
|
BUSINESS_TYPE_CHOICES = [
|
|
|
|
|
('registered_company', 'Registered Company'),
|
|
|
|
|
('self_help_group', 'Self Help Group'),
|
|
|
|
|
('individual_maker', 'Individual Maker'),
|
|
|
|
|
]
|
2026-08-09 06:12:43 +00:00
|
|
|
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
|
|
|
|
|
phone = models.CharField(max_length=20, blank=True, null=True)
|
|
|
|
|
phone_verified = models.BooleanField(default=False)
|
|
|
|
|
email_verified = models.BooleanField(default=False)
|
2026-08-12 07:51:11 +00:00
|
|
|
gstin = models.CharField(max_length=15, blank=True, null=True)
|
2026-08-09 06:12:43 +00:00
|
|
|
is_gstin_verified = models.BooleanField(default=False)
|
2026-08-18 06:00:22 +00:00
|
|
|
business_type = models.CharField(max_length=50, choices=BUSINESS_TYPE_CHOICES, blank=True, null=True)
|
|
|
|
|
store_slug = models.SlugField(max_length=255, unique=True, blank=True, null=True)
|
|
|
|
|
categories = models.ManyToManyField(Category, blank=True, related_name='suppliers')
|
2026-08-12 07:51:11 +00:00
|
|
|
aadhar_file = models.CharField(max_length=255, blank=True, null=True) # retaining old files or S3 keys
|
2026-08-09 06:12:43 +00:00
|
|
|
pan_file = models.CharField(max_length=255, blank=True, null=True)
|
2026-08-12 07:51:11 +00:00
|
|
|
aadhar_s3_key = models.CharField(max_length=255, blank=True, null=True)
|
|
|
|
|
pan_s3_key = models.CharField(max_length=255, blank=True, null=True)
|
2026-08-09 06:12:43 +00:00
|
|
|
store_name = models.CharField(max_length=255, blank=True, null=True)
|
2026-08-12 07:51:11 +00:00
|
|
|
store_logo = models.TextField(blank=True, null=True)
|
|
|
|
|
logo_s3_key = models.CharField(max_length=255, blank=True, null=True)
|
2026-08-09 06:12:43 +00:00
|
|
|
business_bio = models.TextField(blank=True, null=True)
|
|
|
|
|
street = models.CharField(max_length=255, blank=True, null=True)
|
|
|
|
|
city = models.CharField(max_length=100, blank=True, null=True)
|
|
|
|
|
state = models.CharField(max_length=100, blank=True, null=True)
|
|
|
|
|
pincode = models.CharField(max_length=20, blank=True, null=True)
|
|
|
|
|
latitude = models.FloatField(default=12.9716)
|
|
|
|
|
longitude = models.FloatField(default=77.5946)
|
2026-08-12 07:51:11 +00:00
|
|
|
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='unverified')
|
|
|
|
|
submitted_at = models.DateTimeField(blank=True, null=True)
|
|
|
|
|
approved_at = models.DateTimeField(blank=True, null=True)
|
|
|
|
|
approved_by = models.CharField(max_length=150, blank=True, null=True)
|
|
|
|
|
rejected_reason = models.TextField(blank=True, null=True)
|
2026-08-09 06:12:43 +00:00
|
|
|
is_profile_complete = models.BooleanField(default=False)
|
|
|
|
|
|
2026-08-12 07:51:11 +00:00
|
|
|
def __block__(self):
|
|
|
|
|
return
|
|
|
|
|
|
2026-08-09 06:12:43 +00:00
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.user.username}'s Profile"
|
|
|
|
|
|
2026-08-28 16:42:59 +00:00
|
|
|
class SellerKYC(models.Model):
|
|
|
|
|
supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name='kyc')
|
|
|
|
|
aadhaar_number = models.CharField(max_length=20, blank=True, null=True)
|
|
|
|
|
name = models.CharField(max_length=255, blank=True, null=True)
|
|
|
|
|
dob = models.CharField(max_length=50, blank=True, null=True)
|
|
|
|
|
gender = models.CharField(max_length=10, blank=True, null=True)
|
|
|
|
|
address = models.TextField(blank=True, null=True)
|
|
|
|
|
face_base64 = models.TextField(blank=True, null=True)
|
|
|
|
|
verification_response = models.JSONField(default=dict, blank=True, null=True)
|
2026-08-28 17:00:21 +00:00
|
|
|
|
|
|
|
|
pan_number = models.CharField(max_length=20, blank=True, null=True)
|
|
|
|
|
pan_full_name = models.CharField(max_length=255, blank=True, null=True)
|
|
|
|
|
pan_dob = models.CharField(max_length=50, blank=True, null=True)
|
|
|
|
|
pan_status = models.CharField(max_length=50, blank=True, null=True)
|
|
|
|
|
pan_aadhaar_linked = models.BooleanField(default=False)
|
|
|
|
|
pan_verification_response = models.JSONField(default=dict, blank=True, null=True)
|
|
|
|
|
|
2026-08-28 16:42:59 +00:00
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.supplier.username} - KYC"
|
|
|
|
|
|
2026-08-12 07:51:11 +00:00
|
|
|
class OTPRecord(models.Model):
|
|
|
|
|
TYPE_CHOICES = [
|
|
|
|
|
('phone', 'Phone'),
|
|
|
|
|
('email', 'Email'),
|
|
|
|
|
('password_reset', 'Password Reset'),
|
|
|
|
|
]
|
|
|
|
|
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='otp_records')
|
|
|
|
|
type = models.CharField(max_length=20, choices=TYPE_CHOICES)
|
|
|
|
|
otp_hash = models.CharField(max_length=64) # SHA-256 hash
|
|
|
|
|
expires_at = models.DateTimeField()
|
|
|
|
|
attempts = models.IntegerField(default=0)
|
|
|
|
|
used = models.BooleanField(default=False)
|
|
|
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.user.username} - {self.type} OTP"
|
|
|
|
|
|
|
|
|
|
class BulkUploadLog(models.Model):
|
|
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
('parsing', 'Parsing'),
|
|
|
|
|
('validating', 'Validating'),
|
|
|
|
|
('completed', 'Completed'),
|
|
|
|
|
('failed', 'Failed'),
|
|
|
|
|
]
|
2026-08-12 13:58:24 +00:00
|
|
|
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='bulk_upload_logs', db_constraint=False)
|
2026-08-12 07:51:11 +00:00
|
|
|
log_id = models.CharField(max_length=50, unique=True)
|
|
|
|
|
s3_key = models.CharField(max_length=255)
|
|
|
|
|
total_rows = models.IntegerField(default=0)
|
|
|
|
|
rows_created = models.IntegerField(default=0)
|
|
|
|
|
rows_updated = models.IntegerField(default=0)
|
|
|
|
|
rows_skipped = models.IntegerField(default=0)
|
|
|
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='parsing')
|
|
|
|
|
error_summary = models.TextField(blank=True, null=True) # JSON stored as string
|
|
|
|
|
uploaded_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
|
completed_at = models.DateTimeField(blank=True, null=True)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.log_id
|
|
|
|
|
|
|
|
|
|
|
2026-08-09 06:12:43 +00:00
|
|
|
class Product(models.Model):
|
2026-08-12 13:58:24 +00:00
|
|
|
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products', db_constraint=False)
|
2026-08-09 06:12:43 +00:00
|
|
|
title = models.CharField(max_length=255)
|
|
|
|
|
category = models.CharField(max_length=100)
|
|
|
|
|
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
|
|
|
stock = models.IntegerField(default=0)
|
|
|
|
|
sku = models.CharField(max_length=100, unique=True)
|
|
|
|
|
image = models.TextField(blank=True, null=True) # Can store URL or base64
|
2026-08-16 09:27:19 +00:00
|
|
|
status = models.CharField(max_length=50, default='pending')
|
|
|
|
|
is_active = models.BooleanField(default=True)
|
2026-08-18 06:52:55 +00:00
|
|
|
|
|
|
|
|
# New heritage fields from UI designs
|
|
|
|
|
description = models.TextField(blank=True, null=True)
|
|
|
|
|
is_gi_tagged = models.BooleanField(default=False)
|
|
|
|
|
additional_images = models.JSONField(default=list, blank=True, null=True)
|
|
|
|
|
video_url = models.URLField(blank=True, null=True)
|
|
|
|
|
view_360_url = models.URLField(blank=True, null=True)
|
|
|
|
|
compare_at_price = models.DecimalField(max_digits=10, decimal_places=2, blank=True, null=True)
|
|
|
|
|
track_inventory = models.BooleanField(default=True)
|
|
|
|
|
shipping_profile = models.CharField(max_length=100, default='Standard Fragile')
|
|
|
|
|
processing_days = models.IntegerField(default=3)
|
|
|
|
|
package_weight = models.FloatField(default=1.0)
|
2026-08-09 06:12:43 +00:00
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.title
|
|
|
|
|
|
|
|
|
|
class Order(models.Model):
|
|
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
('Pending Acceptance', 'Pending Acceptance'),
|
|
|
|
|
('Ready to Ship', 'Ready to Ship'),
|
|
|
|
|
('Shipped', 'Shipped'),
|
|
|
|
|
('Delivered', 'Delivered'),
|
|
|
|
|
('Cancelled', 'Cancelled'),
|
|
|
|
|
('Rejected', 'Rejected'),
|
|
|
|
|
]
|
2026-08-12 13:58:24 +00:00
|
|
|
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='orders', db_constraint=False)
|
2026-08-09 06:12:43 +00:00
|
|
|
order_id = models.CharField(max_length=50, unique=True)
|
|
|
|
|
date = models.DateField()
|
|
|
|
|
item = models.CharField(max_length=255)
|
|
|
|
|
quantity = models.IntegerField(default=1)
|
|
|
|
|
customer = models.CharField(max_length=255)
|
|
|
|
|
total = models.DecimalField(max_digits=10, decimal_places=2)
|
|
|
|
|
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Pending Acceptance')
|
|
|
|
|
carrier = models.CharField(max_length=100, default='Pending')
|
|
|
|
|
tracking = models.CharField(max_length=100, default='Pending')
|
|
|
|
|
eta = models.CharField(max_length=50, default='N/A')
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.order_id
|
|
|
|
|
|
|
|
|
|
class ReturnRequest(models.Model):
|
|
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
('Pending Approval', 'Pending Approval'),
|
|
|
|
|
('Approved', 'Approved'),
|
|
|
|
|
('Rejected', 'Rejected'),
|
|
|
|
|
('In Transit', 'In Transit'),
|
|
|
|
|
]
|
2026-08-12 13:58:24 +00:00
|
|
|
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='returns', db_constraint=False)
|
2026-08-09 06:12:43 +00:00
|
|
|
return_id = models.CharField(max_length=50, unique=True)
|
|
|
|
|
order_id = models.CharField(max_length=50)
|
|
|
|
|
customer = models.CharField(max_length=255)
|
|
|
|
|
item = models.CharField(max_length=255)
|
|
|
|
|
reason = models.TextField()
|
|
|
|
|
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Pending Approval')
|
|
|
|
|
image = models.TextField(blank=True, null=True)
|
|
|
|
|
returning_tracking = models.CharField(max_length=100, blank=True, null=True)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.return_id
|
|
|
|
|
|
|
|
|
|
class Wallet(models.Model):
|
2026-08-12 13:58:24 +00:00
|
|
|
supplier = models.OneToOneField(User, on_delete=models.CASCADE, related_name='wallet', db_constraint=False)
|
2026-08-09 06:12:43 +00:00
|
|
|
outstanding = models.DecimalField(max_digits=12, decimal_places=2, default=0.00)
|
|
|
|
|
withdrawn = models.DecimalField(max_digits=12, decimal_places=2, default=0.00)
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"{self.supplier.username}'s Wallet"
|
|
|
|
|
|
|
|
|
|
class WalletTransaction(models.Model):
|
|
|
|
|
STATUS_CHOICES = [
|
|
|
|
|
('Transferred', 'Transferred'),
|
|
|
|
|
('Pending', 'Pending'),
|
|
|
|
|
]
|
|
|
|
|
wallet = models.ForeignKey(Wallet, on_delete=models.CASCADE, related_name='transactions')
|
|
|
|
|
tx_id = models.CharField(max_length=50, unique=True)
|
|
|
|
|
date = models.DateField()
|
|
|
|
|
amount = models.DecimalField(max_digits=12, decimal_places=2)
|
|
|
|
|
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='Transferred')
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return self.tx_id
|