feat: add serializers, SKU generator, and views for seller central API
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 36s

This commit is contained in:
vickytechkey 2026-11-01 15:54:59 +05:30
parent 2212786d4b
commit 2326e3da1a
3 changed files with 121 additions and 12 deletions

View file

@ -398,8 +398,16 @@ class ProductSerializer(serializers.ModelSerializer):
def validate_sku(self, value):
clean_sku = str(value or '').strip().upper()
if not clean_sku or not re.match(r'^[A-Z0-9][A-Z0-9\-]{1,99}$', clean_sku):
clean_sku = f"TRDX-{secrets.token_hex(3).upper()}"
clean_sku = re.sub(r'[\s_]+', '-', clean_sku)
clean_sku = re.sub(r'[^A-Z0-9\-]', '', clean_sku)
request = self.context.get('request')
user = getattr(request, 'user', None)
if not clean_sku or (clean_sku.startswith('TRDX-') and len(clean_sku.split('-')) <= 2):
from .sku_generator import generate_product_sku
category_name = self.initial_data.get('category') if hasattr(self, 'initial_data') else None
clean_sku = generate_product_sku(user, category_name)
instance = getattr(self, 'instance', None)
qs = Product.objects.filter(sku=clean_sku)
@ -408,23 +416,22 @@ class ProductSerializer(serializers.ModelSerializer):
if qs.exists():
counter = 1
unique_sku = f"{clean_sku}-{counter}"
unique_sku = f"{clean_sku}-{counter:02d}"
while Product.objects.filter(sku=unique_sku).exclude(pk=instance.pk if instance else None).exists():
counter += 1
unique_sku = f"{clean_sku}-{counter}"
unique_sku = f"{clean_sku}-{counter:02d}"
clean_sku = unique_sku
return clean_sku
def validate_category(self, value):
allowed = [
"Apparel", "Home Decor", "Sculptures", "Kitchenware", "Jewellery", "Textiles", "Other",
"Handicrafts & Art", "Handloom & Textiles", "Home & Living", "Food & Organic",
"Beauty & Herbs", "Toys & Folk Art", "Other Craft", "Pottery & Ceramics",
"Brass & Metal Craft", "Woodcraft & Carvings"
]
from .sku_generator import CATEGORY_CODES
allowed = list(CATEGORY_CODES.keys()) + ["Other"]
if value not in allowed:
return "Other"
for cat in allowed:
if cat.lower() == str(value).strip().lower():
return cat
return value or "Handicrafts & Folk Art"
return value
def validate(self, attrs):

99
api/sku_generator.py Normal file
View file

@ -0,0 +1,99 @@
import re
import logging
logger = logging.getLogger('api')
CATEGORY_CODES = {
'Food & Beverages': 'FNB',
'Personal Care': 'PCR',
'Hygiene & Reusable Care': 'HYG',
'Clothing & Textiles': 'TEX',
'Bags, Footwear & Leather': 'LEA',
'Jewellery': 'JWL',
'Home Decor & Furnishings': 'DEC',
'Kitchen & Dining': 'KIT',
'Eco Packaging & Disposables': 'PKG',
'Handicrafts & Folk Art': 'ART',
'Wooden, Bamboo & Coir Products': 'WOD',
'Toys & Games': 'TOY',
'Baby & Kids': 'KID',
'Pet Products': 'PET',
'Eco Cleaning Products': 'CLN',
'Farm, Garden & Compost': 'GRD',
'Stationery, Office & Tech': 'STN',
'Engineering & Manufactured Goods': 'ENG',
# Legacy categories mapped to official codes
'Handicrafts & Art': 'ART',
'Handloom & Textiles': 'TEX',
'Home & Living': 'DEC',
'Food & Organic': 'FNB',
'Beauty & Herbs': 'PCR',
'Toys & Folk Art': 'TOY',
'Pottery & Ceramics': 'ART',
'Brass & Metal Craft': 'ART',
'Woodcraft & Carvings': 'WOD',
'Apparel': 'TEX',
'Sculptures': 'ART',
'Kitchenware': 'KIT',
'Textiles': 'TEX',
}
def get_category_code(category_name):
"""
Returns the standardized 3-letter uppercase category code.
"""
if not category_name:
return 'GEN'
clean = str(category_name).strip()
if clean in CATEGORY_CODES:
return CATEGORY_CODES[clean]
lower = clean.lower()
for cat, code in CATEGORY_CODES.items():
if cat.lower() == lower or cat.lower() in lower or lower in cat.lower():
return code
alnum = re.sub(r'[^A-Z0-9]', '', clean.upper())
return (alnum[:3] if len(alnum) >= 3 else alnum.ljust(3, 'X'))
def normalize_merchant_id(user):
"""
Extracts and standardizes the seller's Merchant ID (TRDPX000001 format).
"""
if not user:
return 'TRDPX000001'
profile = getattr(user, 'profile', None)
mid = getattr(profile, 'merchant_id', None)
if not mid:
draft = getattr(user, 'onboarding_draft', None)
mid = getattr(draft, 'merchant_id', None)
if mid and str(mid).strip():
return str(mid).strip().upper().replace(' ', '')
user_id = getattr(user, 'id', 1)
return f"TRDPX{str(user_id).zfill(6)}"
def generate_product_sku(user, category_name=None, product_model=None):
"""
Generates a unique SKU in format: TRDX-[categorycode]-[sellermid]-[productsno]
e.g. TRDX-TEX-TRDPX000042-001
"""
if product_model is None:
from .models import Product
product_model = Product
cat_code = get_category_code(category_name)
mid = normalize_merchant_id(user)
user_product_count = product_model.objects.filter(supplier=user).count() if user else 0
seq = user_product_count + 1
candidate_sku = f"TRDX-{cat_code}-{mid}-{seq:03d}"
while product_model.objects.filter(sku=candidate_sku).exists():
seq += 1
candidate_sku = f"TRDX-{cat_code}-{mid}-{seq:03d}"
logger.info("Generated SKU: %s for user=%s category=%s", candidate_sku, getattr(user, 'username', 'anon'), category_name)
return candidate_sku

View file

@ -1337,7 +1337,10 @@ class BulkUploadView(APIView):
if not title or not str(title).strip():
row_errors.append("Missing Product Title")
if not sku or not str(sku).strip():
row_errors.append("Missing SKU code")
from .sku_generator import generate_product_sku
sku = generate_product_sku(user, category_val)
else:
sku = str(sku).strip().upper()
if price_val <= 0:
row_errors.append("Invalid Price (must be a positive number > 0)")
if stock_val < 0: