feat: add backend models, serializers, views, and frontend page for sellers
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 3m17s

This commit is contained in:
vickytechkey 2026-11-10 19:25:31 +05:30
parent c3a887b192
commit f7e90ebaeb
4 changed files with 134 additions and 0 deletions

View file

@ -42,6 +42,11 @@ class SupplierProfile(models.Model):
bank_name = models.CharField(max_length=255, blank=True, null=True)
is_bank_verified = models.BooleanField(default=False)
tax_mode = models.CharField(max_length=20, default='gstin')
gi_seller_type = models.CharField(max_length=50, default='non_gi')
is_exclusive_gi = models.BooleanField(default=False)
gi_certificate_number = models.CharField(max_length=100, blank=True, null=True)
guild_status = models.CharField(max_length=255, blank=True, null=True)
gi_registry_verified = models.BooleanField(default=False)
class Meta:
managed = False

View file

@ -191,6 +191,11 @@ class SupplierProfileDetailSerializer(serializers.ModelSerializer):
"bank_name",
"is_bank_verified",
"tax_mode",
"gi_seller_type",
"is_exclusive_gi",
"gi_certificate_number",
"guild_status",
"gi_registry_verified",
"date_joined",
"metrics",
"products",

View file

@ -20,6 +20,96 @@ from .serializers import (
BulkUploadLogSerializer,
SellerOrderSerializer,
)
import logging
logger = logging.getLogger(__name__)
def sync_seller_gi_profile(pk=None, product=None):
"""
Synchronizes the seller's GI accreditation in seller_db (api_supplierprofile).
By default, a seller is categorized under Non-GI Craft ('non_gi').
If the seller applied for any GI tag and it has been approved through the CRM,
their profile is updated to 'exclusive' (all active products are GI-certified)
or 'mixed' (carries certified GI along with general crafts).
"""
try:
seller = None
if pk:
seller = SupplierProfile.objects.filter(models.Q(pk=pk) | models.Q(id=pk) | models.Q(user_id=pk)).first()
if not seller and product and hasattr(product, 'supplier_id'):
seller = SupplierProfile.objects.filter(
models.Q(pk=product.supplier_id) | models.Q(user_id=product.supplier_id)
).first()
if not seller:
return
approved_prods = Product.objects.using("product_db").filter(
models.Q(supplier_id=seller.id) | models.Q(supplier_id=seller.user_id),
status="approved",
is_active=True
)
total_approved = approved_prods.count()
gi_approved = approved_prods.filter(is_gi_tagged=True)
gi_count = gi_approved.count()
if gi_count > 0:
first_gi = gi_approved.first()
gi_name = (
getattr(first_gi, "gi_tag_name", None)
or getattr(first_gi, "statutory_certifications", None)
or "Govt. of India GI Registry Verified"
)
if gi_count == total_approved and total_approved > 0:
seller.gi_seller_type = "exclusive"
seller.is_exclusive_gi = True
else:
seller.gi_seller_type = "mixed"
seller.is_exclusive_gi = False
seller.gi_registry_verified = True
if not getattr(seller, "gi_certificate_number", None):
seller.gi_certificate_number = gi_name
if not getattr(seller, "guild_status", None):
seller.guild_status = getattr(first_gi, "geographical_origin", None) or "Certified GI Artisan Maker"
else:
# Default to Non-GI Craft
seller.gi_seller_type = "non_gi"
seller.is_exclusive_gi = False
seller.gi_registry_verified = False
try:
seller.save()
except Exception as save_err:
logger.warning("Standard seller save failed, ensuring columns: %s", save_err)
with connections["seller_db"].cursor() as cursor:
cursor.execute("""
ALTER TABLE api_supplierprofile
ADD COLUMN IF NOT EXISTS gi_seller_type VARCHAR(50) DEFAULT 'non_gi',
ADD COLUMN IF NOT EXISTS is_exclusive_gi BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS gi_certificate_number VARCHAR(100),
ADD COLUMN IF NOT EXISTS guild_status VARCHAR(255),
ADD COLUMN IF NOT EXISTS gi_registry_verified BOOLEAN DEFAULT FALSE;
""")
cursor.execute("""
UPDATE api_supplierprofile
SET gi_seller_type = %s,
is_exclusive_gi = %s,
gi_certificate_number = %s,
guild_status = %s,
gi_registry_verified = %s
WHERE id = %s;
""", [
seller.gi_seller_type,
seller.is_exclusive_gi,
seller.gi_certificate_number,
seller.guild_status,
seller.gi_registry_verified,
seller.id
])
except Exception as e:
logger.error("Error syncing seller GI accreditation profile: %s", e)
class SellerListView(APIView):
@ -104,6 +194,11 @@ class SellerDetailView(APIView):
"account_number",
"ifsc_code",
"bank_name",
"gi_seller_type",
"is_exclusive_gi",
"gi_certificate_number",
"guild_status",
"gi_registry_verified",
]
for field in allowed_fields:
if field in request.data:
@ -304,6 +399,9 @@ class SellerProductApproveView(APIView):
product.rejection_reason = None
product.save(using="product_db")
# Automatically update seller GI accreditation profile based on approved product catalog
sync_seller_gi_profile(pk=pk, product=product)
return Response({
"message": f"Product '{product.title}' has been approved and published to website.",
"product": ProductSerializer(product).data
@ -324,6 +422,9 @@ class SellerProductRejectView(APIView):
product.rejection_reason = reason
product.save(using="product_db")
# Synchronize GI status in case all approved GI products were revoked
sync_seller_gi_profile(pk=pk, product=product)
return Response({
"message": f"Product '{product.title}' was rejected.",
"product": ProductSerializer(product).data,
@ -377,6 +478,8 @@ class SellerProductDetailView(APIView):
setattr(product, field, request.data[field])
product.save(using="product_db")
sync_seller_gi_profile(pk=pk, product=product)
return Response({
"message": f"Product '{product.title}' content updated successfully.",
"product": ProductSerializer(product).data
@ -398,6 +501,8 @@ class SellerProductToggleView(APIView):
product.status = request.data["status"]
product.save(using="product_db")
sync_seller_gi_profile(pk=pk, product=product)
return Response({
"message": f"Product '{product.title}' updated.",
"product": ProductSerializer(product).data

View file

@ -206,6 +206,14 @@
{seller.status}
</Pill>
{#if seller.gi_seller_type === 'exclusive'}
<Pill tone="moss" dot>★ Exclusive GI Maker</Pill>
{:else if seller.gi_seller_type === 'mixed'}
<Pill tone="clay" dot>Mixed GI Catalog</Pill>
{:else}
<Pill tone="slate">Non-GI Craft</Pill>
{/if}
{#if seller.status === 'pending' || seller.status === 'pending_approval'}
<form method="POST" action="?/approve" use:enhance>
<button type="submit" class="v2-btn v2-btn-primary" style="background:var(--v2-moss);border-color:var(--v2-moss);">
@ -387,6 +395,17 @@
<dt class="v2-muted">Business Type</dt>
<dd>{seller.business_type || 'individual'}</dd>
<dt class="v2-muted">GI Accreditation</dt>
<dd>
{#if seller.gi_seller_type === 'exclusive'}
<span style="color:var(--v2-moss);font-weight:600;">★ Exclusive GI Only ({seller.gi_certificate_number || 'Registry Verified'})</span>
{:else if seller.gi_seller_type === 'mixed'}
<span style="color:var(--v2-clay);font-weight:600;">Mixed GI & Craft Catalog ({seller.gi_certificate_number || 'Registry Verified'})</span>
{:else}
<span style="color:var(--v2-slate);">Non-GI Craft (Default)</span>
{/if}
</dd>
<dt class="v2-muted">Tax Mode</dt>
<dd>{seller.tax_mode || 'gstin'}</dd>