integrated seller items
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 19s

This commit is contained in:
vickytechkey 2026-08-14 19:24:24 +05:30
parent 10361ecc2c
commit ba49657163
21 changed files with 228 additions and 59 deletions

View file

@ -0,0 +1,20 @@
class DatabaseRouter:
def db_for_read(self, model, **hints):
if model._meta.model_name == 'product':
return 'productprofile'
elif model._meta.model_name in ['sellerorder', 'orderitem']:
return 'customerprofile'
return 'default'
def db_for_write(self, model, **hints):
return self.db_for_read(model, **hints)
def allow_relation(self, obj1, obj2, **hints):
return True
def allow_migrate(self, db, app_label, model_name=None, **hints):
if model_name == 'product':
return db == 'productprofile'
elif model_name in ['sellerorder', 'orderitem']:
return db == 'customerprofile'
return db == 'default'

View file

@ -88,9 +88,27 @@ DATABASES = {
'PASSWORD': 'vtechnosoft@123A',
'HOST': '127.0.0.1',
'PORT': '5432',
},
'productprofile': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'productprofile',
'USER': 'vignesh',
'PASSWORD': 'vtechnosoft@123A',
'HOST': '127.0.0.1',
'PORT': '5432',
},
'customerprofile': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'customerprofile',
'USER': 'vignesh',
'PASSWORD': 'vtechnosoft@123A',
'HOST': '127.0.0.1',
'PORT': '5432',
}
}
DATABASE_ROUTERS = ['customerwebsitebackend.db_router.DatabaseRouter']
# Password validation
# https://docs.djangoproject.com/en/6.1/ref/settings/#auth-password-validators

View file

@ -5,3 +5,7 @@ INFO 2026-08-14 13:43:48,808 autoreload 57706 139697750147200 /home/vignesh/gith
INFO 2026-08-14 13:43:49,076 autoreload 58191 123150790480000 Watching for file changes with StatReloader
INFO 2026-08-14 13:46:26,838 autoreload 58191 123150790480000 /home/vignesh/github/customerwebsitebackend/customerwebsitebackend/settings.py changed, reloading.
INFO 2026-08-14 13:46:27,112 autoreload 60038 140193920041088 Watching for file changes with StatReloader
INFO 2026-08-14 13:52:49,207 autoreload 60038 140193920041088 /home/vignesh/github/customerwebsitebackend/orders/models.py changed, reloading.
INFO 2026-08-14 13:52:49,524 autoreload 64078 126174254198912 Watching for file changes with StatReloader
INFO 2026-08-14 13:53:37,086 autoreload 64078 126174254198912 /home/vignesh/github/customerwebsitebackend/orders/models.py changed, reloading.
INFO 2026-08-14 13:53:37,360 autoreload 64566 128112213057664 Watching for file changes with StatReloader

View file

@ -0,0 +1,46 @@
# Generated by Django 6.1 on 2026-08-14 13:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='SellerOrder',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('supplier_id', models.IntegerField()),
('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(decimal_places=2, max_digits=10)),
('status', models.CharField(default='Pending Acceptance', max_length=50)),
('carrier', models.CharField(default='Pending', max_length=100)),
('tracking', models.CharField(default='Pending', max_length=100)),
('eta', models.CharField(default='N/A', max_length=50)),
],
options={
'db_table': 'api_order',
'managed': False,
},
),
migrations.RemoveField(
model_name='cartitem',
name='product',
),
migrations.AddField(
model_name='cartitem',
name='product_id',
field=models.IntegerField(default=1),
),
migrations.DeleteModel(
name='OrderItem',
),
]

View file

@ -21,12 +21,19 @@ class Cart(models.Model):
class CartItem(models.Model):
cart = models.ForeignKey(Cart, on_delete=models.CASCADE, related_name='items')
product = models.ForeignKey(Product, on_delete=models.CASCADE)
product_id = models.IntegerField(default=1) # Keep reference as simple int since product is in productprofile db
quantity = models.PositiveIntegerField(default=1)
price_at_add = models.DecimalField(max_digits=10, decimal_places=2)
@property
def product(self):
try:
return Product.objects.get(id=self.product_id)
except Product.DoesNotExist:
return None
def __str__(self):
return f"{self.quantity} x {self.product.title}"
return f"{self.quantity} x Product {self.product_id}"
class Order(models.Model):
STATUS_CHOICES = (
@ -47,13 +54,23 @@ class Order(models.Model):
def __str__(self):
return f"Order {self.id} by {self.user.username}"
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
quantity = models.PositiveIntegerField()
unit_price = models.DecimalField(max_digits=10, decimal_places=2)
tracking_number = models.CharField(max_length=100, blank=True, null=True)
carrier = models.CharField(max_length=100, blank=True, null=True)
# Align to seller_central_backend api_order schema
class SellerOrder(models.Model):
supplier_id = models.IntegerField() # Raw integer referring to User in sellerprofile db
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, 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')
class Meta:
db_table = 'api_order'
managed = False # Managed by seller central backend migrations
def __str__(self):
return f"{self.quantity} x {self.product.title if self.product else 'Deleted Product'}"
return self.order_id

View file

@ -1,16 +1,22 @@
from rest_framework import serializers
from .models import Cart, CartItem, Order, OrderItem
from .models import Cart, CartItem, Order, SellerOrder
from products.serializers import ProductSerializer
class CartItemSerializer(serializers.ModelSerializer):
product = ProductSerializer(read_only=True)
product_id = serializers.IntegerField(write_only=True)
product = serializers.SerializerMethodField()
product_id = serializers.IntegerField()
subtotal = serializers.SerializerMethodField()
class Meta:
model = CartItem
fields = ['id', 'product', 'product_id', 'quantity', 'price_at_add', 'subtotal']
def get_product(self, obj):
prod = obj.product
if prod:
return ProductSerializer(prod).data
return None
def get_subtotal(self, obj):
return obj.quantity * obj.price_at_add
@ -25,18 +31,9 @@ class CartSerializer(serializers.ModelSerializer):
def get_total_price(self, obj):
return sum(item.quantity * item.price_at_add for item in obj.items.all())
class OrderItemSerializer(serializers.ModelSerializer):
product_title = serializers.CharField(source='product.title', read_only=True)
product_image = serializers.CharField(source='product.image_url', read_only=True)
class Meta:
model = OrderItem
fields = ['id', 'product', 'product_title', 'product_image', 'quantity', 'unit_price', 'tracking_number', 'carrier']
class OrderSerializer(serializers.ModelSerializer):
items = OrderItemSerializer(many=True, read_only=True)
username = serializers.CharField(source='user.username', read_only=True)
class Meta:
model = Order
fields = ['id', 'username', 'razorpay_order_id', 'razorpay_payment_id', 'total_amount', 'status', 'shipping_address', 'placed_at', 'items']
fields = ['id', 'username', 'razorpay_order_id', 'razorpay_payment_id', 'total_amount', 'status', 'shipping_address', 'placed_at']

View file

@ -3,10 +3,11 @@ audit_logger = logging.getLogger('audit')
from rest_framework import status, permissions
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Cart, CartItem, Order, OrderItem
from .models import Cart, CartItem, Order, SellerOrder
from .serializers import CartSerializer, OrderSerializer
from products.models import Product
import uuid
from django.utils.timezone import now
class CartView(APIView):
permission_classes = [permissions.IsAuthenticated]
@ -26,17 +27,17 @@ class CartView(APIView):
quantity = int(request.data.get('quantity', 1))
try:
product = Product.objects.get(id=product_id, is_active=True)
product = Product.objects.get(id=product_id)
except Product.DoesNotExist:
return Response({"error": "Product not found"}, status=status.HTTP_404_NOT_FOUND)
cart_item, created = CartItem.objects.get_or_create(
cart=cart,
product=product,
product_id=product_id,
defaults={'price_at_add': product.price, 'quantity': 0}
)
cart_item.quantity += quantity
cart_item.price_at_add = product.price # update price to latest
cart_item.price_at_add = product.price
cart_item.save()
return Response({"message": "Item added to cart"})
@ -86,13 +87,10 @@ class CheckoutView(APIView):
if not shipping_address:
return Response({"error": "Shipping address is required"}, status=status.HTTP_400_BAD_REQUEST)
# Calculate total
total_amount = sum(item.quantity * item.price_at_add for item in items)
# Mock Razorpay Order Creation
razorpay_order_id = f"order_{uuid.uuid4().hex[:14]}"
# Create Order
# 1. Create a local Customer Order
order = Order.objects.create(
user=user,
total_amount=total_amount,
@ -102,17 +100,20 @@ class CheckoutView(APIView):
)
audit_logger.info(f"AUDIT - Checkout: User '{user.username}' created order ID {order.id} for amount ${total_amount:.2f}.")
# 2. Write to shared SellerOrder (api_order) so sellers get notified of incoming requests
for item in items:
OrderItem.objects.create(
order=order,
product=item.product,
quantity=item.quantity,
unit_price=item.price_at_add
)
# Deduct stock
if item.product.stock >= item.quantity:
item.product.stock -= item.quantity
item.product.save()
prod = item.product
if prod:
SellerOrder.objects.create(
supplier_id=prod.supplier_id,
order_id=f"{order.id}-{prod.id}",
date=now().date(),
item=prod.title,
quantity=item.quantity,
customer=user.username,
total=item.quantity * item.price_at_add,
status='Pending Acceptance'
)
# Clear Cart
items.delete()
@ -141,6 +142,9 @@ class PaymentSuccessView(APIView):
order.save()
audit_logger.info(f"AUDIT - Payment Success: User '{request.user.username}' successfully paid order ID {order.id} (Payment ID: {payment_id}).")
# Update matching SellerOrders status
SellerOrder.objects.filter(order_id__startswith=f"{order.id}-").update(status='Ready to Ship')
return Response({"message": "Payment verified and order confirmed"})
class OrderHistoryView(APIView):
@ -149,7 +153,40 @@ class OrderHistoryView(APIView):
def get(self, request):
orders = Order.objects.filter(user=request.user).order_by('-placed_at')
serializer = OrderSerializer(orders, many=True)
return Response(serializer.data)
# Merge tracking details from SellerOrders
response_data = []
for order_data in serializer.data:
order_id = order_data['id']
seller_orders = SellerOrder.objects.filter(order_id__startswith=f"{order_id}-")
# Enrich with tracking details if available
order_data['items'] = []
for s_ord in seller_orders:
order_data['items'].append({
"product_title": s_ord.item,
"quantity": s_ord.quantity,
"unit_price": float(s_ord.total / s_ord.quantity),
"tracking_number": s_ord.tracking if s_ord.tracking != 'Pending' else None,
"carrier": s_ord.carrier if s_ord.carrier != 'Pending' else None,
"status": s_ord.status
})
# Aggregate status
if seller_orders.exists():
statuses = [o.status for o in seller_orders]
if 'Pending Acceptance' in statuses:
order_data['status'] = 'pending'
elif 'Ready to Ship' in statuses:
order_data['status'] = 'confirmed'
elif 'Shipped' in statuses:
order_data['status'] = 'shipped'
elif 'Delivered' in statuses:
order_data['status'] = 'delivered'
response_data.append(order_data)
return Response(response_data)
class OrderDetailView(APIView):
permission_classes = [permissions.IsAuthenticated]
@ -159,5 +196,20 @@ class OrderDetailView(APIView):
order = Order.objects.get(id=pk, user=request.user)
except Order.DoesNotExist:
return Response({"error": "Order not found"}, status=status.HTTP_404_NOT_FOUND)
serializer = OrderSerializer(order)
return Response(serializer.data)
order_data = serializer.data
seller_orders = SellerOrder.objects.filter(order_id__startswith=f"{pk}-")
order_data['items'] = []
for s_ord in seller_orders:
order_data['items'].append({
"product_title": s_ord.item,
"quantity": s_ord.quantity,
"unit_price": float(s_ord.total / s_ord.quantity),
"tracking_number": s_ord.tracking if s_ord.tracking != 'Pending' else None,
"carrier": s_ord.carrier if s_ord.carrier != 'Pending' else None,
"status": s_ord.status
})
return Response(order_data)

View file

@ -0,0 +1,17 @@
# Generated by Django 6.1 on 2026-08-14 13:53
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('products', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='product',
options={'managed': False},
),
]

View file

@ -1,5 +1,5 @@
from django.db import models
from django.utils.text import slugify
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
@ -11,25 +11,22 @@ class Category(models.Model):
verbose_name_plural = "Categories"
ordering = ['sort_order', 'name']
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
super().save(*args, **kwargs)
def __str__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
# Align to seller_central_backend api_product schema
supplier = models.ForeignKey(User, on_delete=models.CASCADE, related_name='products', db_constraint=False)
title = models.CharField(max_length=255)
category = models.CharField(max_length=100) # CharField matching seller central
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.IntegerField(default=0)
sku = models.CharField(max_length=100, unique=True)
image_url = models.URLField(max_length=500, blank=True, null=True)
description = models.TextField(blank=True)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
image = models.TextField(blank=True, null=True) # Textfield for base64 / URL
class Meta:
db_table = 'api_product'
managed = False # Managed by seller central backend migrations
def __str__(self):
return self.title

View file

@ -7,8 +7,9 @@ class CategorySerializer(serializers.ModelSerializer):
fields = ['id', 'name', 'slug', 'is_active', 'sort_order']
class ProductSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.name', read_only=True)
category_name = serializers.CharField(source='category', read_only=True)
image_url = serializers.CharField(source='image', read_only=True)
class Meta:
model = Product
fields = ['id', 'category', 'category_name', 'title', 'price', 'stock', 'sku', 'image_url', 'description', 'is_active']
fields = ['id', 'category', 'category_name', 'title', 'price', 'stock', 'sku', 'image_url']

View file

@ -15,7 +15,7 @@ class ProductListView(generics.ListAPIView):
permission_classes = [permissions.AllowAny]
def get_queryset(self):
queryset = Product.objects.filter(is_active=True)
queryset = Product.objects.all()
category_slug = self.request.query_params.get('category', None)
search_query = self.request.query_params.get('search', None)
@ -29,6 +29,6 @@ class ProductListView(generics.ListAPIView):
return queryset
class ProductDetailView(generics.RetrieveAPIView):
queryset = Product.objects.filter(is_active=True)
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = [permissions.AllowAny]