Skip to content

Merge updates from salehah737:main into github/codespaces-blank:main #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Python
venv/
__pycache__/
*.pyc

# Node.js
node_modules/
dist/

# Environment
.env

# Database
*.sqlite3

# Docker
**/Dockerfile
**/docker-compose.yml
138 changes: 138 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Groceries-Project
**Advanced Groceries Franchise + E-Commerce Project**:

---

### **Project Overview**
A hybrid platform combining **brick-and-mortar franchise management** with **e-commerce capabilities**, enabling:
- Online grocery shopping and delivery/pickup.
- Centralized inventory tracking across franchises.
- Franchise owner dashboards (orders, sales, staffing).
- Customer loyalty programs and localized promotions.

---

### **Technical Stack**
| **Component** | **Tools & Frameworks** |
|----------------------|-------------------------------------------------|
| **Backend (Python)** | Django/Flask, Django REST Framework, Celery |
| **Frontend** | React/Next.js, Redux/Toolkit, Tailwind CSS |
| **Database** | PostgreSQL (relational), Redis (caching) |
| **Auth** | JWT, OAuth 2.0 (Social logins) |
| **Payment** | Stripe, PayPal, or RazorPay integration |
| **DevOps** | Docker, GitHub Actions, Nginx, AWS/GCP |

---

### **Directory Structure**
```bash
groceries-project/
├── backend/
│ ├── apps/
│ │ ├── ecommerce/
│ │ │ ├── models/
│ │ │ │ ├── order.py
│ │ │ │ ├── payment.py
│ │ │ │ └── cart.py
│ │ │ ├── serializers/
│ │ │ ├── views/
│ │ │ ├── urls.py
│ │ │ └── tests/
│ │ ├── franchises/
│ │ │ ├── models/
│ │ │ │ ├── franchise.py
│ │ │ │ └── staff.py
│ │ │ └── geo/
│ │ ├── inventory/
│ │ │ ├── models/
│ │ │ │ ├── product.py
│ │ │ │ ├── category.py
│ │ │ │ └── stock.py
│ │ │ └── management/
│ │ ├── users/
│ │ │ ├── models/
│ │ │ │ └── user.py
│ │ │ ├── auth/
│ │ │ └── profiles/
│ │ └── utils/
│ │ ├── notifications.py
│ │ ├── pricing.py
│ │ └── validators.py
│ ├── config/
│ │ ├── settings/
│ │ │ ├── base.py
│ │ │ ├── production.py
│ │ │ └── local.py
│ │ └── asgi.py
│ └── manage.py
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── cart/
│ │ │ ├── product/
│ │ │ └── franchise/
│ │ ├── pages/
│ │ │ ├── api/
│ │ │ ├── account/
│ │ │ └── checkout/
│ │ ├── store/
│ │ │ ├── cartSlice.ts
│ │ │ └── authSlice.ts
│ │ └── services/
│ │ ├── api.ts
│ │ ├── cart.ts
│ │ └── auth.ts
├── ops/
│ ├── docker/
│ │ ├── nginx/
│ │ └── postgres/
│ └── scripts/
├── docs/
│ ├── api/
│ └── architecture/
└── .github/
└── workflows/
```

---

### **Core Features**
#### **1. Franchise Management**
- Geolocation-based franchise lookup for customers.
- Inventory sync across franchises (e.g., stock transfers).
- Franchise owner dashboard (sales analytics, staff scheduling).

#### **2. E-Commerce**
- Product catalog with filters (organic, gluten-free, etc.).
- Cart/checkout with pickup/delivery scheduling.
- Loyalty programs and referral systems.

#### **3. Inventory & Logistics**
- Real-time stock updates across franchises.
- Automated reordering alerts for low stock.
- Delivery routing optimization.

#### **4. Customer Experience**
- User profiles (order history, saved addresses).
- Multi-language support for diverse regions.
- Reviews/ratings for products and franchises.

---

### **DevOps & Deployment**
- **CI/CD**: Automated testing (pytest, Jest) + GitHub Actions.
- **Containerization**: Docker for backend (Django/Flask) and frontend (React).
- **Monitoring**: Prometheus + Grafana for performance tracking.
- **Scalability**: Load balancing with Nginx; cloud storage for product images.

---

### **Additional Considerations**
- **SEO**: Optimize product pages for search engines.
- **Security**: HTTPS, data encryption, and regular audits.
- **Analytics**: Google Analytics + custom dashboards for sales trends.
- **Localization**: Currency/food preferences per region.

---

This structure balances scalability, maintainability, and user experience. Start by building the **backend APIs** (e.g., `/api/products`, `/api/franchises`) and a **minimum viable frontend**, then iterate with advanced features. 🛒🚀
Empty file.
3 changes: 3 additions & 0 deletions backend/apps/ecommerce/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/apps/ecommerce/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class EcommerceConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "ecommerce"
Empty file.
38 changes: 38 additions & 0 deletions backend/apps/ecommerce/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Create your models here.
from django.db import models # type: ignore
from users.models import User

class Product(models.Model):
CATEGORY_CHOICES = [
('FR', 'Fruits'),
('VG', 'Vegetables'),
('DA', 'Dairy'),
('MT', 'Meat'),
('GR', 'Grains'),
]

name = models.CharField(max_length=100)
description = models.TextField()
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.CharField(max_length=2, choices=CATEGORY_CHOICES)
stock = models.PositiveIntegerField(default=0)
image = models.ImageField(upload_to='products/', null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.name

class Cart(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='items', on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=1)
added_at = models.DateTimeField(auto_now_add=True)

def total_price(self):
return self.product.price * self.quantity
29 changes: 29 additions & 0 deletions backend/apps/ecommerce/serializer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from rest_framework import serializers # type: ignore
from .models import Product, Cart, CartItem

class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'

class CartItemSerializer(serializers.ModelSerializer):
product = ProductSerializer()
total_price = serializers.SerializerMethodField()

class Meta:
model = CartItem
fields = ['id', 'product', 'quantity', 'total_price']

def get_total_price(self, obj):
return obj.total_price()

class CartSerializer(serializers.ModelSerializer):
items = CartItemSerializer(many=True)
total = serializers.SerializerMethodField()

class Meta:
model = Cart
fields = ['id', 'items', 'total']

def get_total(self, obj):
return sum(item.total_price() for item in obj.items.all())
3 changes: 3 additions & 0 deletions backend/apps/ecommerce/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
10 changes: 10 additions & 0 deletions backend/apps/ecommerce/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.urls import path
from . import views

urlpatterns = [
path('products/', views.ProductListView.as_view(), name='product-list'),
path('products/<int:pk>/', views.ProductDetailView.as_view(), name='product-detail'),
path('cart/', views.CartView.as_view(), name='cart'),
path('cart/add/', views.AddToCartView.as_view(), name='add-to-cart'),
path('product-list/', views.product_list, name='product-list-api'), # New route
]
40 changes: 40 additions & 0 deletions backend/apps/ecommerce/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Create your views here.
from rest_framework import generics, permissions, filters # type: ignore
from rest_framework.response import Response # type: ignore
from rest_framework.decorators import api_view # type: ignore # Added import
from .models import Product, Cart, CartItem
from .serializers import ProductSerializer, CartSerializer, CartItemSerializer # type: ignore
from django_filters.rest_framework import DjangoFilterBackend # type: ignore

class ProductListView(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend, filters.SearchFilter]
filterset_fields = ['category']
search_fields = ['name', 'description']

class ProductDetailView(generics.RetrieveAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer

class CartView(generics.RetrieveAPIView):
serializer_class = CartSerializer
permission_classes = [permissions.IsAuthenticated]

def get_object(self):
cart, created = Cart.objects.get_or_create(user=self.request.user)
return cart

class AddToCartView(generics.CreateAPIView):
serializer_class = CartItemSerializer
permission_classes = [permissions.IsAuthenticated]

def perform_create(self, serializer):
cart, created = Cart.objects.get_or_create(user=self.request.user)
serializer.save(cart=cart)

# New function-based view
@api_view(['GET'])
def product_list(request):
return Response({"message": "This will be your product list API"})

Empty file.
3 changes: 3 additions & 0 deletions backend/apps/franchises/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/apps/franchises/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class FranchisesConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "franchises"
Empty file.
14 changes: 14 additions & 0 deletions backend/apps/franchises/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Create your models here.
from django.db import models # type: ignore

class Franchise(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=200)
contact_email = models.EmailField()
contact_phone = models.CharField(max_length=15)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.name
3 changes: 3 additions & 0 deletions backend/apps/franchises/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
3 changes: 3 additions & 0 deletions backend/apps/franchises/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.
Empty file.
3 changes: 3 additions & 0 deletions backend/apps/inventory/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/apps/inventory/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class InventoryConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "inventory"
Empty file.
15 changes: 15 additions & 0 deletions backend/apps/inventory/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Create your models here.
from django.db import models # type: ignore
from ecommerce.models import Product

class Inventory(models.Model):
product = models.OneToOneField(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField(default=0)
low_stock_threshold = models.PositiveIntegerField(default=10)
last_restocked = models.DateTimeField(auto_now=True)

def is_low_stock(self):
return self.quantity < self.low_stock_threshold

def __str__(self):
return f"{self.product.name} - {self.quantity} in stock"
3 changes: 3 additions & 0 deletions backend/apps/inventory/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.test import TestCase

# Create your tests here.
3 changes: 3 additions & 0 deletions backend/apps/inventory/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.
Empty file added backend/apps/users/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions backend/apps/users/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions backend/apps/users/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class UsersConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "users"
Empty file.
13 changes: 13 additions & 0 deletions backend/apps/users/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Create your models here.
from django.contrib.auth.models import AbstractUser # type: ignore
from django.db import models # type: ignore

class User(AbstractUser):
email = models.EmailField(unique=True)
phone = models.CharField(max_length=15, blank=True, null=True)

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']

def __str__(self):
return self.email
Loading