Some checks failed
System Monitoring / Health Checks (push) Has been cancelled
System Monitoring / Performance Monitoring (push) Has been cancelled
System Monitoring / Database Monitoring (push) Has been cancelled
System Monitoring / Cache Monitoring (push) Has been cancelled
System Monitoring / Log Monitoring (push) Has been cancelled
System Monitoring / Resource Monitoring (push) Has been cancelled
System Monitoring / Uptime Monitoring (push) Has been cancelled
System Monitoring / Backup Monitoring (push) Has been cancelled
System Monitoring / Security Monitoring (push) Has been cancelled
System Monitoring / Monitoring Dashboard (push) Has been cancelled
System Monitoring / Alerting (push) Has been cancelled
Security Scanning / Dependency Scanning (push) Has been cancelled
Security Scanning / Code Security Scanning (push) Has been cancelled
Security Scanning / Secrets Scanning (push) Has been cancelled
Security Scanning / Container Security Scanning (push) Has been cancelled
Security Scanning / Compliance Checking (push) Has been cancelled
Security Scanning / Security Dashboard (push) Has been cancelled
Security Scanning / Security Remediation (push) Has been cancelled
579 lines
19 KiB
Python
579 lines
19 KiB
Python
"""
|
|
Integration test for retail module operations.
|
|
This test MUST fail before implementation.
|
|
"""
|
|
|
|
import pytest
|
|
from django.test import TestCase
|
|
from django.urls import reverse
|
|
from rest_framework.test import APIClient
|
|
from rest_framework import status
|
|
import json
|
|
from datetime import datetime, timedelta
|
|
|
|
|
|
class RetailOperationsIntegrationTest(TestCase):
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
|
|
# Tenant authentication header
|
|
self.tenant_auth = {'HTTP_AUTHORIZATION': 'Bearer tenant_token'}
|
|
|
|
# Test product data
|
|
self.product_data = {
|
|
'sku': 'LPT-PRO-001',
|
|
'name': 'Professional Laptop 15"',
|
|
'description': 'High-performance laptop for business use',
|
|
'category': 'ELECTRONICS',
|
|
'price': 3499.99,
|
|
'cost': 2800.00,
|
|
'stock_quantity': 25,
|
|
'barcode': '1234567890123',
|
|
'brand': 'TechBrand',
|
|
'model': 'PRO-15-2024',
|
|
'tax_rate': 6.0
|
|
}
|
|
|
|
# Test customer data
|
|
self.customer_data = {
|
|
'name': 'John Customer',
|
|
'email': 'john.customer@example.com',
|
|
'phone': '+60123456789',
|
|
'address': {
|
|
'street': '123 Customer Street',
|
|
'city': 'Kuala Lumpur',
|
|
'state': 'Wilayah Persekutuan',
|
|
'postal_code': '50000',
|
|
'country': 'Malaysia'
|
|
}
|
|
}
|
|
|
|
def test_complete_retail_workflow(self):
|
|
"""Test complete retail workflow from product creation to sales reporting."""
|
|
# Step 1: Create product (should fail before implementation)
|
|
product_response = self.client.post(
|
|
'/api/v1/retail/products/',
|
|
data=json.dumps(self.product_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert product_response.status_code == status.HTTP_201_CREATED
|
|
product_data = product_response.json()
|
|
|
|
# Verify product structure
|
|
assert 'id' in product_data
|
|
assert product_data['sku'] == self.product_data['sku']
|
|
assert product_data['stock_quantity'] == self.product_data['stock_quantity']
|
|
assert product_data['status'] == 'ACTIVE'
|
|
|
|
# Step 2: Create additional products for inventory testing
|
|
additional_products = [
|
|
{
|
|
'sku': 'MOU-WRL-001',
|
|
'name': 'Wireless Mouse',
|
|
'category': 'ELECTRONICS',
|
|
'price': 89.99,
|
|
'cost': 45.00,
|
|
'stock_quantity': 50
|
|
},
|
|
{
|
|
'sku': 'KEY-MEC-001',
|
|
'name': 'Mechanical Keyboard',
|
|
'category': 'ELECTRONICS',
|
|
'price': 299.99,
|
|
'cost': 180.00,
|
|
'stock_quantity': 30
|
|
}
|
|
]
|
|
|
|
created_products = []
|
|
for prod_data in additional_products:
|
|
prod_response = self.client.post(
|
|
'/api/v1/retail/products/',
|
|
data=json.dumps(prod_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
assert prod_response.status_code == status.HTTP_201_CREATED
|
|
created_products.append(prod_response.json())
|
|
|
|
# Step 3: Process multiple sales transactions
|
|
sales_transactions = [
|
|
{
|
|
'customer': self.customer_data,
|
|
'items': [
|
|
{
|
|
'product_id': product_data['id'],
|
|
'sku': product_data['sku'],
|
|
'quantity': 2,
|
|
'unit_price': product_data['price']
|
|
},
|
|
{
|
|
'product_id': created_products[0]['id'],
|
|
'sku': created_products[0]['sku'],
|
|
'quantity': 1,
|
|
'unit_price': created_products[0]['price']
|
|
}
|
|
],
|
|
'payment': {
|
|
'method': 'CARD',
|
|
'amount_paid': 7290.00,
|
|
'reference_number': 'CARD-001'
|
|
}
|
|
},
|
|
{
|
|
'customer': {
|
|
'name': 'Jane Buyer',
|
|
'email': 'jane@example.com',
|
|
'phone': '+60198765432'
|
|
},
|
|
'items': [
|
|
{
|
|
'product_id': created_products[1]['id'],
|
|
'sku': created_products[1]['sku'],
|
|
'quantity': 3,
|
|
'unit_price': created_products[1]['price']
|
|
}
|
|
],
|
|
'payment': {
|
|
'method': 'CASH',
|
|
'amount_paid': 900.00,
|
|
'reference_number': 'CASH-001'
|
|
}
|
|
}
|
|
]
|
|
|
|
created_sales = []
|
|
for sale_data in sales_transactions:
|
|
sale_response = self.client.post(
|
|
'/api/v1/retail/sales/',
|
|
data=json.dumps(sale_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert sale_response.status_code == status.HTTP_201_CREATED
|
|
created_sales.append(sale_response.json())
|
|
|
|
# Step 4: Verify inventory updates after sales
|
|
inventory_check_response = self.client.get(
|
|
f'/api/v1/retail/products/{product_data["id"]}/',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert inventory_check_response.status_code == status.HTTP_200_OK
|
|
updated_product = inventory_check_response.json()
|
|
|
|
# Stock should be reduced by sold quantity
|
|
expected_stock = self.product_data['stock_quantity'] - 2 # 2 laptops sold
|
|
assert updated_product['stock_quantity'] == expected_stock
|
|
|
|
# Step 5: Test sales reporting
|
|
sales_report_response = self.client.get(
|
|
'/api/v1/retail/reports/sales/',
|
|
data={
|
|
'start_date': (datetime.now() - timedelta(days=7)).isoformat(),
|
|
'end_date': datetime.now().isoformat()
|
|
},
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert sales_report_response.status_code == status.HTTP_200_OK
|
|
sales_report = sales_report_response.json()
|
|
|
|
assert 'total_sales' in sales_report
|
|
assert 'total_revenue' in sales_report
|
|
assert 'transactions_count' in sales_report
|
|
assert 'top_products' in sales_report
|
|
|
|
# Verify report data
|
|
assert sales_report['transactions_count'] == len(created_sales)
|
|
assert sales_report['total_revenue'] > 0
|
|
|
|
# Step 6: Test inventory reporting
|
|
inventory_report_response = self.client.get(
|
|
'/api/v1/retail/reports/inventory/',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert inventory_report_response.status_code == status.HTTP_200_OK
|
|
inventory_report = inventory_report_response.json()
|
|
|
|
assert 'total_products' in inventory_report
|
|
assert 'low_stock_items' in inventory_report
|
|
assert 'total_value' in inventory_report
|
|
|
|
# Step 7: Test product search and filtering
|
|
search_response = self.client.get(
|
|
'/api/v1/retail/products/',
|
|
data={'search': 'laptop', 'category': 'ELECTRONICS'},
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert search_response.status_code == status.HTTP_200_OK
|
|
search_results = search_response.json()['products']
|
|
|
|
# Should find the laptop product
|
|
assert len(search_results) > 0
|
|
assert any(product['id'] == product_data['id'] for product in search_results)
|
|
|
|
def test_inventory_management_operations(self):
|
|
"""Test inventory management operations."""
|
|
# Create product first
|
|
product_response = self.client.post(
|
|
'/api/v1/retail/products/',
|
|
data=json.dumps(self.product_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert product_response.status_code == status.HTTP_201_CREATED
|
|
product_data = product_response.json()
|
|
|
|
# Step 1: Stock adjustment
|
|
adjustment_data = {
|
|
'type': 'ADDITION',
|
|
'quantity': 10,
|
|
'reason': 'New stock received',
|
|
'reference': 'PO-2024-001',
|
|
'unit_cost': 2750.00
|
|
}
|
|
|
|
adjustment_response = self.client.post(
|
|
f'/api/v1/retail/products/{product_data["id"]}/inventory/',
|
|
data=json.dumps(adjustment_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert adjustment_response.status_code == status.HTTP_200_OK
|
|
|
|
# Verify stock was updated
|
|
updated_product_response = self.client.get(
|
|
f'/api/v1/retail/products/{product_data["id"]}/',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert updated_product_response.status_code == status.HTTP_200_OK
|
|
updated_product = updated_product_response.json()
|
|
|
|
expected_stock = self.product_data['stock_quantity'] + 10
|
|
assert updated_product['stock_quantity'] == expected_stock
|
|
|
|
# Step 2: Stock transfer
|
|
transfer_data = {
|
|
'quantity': 5,
|
|
'from_location': 'Warehouse A',
|
|
'to_location': 'Store Front',
|
|
'reason': 'Restocking store'
|
|
}
|
|
|
|
transfer_response = self.client.post(
|
|
f'/api/v1/retail/products/{product_data["id"]}/transfer/',
|
|
data=json.dumps(transfer_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert transfer_response.status_code == status.HTTP_200_OK
|
|
|
|
# Step 3: Low stock alerts
|
|
# Create product with low stock
|
|
low_stock_product = self.product_data.copy()
|
|
low_stock_product['sku'] = 'LOW-STOCK-001'
|
|
low_stock_product['stock_quantity'] = 2
|
|
|
|
low_stock_response = self.client.post(
|
|
'/api/v1/retail/products/',
|
|
data=json.dumps(low_stock_product),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert low_stock_response.status_code == status.HTTP_201_CREATED
|
|
|
|
# Check low stock report
|
|
low_stock_report_response = self.client.get(
|
|
'/api/v1/retail/reports/low-stock/',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert low_stock_report_response.status_code == status.HTTP_200_OK
|
|
low_stock_report = low_stock_report_response.json()
|
|
|
|
assert 'low_stock_items' in low_stock_report
|
|
assert len(low_stock_report['low_stock_items']) > 0
|
|
|
|
def test_product_variant_management(self):
|
|
"""Test product variant management."""
|
|
# Create parent product with variants
|
|
parent_product = self.product_data.copy()
|
|
parent_product['variants'] = [
|
|
{
|
|
'sku': 'LPT-PRO-001-BLK',
|
|
'name': 'Professional Laptop 15" - Black',
|
|
'attributes': {'color': 'Black', 'storage': '512GB SSD'},
|
|
'price_adjustment': 0,
|
|
'stock_quantity': 10
|
|
},
|
|
{
|
|
'sku': 'LPT-PRO-001-SLV',
|
|
'name': 'Professional Laptop 15" - Silver',
|
|
'attributes': {'color': 'Silver', 'storage': '1TB SSD'},
|
|
'price_adjustment': 200,
|
|
'stock_quantity': 8
|
|
}
|
|
]
|
|
|
|
product_response = self.client.post(
|
|
'/api/v1/retail/products/',
|
|
data=json.dumps(parent_product),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert product_response.status_code == status.HTTP_201_CREATED
|
|
created_product = product_response.json()
|
|
|
|
# Verify variants were created
|
|
assert 'variants' in created_product
|
|
assert len(created_product['variants']) == 2
|
|
|
|
# Test variant operations
|
|
variant = created_product['variants'][0]
|
|
|
|
# Update variant stock
|
|
variant_stock_update = {
|
|
'stock_quantity': 15,
|
|
'reason': 'New stock received'
|
|
}
|
|
|
|
variant_update_response = self.client.put(
|
|
f'/api/v1/retail/products/{created_product["id"]}/variants/{variant["sku"]}/',
|
|
data=json.dumps(variant_stock_update),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert variant_update_response.status_code == status.HTTP_200_OK
|
|
|
|
def test_customer_management(self):
|
|
"""Test customer management operations."""
|
|
# Create customer
|
|
customer_response = self.client.post(
|
|
'/api/v1/retail/customers/',
|
|
data=json.dumps(self.customer_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert customer_response.status_code == status.HTTP_201_CREATED
|
|
customer_data = customer_response.json()
|
|
|
|
# Step 1: Customer purchase history
|
|
# Create a sale for this customer
|
|
sale_data = {
|
|
'customer_id': customer_data['id'],
|
|
'items': [
|
|
{
|
|
'product_id': 'product-001',
|
|
'sku': 'TEST-001',
|
|
'quantity': 1,
|
|
'unit_price': 99.99
|
|
}
|
|
],
|
|
'payment': {
|
|
'method': 'CASH',
|
|
'amount_paid': 99.99
|
|
}
|
|
}
|
|
|
|
sale_response = self.client.post(
|
|
'/api/v1/retail/sales/',
|
|
data=json.dumps(sale_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert sale_response.status_code == status.HTTP_201_CREATED
|
|
|
|
# Get customer purchase history
|
|
history_response = self.client.get(
|
|
f'/api/v1/retail/customers/{customer_data["id"]}/history/',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert history_response.status_code == status.HTTP_200_OK
|
|
history_data = history_response.json()
|
|
|
|
assert 'purchases' in history_data
|
|
assert 'total_spent' in history_data
|
|
assert 'loyalty_points' in history_data
|
|
|
|
# Step 2: Customer loyalty program
|
|
loyalty_data = {
|
|
'points_earned': 100,
|
|
'notes': 'Purchase bonus'
|
|
}
|
|
|
|
loyalty_response = self.client.post(
|
|
f'/api/v1/retail/customers/{customer_data["id"]}/loyalty/',
|
|
data=json.dumps(loyalty_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert loyalty_response.status_code == status.HTTP_200_OK
|
|
|
|
def test_discount_and_promotion_management(self):
|
|
"""Test discount and promotion management."""
|
|
# Create promotion
|
|
promotion_data = {
|
|
'name': 'New Year Sale',
|
|
'type': 'PERCENTAGE',
|
|
'value': 20,
|
|
'start_date': (datetime.now() - timedelta(days=1)).isoformat(),
|
|
'end_date': (datetime.now() + timedelta(days=30)).isoformat(),
|
|
'applicable_products': ['product-001', 'product-002'],
|
|
'minimum_purchase': 100,
|
|
'usage_limit': 100
|
|
}
|
|
|
|
promotion_response = self.client.post(
|
|
'/api/v1/retail/promotions/',
|
|
data=json.dumps(promotion_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert promotion_response.status_code == status.HTTP_201_CREATED
|
|
created_promotion = promotion_response.json()
|
|
|
|
# Test promotion application in sale
|
|
sale_with_promotion = {
|
|
'customer': self.customer_data,
|
|
'items': [
|
|
{
|
|
'product_id': 'product-001',
|
|
'sku': 'TEST-001',
|
|
'quantity': 2,
|
|
'unit_price': 100.00
|
|
}
|
|
],
|
|
'promotion_code': created_promotion['code'],
|
|
'payment': {
|
|
'method': 'CARD',
|
|
'amount_paid': 160.00 # 20% discount on 200
|
|
}
|
|
}
|
|
|
|
sale_response = self.client.post(
|
|
'/api/v1/retail/sales/',
|
|
data=json.dumps(sale_with_promotion),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert sale_response.status_code == status.HTTP_201_CREATED
|
|
sale_data = sale_response.json()
|
|
|
|
# Verify discount was applied
|
|
assert 'discount_amount' in sale_data['totals']
|
|
assert sale_data['totals']['discount_amount'] == 40.00
|
|
|
|
def test_return_and_refund_operations(self):
|
|
"""Test return and refund operations."""
|
|
# Create a sale first
|
|
sale_data = {
|
|
'customer': self.customer_data,
|
|
'items': [
|
|
{
|
|
'product_id': 'product-001',
|
|
'sku': 'TEST-001',
|
|
'quantity': 2,
|
|
'unit_price': 100.00
|
|
}
|
|
],
|
|
'payment': {
|
|
'method': 'CARD',
|
|
'amount_paid': 200.00
|
|
}
|
|
}
|
|
|
|
sale_response = self.client.post(
|
|
'/api/v1/retail/sales/',
|
|
data=json.dumps(sale_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert sale_response.status_code == status.HTTP_201_CREATED
|
|
created_sale = sale_response.json()
|
|
|
|
# Process return
|
|
return_data = {
|
|
'sale_id': created_sale['id'],
|
|
'items': [
|
|
{
|
|
'product_id': 'product-001',
|
|
'quantity': 1,
|
|
'reason': 'Defective product',
|
|
'condition': 'DAMAGED'
|
|
}
|
|
],
|
|
'refund_method': 'ORIGINAL',
|
|
'notes': 'Customer reported defective item'
|
|
}
|
|
|
|
return_response = self.client.post(
|
|
'/api/v1/retail/returns/',
|
|
data=json.dumps(return_data),
|
|
content_type='application/json',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert return_response.status_code == status.HTTP_201_CREATED
|
|
return_data = return_response.json()
|
|
|
|
# Verify inventory was updated (returned to stock)
|
|
# Verify refund was processed
|
|
assert 'refund_amount' in return_data
|
|
assert return_data['refund_amount'] == 100.00
|
|
|
|
def test_retail_analytics_and_reporting(self):
|
|
"""Test retail analytics and reporting."""
|
|
# Generate some test data first
|
|
# This would involve creating multiple products and sales
|
|
|
|
# Test sales analytics
|
|
analytics_response = self.client.get(
|
|
'/api/v1/retail/analytics/',
|
|
data={
|
|
'period': 'monthly',
|
|
'year': 2024,
|
|
'month': 1
|
|
},
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert analytics_response.status_code == status.HTTP_200_OK
|
|
analytics_data = analytics_response.json()
|
|
|
|
assert 'revenue' in analytics_data
|
|
assert 'profit' in analytics_data
|
|
assert 'top_products' in analytics_data
|
|
assert 'customer_metrics' in analytics_data
|
|
|
|
# Test product performance
|
|
performance_response = self.client.get(
|
|
'/api/v1/retail/reports/product-performance/',
|
|
**self.tenant_auth
|
|
)
|
|
|
|
assert performance_response.status_code == status.HTTP_200_OK
|
|
performance_data = performance_response.json()
|
|
|
|
assert 'products' in performance_data
|
|
assert 'best_sellers' in performance_data
|
|
assert 'low_performers' in performance_data |