765 lines
32 KiB
Python
765 lines
32 KiB
Python
import os
|
|
import django
|
|
|
|
# Set up Django environment
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings')
|
|
django.setup()
|
|
|
|
import random
|
|
import uuid
|
|
from datetime import datetime, timedelta
|
|
from decimal import Decimal
|
|
from django.utils import timezone as django_timezone
|
|
from django.db import transaction, connection
|
|
|
|
from core.models import Tenant
|
|
from accounts.models import User
|
|
from inventory.models import InventoryItem, InventoryStock, InventoryLocation, PurchaseOrder, PurchaseOrderItem, \
|
|
Supplier
|
|
|
|
# Test database connection
|
|
def test_database_connection():
|
|
"""Test if database connection is working"""
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("SELECT 1")
|
|
result = cursor.fetchone()
|
|
print(f"Database connection test: {'SUCCESS' if result else 'FAILED'}")
|
|
return result is not None
|
|
except Exception as e:
|
|
print(f"Database connection error: {e}")
|
|
return False
|
|
|
|
# Saudi Arabian Inventory Data
|
|
SAUDI_MEDICAL_CATEGORIES = [
|
|
'Pharmaceuticals',
|
|
'Medical Devices',
|
|
'Surgical Instruments',
|
|
'Laboratory Supplies',
|
|
'Radiology Supplies',
|
|
'PPE & Safety',
|
|
'Wound Care',
|
|
'IV Therapy',
|
|
'Respiratory Care',
|
|
'Cardiology Equipment',
|
|
'Orthopedic Supplies',
|
|
'Emergency Supplies',
|
|
'Cleaning & Disinfection',
|
|
'Office Supplies',
|
|
'Food & Nutrition'
|
|
]
|
|
|
|
SAUDI_PHARMACEUTICAL_SUBCATEGORIES = {
|
|
'Pharmaceuticals': [
|
|
'Antibiotics', 'Analgesics', 'Cardiovascular', 'Diabetes Care',
|
|
'Respiratory', 'Oncology', 'Psychiatric', 'Emergency Drugs',
|
|
'Anesthetics', 'Vaccines', 'IV Solutions', 'Blood Products'
|
|
]
|
|
}
|
|
|
|
SAUDI_MEDICAL_MANUFACTURERS = [
|
|
'Saudi Pharmaceutical Industries (SPIMACO)',
|
|
'Tabuk Pharmaceuticals',
|
|
'Jamjoom Pharmaceuticals',
|
|
'Al-Jazeera Pharmaceutical Industries',
|
|
'Medical Supplies Company',
|
|
'Saudi Medical Supplies',
|
|
'Gulf Pharmaceutical Industries',
|
|
'Middle East Healthcare',
|
|
'Arabian Medical Equipment',
|
|
'Riyadh Pharma',
|
|
'Johnson & Johnson Saudi',
|
|
'Pfizer Saudi Arabia',
|
|
'Novartis Saudi',
|
|
'Roche Saudi Arabia',
|
|
'Abbott Saudi Arabia'
|
|
]
|
|
|
|
SAUDI_STORAGE_LOCATIONS = {
|
|
'buildings': ['Main Hospital', 'Outpatient Clinic', 'Emergency Wing', 'Research Center', 'Administrative Building'],
|
|
'floors': ['Ground Floor', 'First Floor', 'Second Floor', 'Third Floor', 'Basement'],
|
|
'rooms': ['Pharmacy', 'Central Supply', 'OR Storage', 'ICU Supply', 'Ward Storage', 'Emergency Supply'],
|
|
'zones': ['Zone A', 'Zone B', 'Zone C', 'Zone D'],
|
|
'aisles': ['Aisle 1', 'Aisle 2', 'Aisle 3', 'Aisle 4', 'Aisle 5'],
|
|
'shelves': ['Shelf A', 'Shelf B', 'Shelf C', 'Shelf D', 'Shelf E'],
|
|
'bins': ['Bin 1', 'Bin 2', 'Bin 3', 'Bin 4', 'Bin 5']
|
|
}
|
|
|
|
SAUDI_SUPPLIER_DATA = [
|
|
{
|
|
'name': 'Saudi Medical Supply Co.',
|
|
'type': 'DISTRIBUTOR',
|
|
'city': 'Riyadh',
|
|
'phone': '+966-11-234-5678'
|
|
},
|
|
{
|
|
'name': 'Gulf Medical Equipment',
|
|
'type': 'MANUFACTURER',
|
|
'city': 'Dammam',
|
|
'phone': '+966-13-345-6789'
|
|
},
|
|
{
|
|
'name': 'Arabian Healthcare Supplies',
|
|
'type': 'WHOLESALER',
|
|
'city': 'Jeddah',
|
|
'phone': '+966-12-456-7890'
|
|
},
|
|
{
|
|
'name': 'Riyadh Medical Trading',
|
|
'type': 'DISTRIBUTOR',
|
|
'city': 'Riyadh',
|
|
'phone': '+966-11-567-8901'
|
|
},
|
|
{
|
|
'name': 'Al-Dawaa Medical',
|
|
'type': 'MANUFACTURER',
|
|
'city': 'Medina',
|
|
'phone': '+966-14-678-9012'
|
|
},
|
|
{
|
|
'name': 'Nahdi Medical Company',
|
|
'type': 'RETAILER',
|
|
'city': 'Jeddah',
|
|
'phone': '+966-12-789-0123'
|
|
},
|
|
{
|
|
'name': 'United Pharmaceuticals',
|
|
'type': 'MANUFACTURER',
|
|
'city': 'Khobar',
|
|
'phone': '+966-13-890-1234'
|
|
},
|
|
{
|
|
'name': 'Middle East Medical',
|
|
'type': 'DISTRIBUTOR',
|
|
'city': 'Taif',
|
|
'phone': '+966-12-901-2345'
|
|
},
|
|
{
|
|
'name': 'Kingdom Medical Supplies',
|
|
'type': 'WHOLESALER',
|
|
'city': 'Buraidah',
|
|
'phone': '+966-16-012-3456'
|
|
},
|
|
{
|
|
'name': 'Eastern Province Medical Co.',
|
|
'type': 'DISTRIBUTOR',
|
|
'city': 'Dhahran',
|
|
'phone': '+966-13-123-4567'
|
|
}
|
|
]
|
|
|
|
SAUDI_CITIES = [
|
|
'Riyadh', 'Jeddah', 'Mecca', 'Medina', 'Dammam', 'Khobar', 'Dhahran',
|
|
'Taif', 'Buraidah', 'Tabuk', 'Hail', 'Khamis Mushait', 'Hofuf', 'Jubail',
|
|
'Hafar Al-Batin', 'Yanbu', 'Abha', 'Najran', 'Qatif', 'Sakaka'
|
|
]
|
|
|
|
COMMON_MEDICAL_ITEMS = [
|
|
# Pharmaceuticals
|
|
{'name': 'Paracetamol 500mg', 'category': 'Pharmaceuticals', 'subcategory': 'Analgesics', 'unit': 'TAB',
|
|
'controlled': False},
|
|
{'name': 'Amoxicillin 250mg', 'category': 'Pharmaceuticals', 'subcategory': 'Antibiotics', 'unit': 'CAP',
|
|
'controlled': False},
|
|
{'name': 'Insulin Regular', 'category': 'Pharmaceuticals', 'subcategory': 'Diabetes Care', 'unit': 'VIAL',
|
|
'controlled': False},
|
|
{'name': 'Morphine 10mg/ml', 'category': 'Pharmaceuticals', 'subcategory': 'Analgesics', 'unit': 'AMP',
|
|
'controlled': True},
|
|
{'name': 'Diazepam 5mg', 'category': 'Pharmaceuticals', 'subcategory': 'Psychiatric', 'unit': 'TAB',
|
|
'controlled': True},
|
|
{'name': 'Normal Saline 0.9%', 'category': 'Pharmaceuticals', 'subcategory': 'IV Solutions', 'unit': 'BAG',
|
|
'controlled': False},
|
|
|
|
# Medical Devices
|
|
{'name': 'Disposable Syringe 5ml', 'category': 'Medical Devices', 'subcategory': 'Injection Equipment',
|
|
'unit': 'PCS', 'controlled': False},
|
|
{'name': 'Blood Pressure Cuff', 'category': 'Medical Devices', 'subcategory': 'Monitoring Equipment', 'unit': 'PCS',
|
|
'controlled': False},
|
|
{'name': 'ECG Electrodes', 'category': 'Medical Devices', 'subcategory': 'Cardiology Equipment', 'unit': 'PCS',
|
|
'controlled': False},
|
|
{'name': 'Surgical Gloves Size M', 'category': 'PPE & Safety', 'subcategory': 'Protective Equipment',
|
|
'unit': 'PAIR', 'controlled': False},
|
|
|
|
# Surgical Instruments
|
|
{'name': 'Scalpel Blade #15', 'category': 'Surgical Instruments', 'subcategory': 'Cutting Instruments',
|
|
'unit': 'PCS', 'controlled': False},
|
|
{'name': 'Forceps Straight', 'category': 'Surgical Instruments', 'subcategory': 'Grasping Instruments',
|
|
'unit': 'PCS', 'controlled': False},
|
|
|
|
# Laboratory Supplies
|
|
{'name': 'Blood Collection Tube EDTA', 'category': 'Laboratory Supplies', 'subcategory': 'Collection Tubes',
|
|
'unit': 'PCS', 'controlled': False},
|
|
{'name': 'Urine Container', 'category': 'Laboratory Supplies', 'subcategory': 'Collection Containers',
|
|
'unit': 'PCS', 'controlled': False},
|
|
]
|
|
|
|
|
|
def generate_saudi_item_code():
|
|
"""Generate Saudi medical item code"""
|
|
return f"SAU-{random.randint(100000, 999999)}"
|
|
|
|
|
|
def generate_saudi_lot_number():
|
|
"""Generate Saudi lot number"""
|
|
return f"LOT{random.randint(2024, 2025)}{random.randint(100, 999)}"
|
|
|
|
|
|
def generate_saudi_po_number():
|
|
"""Generate Saudi purchase order number"""
|
|
return f"PO-{random.randint(2024, 2025)}-{random.randint(1000, 9999)}"
|
|
|
|
|
|
def create_saudi_suppliers(tenants):
|
|
"""Create Saudi suppliers"""
|
|
suppliers = []
|
|
|
|
for tenant in tenants:
|
|
print(f"Creating suppliers for {tenant.name}...")
|
|
|
|
# Get or create creator
|
|
try:
|
|
creators = User.objects.filter(tenant=tenant)
|
|
creator = random.choice(list(creators)) if creators.exists() else None
|
|
except Exception as e:
|
|
print(f"Warning: Could not get creator: {e}")
|
|
creator = None
|
|
|
|
for i, supplier_data in enumerate(SAUDI_SUPPLIER_DATA):
|
|
supplier_code = f"SUP-{i + 1:03d}"
|
|
|
|
# Ensure unique supplier code
|
|
counter = 1
|
|
original_code = supplier_code
|
|
try:
|
|
while Supplier.objects.filter(tenant=tenant, supplier_code=supplier_code).exists():
|
|
supplier_code = f"{original_code}-{counter}"
|
|
counter += 1
|
|
except Exception as e:
|
|
print(f"Warning: Could not check existing suppliers: {e}")
|
|
|
|
try:
|
|
# Use atomic transaction instead of savepoint
|
|
with transaction.atomic():
|
|
supplier = Supplier.objects.create(
|
|
tenant=tenant,
|
|
supplier_code=supplier_code,
|
|
name=supplier_data['name'],
|
|
supplier_type=supplier_data['type'],
|
|
contact_person=f"Manager - {supplier_data['name']}",
|
|
phone=supplier_data['phone'],
|
|
email=f"contact@{supplier_data['name'].lower().replace(' ', '').replace('.', '')}.sa",
|
|
website=f"https://www.{supplier_data['name'].lower().replace(' ', '').replace('.', '')}.sa",
|
|
address_line_1=f"{random.randint(1, 999)} King {random.choice(['Fahd', 'Abdullah', 'Saud'])} Road",
|
|
city=supplier_data['city'],
|
|
state=f"{supplier_data['city']} Province",
|
|
postal_code=f"{random.randint(10000, 99999)}",
|
|
country='Saudi Arabia',
|
|
tax_id=f"TAX-{random.randint(100000000, 999999999)}",
|
|
payment_terms=random.choice(['NET_30', 'NET_60', 'COD']),
|
|
performance_rating=Decimal(str(random.uniform(3.5, 5.0))),
|
|
on_time_delivery_rate=Decimal(str(random.uniform(85, 98))),
|
|
quality_rating=Decimal(str(random.uniform(3.5, 5.0))),
|
|
is_active=True,
|
|
is_preferred=random.choice([True, False]),
|
|
certifications=[
|
|
{'name': 'ISO 13485', 'number': f"ISO-{random.randint(10000, 99999)}",
|
|
'expiry': '2025-12-31'},
|
|
{'name': 'Saudi FDA', 'number': f"SFDA-{random.randint(10000, 99999)}",
|
|
'expiry': '2025-06-30'}
|
|
],
|
|
notes=f"Saudi medical supplier - {supplier_data['name']}",
|
|
created_by=creator
|
|
)
|
|
suppliers.append(supplier)
|
|
print(f"Created supplier: {supplier_code}")
|
|
|
|
except Exception as e:
|
|
print(f"Error creating supplier {supplier_code}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(suppliers)} suppliers")
|
|
return suppliers
|
|
|
|
|
|
def create_saudi_inventory_locations(tenants, locations_per_tenant=20):
|
|
"""Create Saudi inventory locations"""
|
|
locations = []
|
|
|
|
for tenant in tenants:
|
|
print(f"Creating inventory locations for {tenant.name}...")
|
|
|
|
for i in range(locations_per_tenant):
|
|
location_code = f"LOC-{i + 1:03d}"
|
|
building = random.choice(SAUDI_STORAGE_LOCATIONS['buildings'])
|
|
floor = random.choice(SAUDI_STORAGE_LOCATIONS['floors'])
|
|
room = random.choice(SAUDI_STORAGE_LOCATIONS['rooms'])
|
|
zone = random.choice(SAUDI_STORAGE_LOCATIONS['zones']) if random.choice([True, False]) else None
|
|
aisle = random.choice(SAUDI_STORAGE_LOCATIONS['aisles']) if random.choice([True, False]) else None
|
|
shelf = random.choice(SAUDI_STORAGE_LOCATIONS['shelves']) if random.choice([True, False]) else None
|
|
bin_location = random.choice(SAUDI_STORAGE_LOCATIONS['bins']) if random.choice([True, False]) else None
|
|
|
|
location_type = random.choice(['WAREHOUSE', 'PHARMACY', 'NURSING_UNIT', 'OR_STORAGE', 'CENTRAL_SUPPLY'])
|
|
|
|
# Temperature controlled for pharmaceuticals
|
|
temp_controlled = random.choice([True, False])
|
|
temp_min = random.choice([2, 15, 20]) if temp_controlled else None
|
|
temp_max = random.choice([8, 25, 30]) if temp_controlled else None
|
|
|
|
# Humidity controlled
|
|
humidity_controlled = random.choice([True, False])
|
|
humidity_min = random.randint(30, 45) if humidity_controlled else None
|
|
humidity_max = random.randint(55, 70) if humidity_controlled else None
|
|
|
|
# Secure location for controlled substances
|
|
secure_location = random.choice([True, False])
|
|
|
|
# Get or create manager
|
|
try:
|
|
managers = User.objects.filter(tenant=tenant)
|
|
manager = random.choice(list(managers)) if managers.exists() else None
|
|
except Exception as e:
|
|
print(f"Warning: Could not get manager: {e}")
|
|
manager = None
|
|
|
|
try:
|
|
# Use atomic transaction instead of savepoint
|
|
with transaction.atomic():
|
|
location = InventoryLocation.objects.create(
|
|
tenant=tenant,
|
|
location_code=location_code,
|
|
name=f"{building} - {room}",
|
|
description=f"Storage location in {building}, {floor}, {room}",
|
|
location_type=location_type,
|
|
building=building,
|
|
floor=floor,
|
|
room=room,
|
|
zone=zone,
|
|
aisle=aisle,
|
|
shelf=shelf,
|
|
bin=bin_location,
|
|
capacity_cubic_feet=Decimal(str(random.randint(100, 1000))),
|
|
max_weight_pounds=Decimal(str(random.randint(500, 5000))),
|
|
temperature_controlled=temp_controlled,
|
|
temperature_min=temp_min,
|
|
temperature_max=temp_max,
|
|
humidity_controlled=humidity_controlled,
|
|
humidity_min=humidity_min,
|
|
humidity_max=humidity_max,
|
|
secure_location=secure_location,
|
|
access_control=random.choice(
|
|
['KEYCARD', 'PIN', 'BIOMETRIC', 'KEY']) if secure_location else None,
|
|
is_active=True,
|
|
location_manager=manager,
|
|
notes=f"Created for {tenant.name} inventory management",
|
|
created_by=manager
|
|
)
|
|
locations.append(location)
|
|
print(f"Created location: {location_code}")
|
|
|
|
except Exception as e:
|
|
print(f"Error creating location {location_code}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(locations)} inventory locations")
|
|
return locations
|
|
|
|
|
|
def create_saudi_inventory_items(tenants, items_per_tenant=100):
|
|
"""Create Saudi inventory items"""
|
|
items = []
|
|
|
|
for tenant in tenants:
|
|
print(f"Creating inventory items for {tenant.name}...")
|
|
|
|
# Get or create creator
|
|
try:
|
|
creators = User.objects.filter(tenant=tenant)
|
|
creator = random.choice(list(creators)) if creators.exists() else None
|
|
except Exception as e:
|
|
print(f"Warning: Could not get creator: {e}")
|
|
creator = None
|
|
|
|
# Create mix of common items and random items
|
|
items_to_create = COMMON_MEDICAL_ITEMS.copy()
|
|
|
|
# Add random items
|
|
for i in range(items_per_tenant - len(COMMON_MEDICAL_ITEMS)):
|
|
category = random.choice(SAUDI_MEDICAL_CATEGORIES)
|
|
subcategory = SAUDI_PHARMACEUTICAL_SUBCATEGORIES.get(category, [f"{category} Supplies"])[
|
|
0] if category in SAUDI_PHARMACEUTICAL_SUBCATEGORIES else f"{category} Supplies"
|
|
|
|
items_to_create.append({
|
|
'name': f"{category} Item {i + 1}",
|
|
'category': category,
|
|
'subcategory': subcategory,
|
|
'unit': random.choice(['PCS', 'BOX', 'VIAL', 'TAB', 'CAP', 'ML', 'GM']),
|
|
'controlled': random.choice([True, False]) if category == 'Pharmaceuticals' else False
|
|
})
|
|
|
|
for item_data in items_to_create[:items_per_tenant]:
|
|
item_code = generate_saudi_item_code()
|
|
|
|
# Ensure unique item code
|
|
counter = 1
|
|
original_code = item_code
|
|
try:
|
|
while InventoryItem.objects.filter(tenant=tenant, item_code=item_code).exists():
|
|
item_code = f"{original_code}-{counter}"
|
|
counter += 1
|
|
except Exception as e:
|
|
print(f"Warning: Could not check existing items: {e}")
|
|
|
|
manufacturer = random.choice(SAUDI_MEDICAL_MANUFACTURERS)
|
|
|
|
# Generate costs in SAR
|
|
unit_cost = Decimal(str(random.uniform(10, 1000)))
|
|
list_price = unit_cost * Decimal(str(random.uniform(1.2, 3.0)))
|
|
|
|
# Storage requirements
|
|
storage_temp_min = random.choice([2, 15, 20]) if random.choice([True, False]) else None
|
|
storage_temp_max = random.choice([8, 25, 30]) if storage_temp_min else None
|
|
|
|
# Controlled substance info
|
|
controlled = item_data.get('controlled', False)
|
|
dea_schedule = random.choice(['II', 'III', 'IV', 'V']) if controlled else None
|
|
|
|
try:
|
|
# Use atomic transaction instead of savepoint
|
|
with transaction.atomic():
|
|
item = InventoryItem.objects.create(
|
|
tenant=tenant,
|
|
item_code=item_code,
|
|
item_name=item_data['name'],
|
|
description=f"Medical supply item for {tenant.name}",
|
|
category=item_data['category'],
|
|
subcategory=item_data['subcategory'],
|
|
item_type=random.choice(['STOCK', 'NON_STOCK', 'SERVICE', 'KIT']),
|
|
manufacturer=manufacturer,
|
|
model_number=f"MOD-{random.randint(1000, 9999)}" if random.choice([True, False]) else None,
|
|
part_number=f"PN-{random.randint(100000, 999999)}" if random.choice([True, False]) else None,
|
|
upc_code=str(random.randint(100000000000, 999999999999)) if random.choice(
|
|
[True, False]) else None,
|
|
ndc_code=f"{random.randint(10000, 99999)}-{random.randint(100, 999)}-{random.randint(10, 99)}" if
|
|
item_data['category'] == 'Pharmaceuticals' else None,
|
|
unit_of_measure=item_data['unit'],
|
|
package_size=random.randint(1, 100),
|
|
package_type=random.choice(['BOTTLE', 'BOX', 'VIAL', 'TUBE', 'POUCH']),
|
|
unit_cost=unit_cost,
|
|
list_price=list_price,
|
|
storage_temperature_min=storage_temp_min,
|
|
storage_temperature_max=storage_temp_max,
|
|
storage_humidity_min=random.randint(30, 45) if random.choice([True, False]) else None,
|
|
storage_humidity_max=random.randint(55, 70) if random.choice([True, False]) else None,
|
|
storage_requirements="Store in cool, dry place" if random.choice([True, False]) else None,
|
|
has_expiration=True if item_data['category'] == 'Pharmaceuticals' else random.choice(
|
|
[True, False]),
|
|
shelf_life_days=random.randint(365, 1095) if item_data[
|
|
'category'] == 'Pharmaceuticals' else None,
|
|
fda_approved=random.choice([True, False]),
|
|
controlled_substance=controlled,
|
|
dea_schedule=dea_schedule,
|
|
is_active=True,
|
|
is_tracked=True,
|
|
is_serialized=random.choice([True, False]),
|
|
is_lot_tracked=True if item_data['category'] == 'Pharmaceuticals' else random.choice(
|
|
[True, False]),
|
|
reorder_point=random.randint(10, 100),
|
|
reorder_quantity=random.randint(50, 500),
|
|
max_stock_level=random.randint(200, 1000),
|
|
clinical_use=f"Used for {item_data['subcategory'].lower()} treatment" if random.choice(
|
|
[True, False]) else None,
|
|
notes=f"Saudi medical supply item - {item_data['name']}",
|
|
created_by=creator
|
|
)
|
|
items.append(item)
|
|
|
|
except Exception as e:
|
|
print(f"Error creating item {item_code}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(items)} inventory items")
|
|
return items
|
|
|
|
|
|
def create_saudi_inventory_stock(items, locations, stock_entries_per_item=2):
|
|
"""Create Saudi inventory stock entries"""
|
|
stocks = []
|
|
|
|
for item in items:
|
|
print(f"Creating stock for {item.item_name}...")
|
|
|
|
# Get locations for this tenant
|
|
tenant_locations = [loc for loc in locations if loc.tenant == item.tenant]
|
|
if not tenant_locations:
|
|
continue
|
|
|
|
for _ in range(min(stock_entries_per_item, len(tenant_locations))):
|
|
location = random.choice(tenant_locations)
|
|
|
|
# Generate stock details
|
|
lot_number = generate_saudi_lot_number() if item.is_lot_tracked else None
|
|
serial_number = f"SN{random.randint(100000, 999999)}" if item.is_serialized else None
|
|
|
|
quantity_on_hand = random.randint(0, 500)
|
|
quantity_reserved = random.randint(0, min(quantity_on_hand, 50))
|
|
|
|
# Dates
|
|
received_date = django_timezone.now().date() - timedelta(days=random.randint(1, 365))
|
|
expiration_date = received_date + timedelta(
|
|
days=item.shelf_life_days) if item.has_expiration and item.shelf_life_days else None
|
|
|
|
# Costs
|
|
unit_cost = item.unit_cost * Decimal(str(random.uniform(0.9, 1.1))) # Some variation
|
|
|
|
# Quality status
|
|
quality_status = 'AVAILABLE'
|
|
if expiration_date and expiration_date <= django_timezone.now().date():
|
|
quality_status = 'EXPIRED'
|
|
elif expiration_date and expiration_date <= django_timezone.now().date() + timedelta(days=30):
|
|
quality_status = 'EXPIRING_SOON'
|
|
elif random.choice([True, False, False, False]): # 25% chance
|
|
quality_status = random.choice(['QUARANTINED', 'DAMAGED', 'RECALLED'])
|
|
|
|
try:
|
|
# Use atomic transaction instead of savepoint
|
|
with transaction.atomic():
|
|
stock = InventoryStock.objects.create(
|
|
inventory_item=item,
|
|
location=location,
|
|
lot_number=lot_number,
|
|
serial_number=serial_number,
|
|
quantity_on_hand=quantity_on_hand,
|
|
quantity_reserved=quantity_reserved,
|
|
received_date=received_date,
|
|
expiration_date=expiration_date,
|
|
unit_cost=unit_cost,
|
|
quality_status=quality_status,
|
|
notes=f"Stock entry for {item.item_name} at {location.name}"
|
|
)
|
|
stocks.append(stock)
|
|
|
|
except Exception as e:
|
|
print(f"Error creating stock for {item.item_code}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(stocks)} stock entries")
|
|
return stocks
|
|
|
|
|
|
def create_saudi_purchase_orders(tenants, suppliers, orders_per_tenant=10):
|
|
"""Create Saudi purchase orders"""
|
|
orders = []
|
|
|
|
for tenant in tenants:
|
|
print(f"Creating purchase orders for {tenant.name}...")
|
|
|
|
# Get suppliers for this tenant
|
|
tenant_suppliers = [supplier for supplier in suppliers if supplier.tenant == tenant]
|
|
if not tenant_suppliers:
|
|
print(f"No suppliers found for {tenant.name}, skipping purchase orders...")
|
|
continue
|
|
|
|
# Get users who can create POs
|
|
try:
|
|
requesters = User.objects.filter(tenant=tenant)
|
|
approvers = User.objects.filter(tenant=tenant)
|
|
|
|
requester = random.choice(list(requesters)) if requesters.exists() else None
|
|
approver = random.choice(list(approvers)) if approvers.exists() else None
|
|
except Exception as e:
|
|
print(f"Warning: Could not get users: {e}")
|
|
requester = None
|
|
approver = None
|
|
|
|
# Get delivery locations
|
|
try:
|
|
locations = InventoryLocation.objects.filter(tenant=tenant)
|
|
delivery_location = random.choice(list(locations)) if locations.exists() else None
|
|
except Exception as e:
|
|
print(f"Warning: Could not get locations: {e}")
|
|
delivery_location = None
|
|
|
|
for i in range(orders_per_tenant):
|
|
po_number = generate_saudi_po_number()
|
|
|
|
# Ensure unique PO number
|
|
counter = 1
|
|
original_po = po_number
|
|
try:
|
|
while PurchaseOrder.objects.filter(tenant=tenant, po_number=po_number).exists():
|
|
po_number = f"{original_po}-{counter}"
|
|
counter += 1
|
|
except Exception as e:
|
|
print(f"Warning: Could not check existing POs: {e}")
|
|
|
|
supplier = random.choice(tenant_suppliers)
|
|
|
|
# Generate dates
|
|
order_date = django_timezone.now().date() - timedelta(days=random.randint(1, 90))
|
|
requested_delivery = order_date + timedelta(days=random.randint(7, 30))
|
|
|
|
# Determine status
|
|
status = random.choices(
|
|
['DRAFT', 'PENDING_APPROVAL', 'APPROVED', 'SENT', 'PARTIAL_RECEIVED', 'RECEIVED', 'CANCELLED'],
|
|
weights=[10, 15, 10, 20, 15, 25, 5]
|
|
)[0]
|
|
|
|
# Generate amounts in SAR
|
|
subtotal = Decimal(str(random.uniform(1000, 50000)))
|
|
tax_rate = Decimal('0.15') # 15% VAT in Saudi Arabia
|
|
tax_amount = subtotal * tax_rate
|
|
shipping_amount = Decimal(str(random.uniform(100, 1000)))
|
|
total_amount = subtotal + tax_amount + shipping_amount
|
|
|
|
try:
|
|
# Use atomic transaction instead of savepoint
|
|
with transaction.atomic():
|
|
order = PurchaseOrder.objects.create(
|
|
tenant=tenant,
|
|
po_number=po_number,
|
|
supplier=supplier, # Now using the Supplier object
|
|
order_date=order_date,
|
|
requested_delivery_date=requested_delivery,
|
|
promised_delivery_date=requested_delivery + timedelta(
|
|
days=random.randint(0, 7)) if status != 'DRAFT' else None,
|
|
actual_delivery_date=requested_delivery + timedelta(days=random.randint(-3, 10)) if status in [
|
|
'RECEIVED', 'PARTIAL_RECEIVED'] else None,
|
|
order_type=random.choice(['STANDARD', 'RUSH', 'EMERGENCY', 'BLANKET']),
|
|
priority=random.choice(['LOW', 'NORMAL', 'HIGH', 'URGENT']),
|
|
subtotal=subtotal,
|
|
tax_amount=tax_amount,
|
|
shipping_amount=shipping_amount,
|
|
total_amount=total_amount,
|
|
status=status,
|
|
delivery_location=delivery_location,
|
|
delivery_instructions=f"Deliver to {delivery_location.name} during business hours" if delivery_location else None,
|
|
payment_terms=random.choice(['NET_30', 'NET_60', 'COD', 'PREPAID']),
|
|
requested_by=requester,
|
|
approved_by=approver if status not in ['DRAFT', 'PENDING_APPROVAL'] else None,
|
|
approval_date=order_date + timedelta(days=random.randint(1, 5)) if status not in ['DRAFT',
|
|
'PENDING_APPROVAL'] else None,
|
|
notes=f"Purchase order for {tenant.name} medical supplies",
|
|
created_by=requester
|
|
)
|
|
orders.append(order)
|
|
|
|
except Exception as e:
|
|
print(f"Error creating PO {po_number}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(orders)} purchase orders")
|
|
return orders
|
|
|
|
|
|
def create_saudi_purchase_order_items(orders, items):
|
|
"""Create Saudi purchase order items"""
|
|
po_items = []
|
|
|
|
for order in orders:
|
|
print(f"Creating items for PO {order.po_number}...")
|
|
|
|
# Get items for this tenant
|
|
tenant_items = [item for item in items if item.tenant == order.tenant]
|
|
if not tenant_items:
|
|
continue
|
|
|
|
# Create 1-10 items per order
|
|
num_items = random.randint(1, min(10, len(tenant_items)))
|
|
selected_items = random.sample(tenant_items, num_items)
|
|
|
|
for line_num, item in enumerate(selected_items, 1):
|
|
quantity_ordered = random.randint(10, 500)
|
|
quantity_received = 0
|
|
|
|
if order.status in ['PARTIAL_RECEIVED', 'RECEIVED']:
|
|
if order.status == 'RECEIVED':
|
|
quantity_received = quantity_ordered
|
|
else:
|
|
quantity_received = random.randint(0, quantity_ordered)
|
|
|
|
# Price variations
|
|
unit_price = item.unit_cost * Decimal(str(random.uniform(0.8, 1.2)))
|
|
total_price = unit_price * quantity_ordered
|
|
|
|
status = 'PENDING'
|
|
if order.status == 'RECEIVED':
|
|
status = 'RECEIVED'
|
|
elif order.status == 'PARTIAL_RECEIVED':
|
|
status = 'PARTIAL_RECEIVED' if quantity_received < quantity_ordered else 'RECEIVED'
|
|
elif order.status == 'CANCELLED':
|
|
status = 'CANCELLED'
|
|
|
|
try:
|
|
# Use atomic transaction instead of savepoint
|
|
with transaction.atomic():
|
|
po_item = PurchaseOrderItem.objects.create(
|
|
purchase_order=order,
|
|
line_number=line_num,
|
|
inventory_item=item,
|
|
quantity_ordered=quantity_ordered,
|
|
quantity_received=quantity_received,
|
|
unit_price=unit_price,
|
|
total_price=total_price,
|
|
requested_delivery_date=order.requested_delivery_date,
|
|
status=status,
|
|
notes=f"PO item for {item.item_name}"
|
|
)
|
|
po_items.append(po_item)
|
|
|
|
except Exception as e:
|
|
print(f"Error creating PO item for {item.item_code}: {e}")
|
|
continue
|
|
|
|
print(f"Created {len(po_items)} purchase order items")
|
|
return po_items
|
|
|
|
|
|
def main():
|
|
"""Main function to create all Saudi inventory data"""
|
|
print("Starting Saudi inventory data generation...")
|
|
|
|
# Test database connection first
|
|
if not test_database_connection():
|
|
print("Database connection failed. Please check your database configuration.")
|
|
return
|
|
|
|
# Get tenants
|
|
try:
|
|
tenants = list(Tenant.objects.filter(is_active=True))
|
|
if not tenants:
|
|
print("No active tenants found. Please create tenants first.")
|
|
return
|
|
|
|
print(f"Found {len(tenants)} active tenants")
|
|
except Exception as e:
|
|
print(f"Error getting tenants: {e}")
|
|
return
|
|
|
|
# Create suppliers first (required for purchase orders)
|
|
suppliers = create_saudi_suppliers(tenants)
|
|
|
|
# Create inventory locations
|
|
locations = create_saudi_inventory_locations(tenants, locations_per_tenant=15)
|
|
|
|
# Create inventory items
|
|
items = create_saudi_inventory_items(tenants, items_per_tenant=50)
|
|
|
|
# Create inventory stock
|
|
stocks = create_saudi_inventory_stock(items, locations, stock_entries_per_item=2)
|
|
|
|
# Create purchase orders
|
|
orders = create_saudi_purchase_orders(tenants, suppliers, orders_per_tenant=8)
|
|
|
|
# Create purchase order items
|
|
po_items = create_saudi_purchase_order_items(orders, items)
|
|
|
|
print("\n=== Saudi Inventory Data Generation Complete ===")
|
|
print(f"Created {len(suppliers)} suppliers")
|
|
print(f"Created {len(locations)} inventory locations")
|
|
print(f"Created {len(items)} inventory items")
|
|
print(f"Created {len(stocks)} stock entries")
|
|
print(f"Created {len(orders)} purchase orders")
|
|
print(f"Created {len(po_items)} purchase order items")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |