feat: add backend models, serializers, views, and frontend page for sellers
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 3m17s
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 3m17s
This commit is contained in:
parent
c3a887b192
commit
f7e90ebaeb
4 changed files with 134 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue