From f7e90ebaeba5d7aec1eebb272e7d1c4acb3d3570 Mon Sep 17 00:00:00 2001 From: vickytechkey Date: Tue, 10 Nov 2026 19:25:31 +0530 Subject: [PATCH] feat: add backend models, serializers, views, and frontend page for sellers --- backend/sellers/models.py | 5 + backend/sellers/serializers.py | 5 + backend/sellers/views.py | 105 ++++++++++++++++++ .../routes/(app)/sellers/[id]/+page.svelte | 19 ++++ 4 files changed, 134 insertions(+) diff --git a/backend/sellers/models.py b/backend/sellers/models.py index 65ac8042..72ed8624 100644 --- a/backend/sellers/models.py +++ b/backend/sellers/models.py @@ -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 diff --git a/backend/sellers/serializers.py b/backend/sellers/serializers.py index 63cc23f0..bea60dee 100644 --- a/backend/sellers/serializers.py +++ b/backend/sellers/serializers.py @@ -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", diff --git a/backend/sellers/views.py b/backend/sellers/views.py index 41be93a7..b5e3df7c 100644 --- a/backend/sellers/views.py +++ b/backend/sellers/views.py @@ -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 diff --git a/frontend/src/routes/(app)/sellers/[id]/+page.svelte b/frontend/src/routes/(app)/sellers/[id]/+page.svelte index a1053db5..d97896c2 100644 --- a/frontend/src/routes/(app)/sellers/[id]/+page.svelte +++ b/frontend/src/routes/(app)/sellers/[id]/+page.svelte @@ -206,6 +206,14 @@ {seller.status} + {#if seller.gi_seller_type === 'exclusive'} + ★ Exclusive GI Maker + {:else if seller.gi_seller_type === 'mixed'} + Mixed GI Catalog + {:else} + Non-GI Craft + {/if} + {#if seller.status === 'pending' || seller.status === 'pending_approval'}