1568 lines
58 KiB
Python
1568 lines
58 KiB
Python
# Generated by Django 5.2.6 on 2025-09-19 10:58
|
||
|
||
import django.core.validators
|
||
import django.db.models.deletion
|
||
import django.utils.timezone
|
||
import uuid
|
||
from decimal import Decimal
|
||
from django.conf import settings
|
||
from django.db import migrations, models
|
||
|
||
|
||
class Migration(migrations.Migration):
|
||
|
||
initial = True
|
||
|
||
dependencies = [
|
||
("core", "0001_initial"),
|
||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||
]
|
||
|
||
operations = [
|
||
migrations.CreateModel(
|
||
name="InventoryItem",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"item_id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
help_text="Unique item identifier",
|
||
unique=True,
|
||
),
|
||
),
|
||
(
|
||
"item_code",
|
||
models.CharField(help_text="Internal item code", max_length=50),
|
||
),
|
||
("item_name", models.CharField(help_text="Item name", max_length=200)),
|
||
(
|
||
"description",
|
||
models.TextField(
|
||
blank=True, help_text="Item description", null=True
|
||
),
|
||
),
|
||
(
|
||
"category",
|
||
models.CharField(
|
||
choices=[
|
||
("MEDICAL_SUPPLIES", "Medical Supplies"),
|
||
("PHARMACEUTICALS", "Pharmaceuticals"),
|
||
("SURGICAL_INSTRUMENTS", "Surgical Instruments"),
|
||
("DIAGNOSTIC_EQUIPMENT", "Diagnostic Equipment"),
|
||
("PATIENT_CARE", "Patient Care Equipment"),
|
||
("LABORATORY_SUPPLIES", "Laboratory Supplies"),
|
||
("RADIOLOGY_SUPPLIES", "Radiology Supplies"),
|
||
("OFFICE_SUPPLIES", "Office Supplies"),
|
||
("MAINTENANCE_SUPPLIES", "Maintenance Supplies"),
|
||
("FOOD_NUTRITION", "Food & Nutrition"),
|
||
("LINENS_TEXTILES", "Linens & Textiles"),
|
||
("CLEANING_SUPPLIES", "Cleaning Supplies"),
|
||
("SAFETY_EQUIPMENT", "Safety Equipment"),
|
||
("IT_EQUIPMENT", "IT Equipment"),
|
||
("FURNITURE", "Furniture"),
|
||
("OTHER", "Other"),
|
||
],
|
||
help_text="Item category",
|
||
max_length=30,
|
||
),
|
||
),
|
||
(
|
||
"subcategory",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Item subcategory",
|
||
max_length=50,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"item_type",
|
||
models.CharField(
|
||
choices=[
|
||
("CONSUMABLE", "Consumable"),
|
||
("REUSABLE", "Reusable"),
|
||
("EQUIPMENT", "Equipment"),
|
||
("MEDICATION", "Medication"),
|
||
("IMPLANT", "Implant"),
|
||
("DEVICE", "Medical Device"),
|
||
("SUPPLY", "Supply"),
|
||
("ASSET", "Asset"),
|
||
],
|
||
help_text="Item type",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"manufacturer",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Manufacturer name",
|
||
max_length=100,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"model_number",
|
||
models.CharField(
|
||
blank=True, help_text="Model number", max_length=50, null=True
|
||
),
|
||
),
|
||
(
|
||
"part_number",
|
||
models.CharField(
|
||
blank=True, help_text="Part number", max_length=50, null=True
|
||
),
|
||
),
|
||
(
|
||
"upc_code",
|
||
models.CharField(
|
||
blank=True, help_text="UPC barcode", max_length=20, null=True
|
||
),
|
||
),
|
||
(
|
||
"ndc_code",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="National Drug Code (for medications)",
|
||
max_length=20,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"gtin_code",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Global Trade Item Number",
|
||
max_length=20,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"unit_of_measure",
|
||
models.CharField(
|
||
choices=[
|
||
("EACH", "Each"),
|
||
("BOX", "Box"),
|
||
("CASE", "Case"),
|
||
("BOTTLE", "Bottle"),
|
||
("VIAL", "Vial"),
|
||
("TUBE", "Tube"),
|
||
("PACK", "Pack"),
|
||
("KIT", "Kit"),
|
||
("ROLL", "Roll"),
|
||
("SHEET", "Sheet"),
|
||
("POUND", "Pound"),
|
||
("KILOGRAM", "Kilogram"),
|
||
("LITER", "Liter"),
|
||
("MILLILITER", "Milliliter"),
|
||
("METER", "Meter"),
|
||
("FOOT", "Foot"),
|
||
],
|
||
default="EACH",
|
||
help_text="Unit of measure",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"package_size",
|
||
models.PositiveIntegerField(
|
||
default=1, help_text="Number of units per package"
|
||
),
|
||
),
|
||
(
|
||
"package_type",
|
||
models.CharField(
|
||
choices=[
|
||
("INDIVIDUAL", "Individual"),
|
||
("BULK", "Bulk"),
|
||
("STERILE", "Sterile Package"),
|
||
("NON_STERILE", "Non-Sterile Package"),
|
||
],
|
||
default="INDIVIDUAL",
|
||
help_text="Package type",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"unit_cost",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Unit cost",
|
||
max_digits=10,
|
||
),
|
||
),
|
||
(
|
||
"list_price",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="List price",
|
||
max_digits=10,
|
||
),
|
||
),
|
||
(
|
||
"storage_temperature_min",
|
||
models.DecimalField(
|
||
blank=True,
|
||
decimal_places=1,
|
||
help_text="Minimum storage temperature (Celsius)",
|
||
max_digits=5,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"storage_temperature_max",
|
||
models.DecimalField(
|
||
blank=True,
|
||
decimal_places=1,
|
||
help_text="Maximum storage temperature (Celsius)",
|
||
max_digits=5,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"storage_humidity_min",
|
||
models.PositiveIntegerField(
|
||
blank=True,
|
||
help_text="Minimum storage humidity (%)",
|
||
null=True,
|
||
validators=[django.core.validators.MaxValueValidator(100)],
|
||
),
|
||
),
|
||
(
|
||
"storage_humidity_max",
|
||
models.PositiveIntegerField(
|
||
blank=True,
|
||
help_text="Maximum storage humidity (%)",
|
||
null=True,
|
||
validators=[django.core.validators.MaxValueValidator(100)],
|
||
),
|
||
),
|
||
(
|
||
"storage_requirements",
|
||
models.TextField(
|
||
blank=True, help_text="Special storage requirements", null=True
|
||
),
|
||
),
|
||
(
|
||
"has_expiration",
|
||
models.BooleanField(
|
||
default=False, help_text="Item has expiration date"
|
||
),
|
||
),
|
||
(
|
||
"shelf_life_days",
|
||
models.PositiveIntegerField(
|
||
blank=True, help_text="Shelf life in days", null=True
|
||
),
|
||
),
|
||
(
|
||
"fda_approved",
|
||
models.BooleanField(default=False, help_text="FDA approved"),
|
||
),
|
||
(
|
||
"controlled_substance",
|
||
models.BooleanField(
|
||
default=False, help_text="Controlled substance"
|
||
),
|
||
),
|
||
(
|
||
"dea_schedule",
|
||
models.CharField(
|
||
blank=True,
|
||
choices=[
|
||
("CI", "Schedule I"),
|
||
("CII", "Schedule II"),
|
||
("CIII", "Schedule III"),
|
||
("CIV", "Schedule IV"),
|
||
("CV", "Schedule V"),
|
||
],
|
||
help_text="DEA schedule (for controlled substances)",
|
||
max_length=5,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"is_active",
|
||
models.BooleanField(default=True, help_text="Item is active"),
|
||
),
|
||
(
|
||
"is_tracked",
|
||
models.BooleanField(
|
||
default=True, help_text="Track inventory levels"
|
||
),
|
||
),
|
||
(
|
||
"is_serialized",
|
||
models.BooleanField(
|
||
default=False, help_text="Track by serial number"
|
||
),
|
||
),
|
||
(
|
||
"is_lot_tracked",
|
||
models.BooleanField(default=False, help_text="Track by lot number"),
|
||
),
|
||
(
|
||
"reorder_point",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Reorder point quantity"
|
||
),
|
||
),
|
||
(
|
||
"reorder_quantity",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Reorder quantity"
|
||
),
|
||
),
|
||
(
|
||
"min_stock_level",
|
||
models.PositiveIntegerField(
|
||
blank=True, help_text="Minimum stock level", null=True
|
||
),
|
||
),
|
||
(
|
||
"max_stock_level",
|
||
models.PositiveIntegerField(
|
||
blank=True, help_text="Maximum stock level", null=True
|
||
),
|
||
),
|
||
(
|
||
"clinical_use",
|
||
models.TextField(
|
||
blank=True, help_text="Clinical use and indications", null=True
|
||
),
|
||
),
|
||
(
|
||
"contraindications",
|
||
models.TextField(
|
||
blank=True,
|
||
help_text="Contraindications and warnings",
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"notes",
|
||
models.TextField(
|
||
blank=True, help_text="Additional notes", null=True
|
||
),
|
||
),
|
||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||
("updated_at", models.DateTimeField(auto_now=True)),
|
||
(
|
||
"created_by",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="User who created the item",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="created_inventory_items",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"tenant",
|
||
models.ForeignKey(
|
||
help_text="Organization tenant",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="inventory_items",
|
||
to="core.tenant",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Inventory Item",
|
||
"verbose_name_plural": "Inventory Items",
|
||
"db_table": "inventory_item",
|
||
"ordering": ["item_name"],
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="InventoryLocation",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"location_id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
help_text="Unique location identifier",
|
||
unique=True,
|
||
),
|
||
),
|
||
(
|
||
"location_code",
|
||
models.CharField(help_text="Location code", max_length=20),
|
||
),
|
||
("name", models.CharField(help_text="Location name", max_length=100)),
|
||
(
|
||
"description",
|
||
models.TextField(
|
||
blank=True, help_text="Location description", null=True
|
||
),
|
||
),
|
||
(
|
||
"location_type",
|
||
models.CharField(
|
||
choices=[
|
||
("WAREHOUSE", "Warehouse"),
|
||
("STOREROOM", "Storeroom"),
|
||
("PHARMACY", "Pharmacy"),
|
||
("NURSING_UNIT", "Nursing Unit"),
|
||
("OR_STORAGE", "OR Storage"),
|
||
("LAB_STORAGE", "Lab Storage"),
|
||
("RADIOLOGY", "Radiology Storage"),
|
||
("CENTRAL_SUPPLY", "Central Supply"),
|
||
("REFRIGERATOR", "Refrigerator"),
|
||
("FREEZER", "Freezer"),
|
||
("CONTROLLED", "Controlled Substance"),
|
||
("QUARANTINE", "Quarantine"),
|
||
("RECEIVING", "Receiving"),
|
||
("SHIPPING", "Shipping"),
|
||
("OTHER", "Other"),
|
||
],
|
||
help_text="Location type",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"building",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Building name/number",
|
||
max_length=50,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"floor",
|
||
models.CharField(
|
||
blank=True, help_text="Floor", max_length=10, null=True
|
||
),
|
||
),
|
||
(
|
||
"room",
|
||
models.CharField(
|
||
blank=True, help_text="Room number", max_length=20, null=True
|
||
),
|
||
),
|
||
(
|
||
"zone",
|
||
models.CharField(
|
||
blank=True, help_text="Zone or area", max_length=20, null=True
|
||
),
|
||
),
|
||
(
|
||
"aisle",
|
||
models.CharField(
|
||
blank=True, help_text="Aisle", max_length=10, null=True
|
||
),
|
||
),
|
||
(
|
||
"shelf",
|
||
models.CharField(
|
||
blank=True, help_text="Shelf", max_length=10, null=True
|
||
),
|
||
),
|
||
(
|
||
"bin",
|
||
models.CharField(
|
||
blank=True, help_text="Bin location", max_length=10, null=True
|
||
),
|
||
),
|
||
(
|
||
"capacity_cubic_feet",
|
||
models.DecimalField(
|
||
blank=True,
|
||
decimal_places=2,
|
||
help_text="Capacity in cubic feet",
|
||
max_digits=10,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"max_weight_pounds",
|
||
models.DecimalField(
|
||
blank=True,
|
||
decimal_places=2,
|
||
help_text="Maximum weight in pounds",
|
||
max_digits=10,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"temperature_controlled",
|
||
models.BooleanField(
|
||
default=False, help_text="Temperature controlled"
|
||
),
|
||
),
|
||
(
|
||
"temperature_min",
|
||
models.DecimalField(
|
||
blank=True,
|
||
decimal_places=1,
|
||
help_text="Minimum temperature (Celsius)",
|
||
max_digits=5,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"temperature_max",
|
||
models.DecimalField(
|
||
blank=True,
|
||
decimal_places=1,
|
||
help_text="Maximum temperature (Celsius)",
|
||
max_digits=5,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"humidity_controlled",
|
||
models.BooleanField(default=False, help_text="Humidity controlled"),
|
||
),
|
||
(
|
||
"humidity_min",
|
||
models.PositiveIntegerField(
|
||
blank=True,
|
||
help_text="Minimum humidity (%)",
|
||
null=True,
|
||
validators=[django.core.validators.MaxValueValidator(100)],
|
||
),
|
||
),
|
||
(
|
||
"humidity_max",
|
||
models.PositiveIntegerField(
|
||
blank=True,
|
||
help_text="Maximum humidity (%)",
|
||
null=True,
|
||
validators=[django.core.validators.MaxValueValidator(100)],
|
||
),
|
||
),
|
||
(
|
||
"secure_location",
|
||
models.BooleanField(
|
||
default=False, help_text="Secure/restricted access location"
|
||
),
|
||
),
|
||
(
|
||
"access_control",
|
||
models.CharField(
|
||
choices=[
|
||
("OPEN", "Open Access"),
|
||
("BADGE", "Badge Access"),
|
||
("KEY", "Key Access"),
|
||
("BIOMETRIC", "Biometric Access"),
|
||
("DUAL_CONTROL", "Dual Control"),
|
||
],
|
||
default="OPEN",
|
||
help_text="Access control method",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"is_active",
|
||
models.BooleanField(default=True, help_text="Location is active"),
|
||
),
|
||
(
|
||
"notes",
|
||
models.TextField(blank=True, help_text="Location notes", null=True),
|
||
),
|
||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||
("updated_at", models.DateTimeField(auto_now=True)),
|
||
(
|
||
"created_by",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="User who created the location",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="created_inventory_locations",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"location_manager",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="Location manager",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="managed_locations",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"parent_location",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="Parent location",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="child_locations",
|
||
to="inventory.inventorylocation",
|
||
),
|
||
),
|
||
(
|
||
"tenant",
|
||
models.ForeignKey(
|
||
help_text="Organization tenant",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="inventory_locations",
|
||
to="core.tenant",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Inventory Location",
|
||
"verbose_name_plural": "Inventory Locations",
|
||
"db_table": "inventory_location",
|
||
"ordering": ["location_code"],
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="PurchaseOrder",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"po_id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
help_text="Unique purchase order identifier",
|
||
unique=True,
|
||
),
|
||
),
|
||
(
|
||
"po_number",
|
||
models.CharField(
|
||
help_text="Purchase order number", max_length=20, unique=True
|
||
),
|
||
),
|
||
(
|
||
"order_date",
|
||
models.DateField(
|
||
default=django.utils.timezone.now, help_text="Order date"
|
||
),
|
||
),
|
||
(
|
||
"requested_delivery_date",
|
||
models.DateField(
|
||
blank=True, help_text="Requested delivery date", null=True
|
||
),
|
||
),
|
||
(
|
||
"promised_delivery_date",
|
||
models.DateField(
|
||
blank=True, help_text="Promised delivery date", null=True
|
||
),
|
||
),
|
||
(
|
||
"actual_delivery_date",
|
||
models.DateField(
|
||
blank=True, help_text="Actual delivery date", null=True
|
||
),
|
||
),
|
||
(
|
||
"order_type",
|
||
models.CharField(
|
||
choices=[
|
||
("STANDARD", "Standard Order"),
|
||
("RUSH", "Rush Order"),
|
||
("EMERGENCY", "Emergency Order"),
|
||
("BLANKET", "Blanket Order"),
|
||
("CONTRACT", "Contract Order"),
|
||
("CONSIGNMENT", "Consignment"),
|
||
],
|
||
default="STANDARD",
|
||
help_text="Order type",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"priority",
|
||
models.CharField(
|
||
choices=[
|
||
("LOW", "Low"),
|
||
("NORMAL", "Normal"),
|
||
("HIGH", "High"),
|
||
("URGENT", "Urgent"),
|
||
],
|
||
default="NORMAL",
|
||
help_text="Order priority",
|
||
max_length=10,
|
||
),
|
||
),
|
||
(
|
||
"subtotal",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Subtotal amount",
|
||
max_digits=12,
|
||
),
|
||
),
|
||
(
|
||
"tax_amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Tax amount",
|
||
max_digits=12,
|
||
),
|
||
),
|
||
(
|
||
"shipping_amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Shipping amount",
|
||
max_digits=12,
|
||
),
|
||
),
|
||
(
|
||
"total_amount",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Total order amount",
|
||
max_digits=12,
|
||
),
|
||
),
|
||
(
|
||
"status",
|
||
models.CharField(
|
||
choices=[
|
||
("DRAFT", "Draft"),
|
||
("PENDING_APPROVAL", "Pending Approval"),
|
||
("APPROVED", "Approved"),
|
||
("SENT", "Sent to Supplier"),
|
||
("ACKNOWLEDGED", "Acknowledged"),
|
||
("PARTIAL_RECEIVED", "Partially Received"),
|
||
("RECEIVED", "Received"),
|
||
("INVOICED", "Invoiced"),
|
||
("PAID", "Paid"),
|
||
("CANCELLED", "Cancelled"),
|
||
("CLOSED", "Closed"),
|
||
],
|
||
default="DRAFT",
|
||
help_text="Order status",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"delivery_instructions",
|
||
models.TextField(
|
||
blank=True, help_text="Delivery instructions", null=True
|
||
),
|
||
),
|
||
(
|
||
"payment_terms",
|
||
models.CharField(
|
||
choices=[
|
||
("NET_30", "Net 30 Days"),
|
||
("NET_60", "Net 60 Days"),
|
||
("NET_90", "Net 90 Days"),
|
||
("COD", "Cash on Delivery"),
|
||
("PREPAID", "Prepaid"),
|
||
("CREDIT_CARD", "Credit Card"),
|
||
],
|
||
default="NET_30",
|
||
help_text="Payment terms",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"approval_date",
|
||
models.DateTimeField(
|
||
blank=True, help_text="Approval date and time", null=True
|
||
),
|
||
),
|
||
(
|
||
"notes",
|
||
models.TextField(
|
||
blank=True, help_text="Order notes and comments", null=True
|
||
),
|
||
),
|
||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||
("updated_at", models.DateTimeField(auto_now=True)),
|
||
(
|
||
"approved_by",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="User who approved the order",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="approved_purchase_orders",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"created_by",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="User who created the order",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="created_purchase_orders",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"delivery_location",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="Delivery location",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="purchase_orders",
|
||
to="inventory.inventorylocation",
|
||
),
|
||
),
|
||
(
|
||
"requested_by",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="User who requested the order",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="requested_purchase_orders",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"tenant",
|
||
models.ForeignKey(
|
||
help_text="Organization tenant",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="purchase_orders",
|
||
to="core.tenant",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Purchase Order",
|
||
"verbose_name_plural": "Purchase Orders",
|
||
"db_table": "inventory_purchase_order",
|
||
"ordering": ["-order_date"],
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="PurchaseOrderItem",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"item_id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
help_text="Unique item identifier",
|
||
unique=True,
|
||
),
|
||
),
|
||
(
|
||
"line_number",
|
||
models.PositiveIntegerField(help_text="Line item number"),
|
||
),
|
||
(
|
||
"quantity_ordered",
|
||
models.PositiveIntegerField(help_text="Quantity ordered"),
|
||
),
|
||
(
|
||
"quantity_received",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Quantity received"
|
||
),
|
||
),
|
||
(
|
||
"quantity_remaining",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Quantity remaining to receive"
|
||
),
|
||
),
|
||
(
|
||
"unit_price",
|
||
models.DecimalField(
|
||
decimal_places=2, help_text="Unit price", max_digits=10
|
||
),
|
||
),
|
||
(
|
||
"total_price",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
help_text="Total price (quantity × unit price)",
|
||
max_digits=12,
|
||
),
|
||
),
|
||
(
|
||
"requested_delivery_date",
|
||
models.DateField(
|
||
blank=True,
|
||
help_text="Requested delivery date for this item",
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"status",
|
||
models.CharField(
|
||
choices=[
|
||
("PENDING", "Pending"),
|
||
("ORDERED", "Ordered"),
|
||
("PARTIAL_RECEIVED", "Partially Received"),
|
||
("RECEIVED", "Received"),
|
||
("CANCELLED", "Cancelled"),
|
||
],
|
||
default="PENDING",
|
||
help_text="Item status",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"notes",
|
||
models.TextField(blank=True, help_text="Item notes", null=True),
|
||
),
|
||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||
("updated_at", models.DateTimeField(auto_now=True)),
|
||
(
|
||
"inventory_item",
|
||
models.ForeignKey(
|
||
help_text="Inventory item",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="purchase_order_items",
|
||
to="inventory.inventoryitem",
|
||
),
|
||
),
|
||
(
|
||
"purchase_order",
|
||
models.ForeignKey(
|
||
help_text="Purchase order",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="line_items",
|
||
to="inventory.purchaseorder",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Purchase Order Item",
|
||
"verbose_name_plural": "Purchase Order Items",
|
||
"db_table": "inventory_purchase_order_item",
|
||
"ordering": ["line_number"],
|
||
},
|
||
),
|
||
migrations.CreateModel(
|
||
name="Supplier",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"supplier_id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
help_text="Unique supplier identifier",
|
||
unique=True,
|
||
),
|
||
),
|
||
(
|
||
"supplier_code",
|
||
models.CharField(help_text="Supplier code", max_length=20),
|
||
),
|
||
("name", models.CharField(help_text="Supplier name", max_length=200)),
|
||
(
|
||
"supplier_type",
|
||
models.CharField(
|
||
choices=[
|
||
("MANUFACTURER", "Manufacturer"),
|
||
("DISTRIBUTOR", "Distributor"),
|
||
("WHOLESALER", "Wholesaler"),
|
||
("RETAILER", "Retailer"),
|
||
("SERVICE", "Service Provider"),
|
||
("CONSULTANT", "Consultant"),
|
||
("OTHER", "Other"),
|
||
],
|
||
help_text="Supplier type",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"contact_person",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Primary contact person",
|
||
max_length=100,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"phone",
|
||
models.CharField(
|
||
blank=True, help_text="Phone number", max_length=20, null=True
|
||
),
|
||
),
|
||
(
|
||
"email",
|
||
models.EmailField(
|
||
blank=True, help_text="Email address", max_length=254, null=True
|
||
),
|
||
),
|
||
(
|
||
"website",
|
||
models.URLField(blank=True, help_text="Website URL", null=True),
|
||
),
|
||
(
|
||
"address_line_1",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Address line 1",
|
||
max_length=100,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"address_line_2",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Address line 2",
|
||
max_length=100,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"city",
|
||
models.CharField(
|
||
blank=True, help_text="City", max_length=50, null=True
|
||
),
|
||
),
|
||
(
|
||
"state",
|
||
models.CharField(
|
||
blank=True, help_text="State/Province", max_length=50, null=True
|
||
),
|
||
),
|
||
(
|
||
"postal_code",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Postal/ZIP code",
|
||
max_length=20,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"country",
|
||
models.CharField(
|
||
blank=True, help_text="Country", max_length=50, null=True
|
||
),
|
||
),
|
||
(
|
||
"tax_id",
|
||
models.CharField(
|
||
blank=True, help_text="Tax ID/EIN", max_length=20, null=True
|
||
),
|
||
),
|
||
(
|
||
"duns_number",
|
||
models.CharField(
|
||
blank=True, help_text="DUNS number", max_length=20, null=True
|
||
),
|
||
),
|
||
(
|
||
"payment_terms",
|
||
models.CharField(
|
||
choices=[
|
||
("NET_30", "Net 30 Days"),
|
||
("NET_60", "Net 60 Days"),
|
||
("NET_90", "Net 90 Days"),
|
||
("COD", "Cash on Delivery"),
|
||
("PREPAID", "Prepaid"),
|
||
("CREDIT_CARD", "Credit Card"),
|
||
],
|
||
default="NET_30",
|
||
help_text="Default payment terms",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"performance_rating",
|
||
models.DecimalField(
|
||
decimal_places=1,
|
||
default=Decimal("0.0"),
|
||
help_text="Performance rating (0-5)",
|
||
max_digits=3,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(0),
|
||
django.core.validators.MaxValueValidator(5),
|
||
],
|
||
),
|
||
),
|
||
(
|
||
"on_time_delivery_rate",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="On-time delivery rate (%)",
|
||
max_digits=5,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(0),
|
||
django.core.validators.MaxValueValidator(100),
|
||
],
|
||
),
|
||
),
|
||
(
|
||
"quality_rating",
|
||
models.DecimalField(
|
||
decimal_places=1,
|
||
default=Decimal("0.0"),
|
||
help_text="Quality rating (0-5)",
|
||
max_digits=3,
|
||
validators=[
|
||
django.core.validators.MinValueValidator(0),
|
||
django.core.validators.MaxValueValidator(5),
|
||
],
|
||
),
|
||
),
|
||
(
|
||
"is_active",
|
||
models.BooleanField(default=True, help_text="Supplier is active"),
|
||
),
|
||
(
|
||
"is_preferred",
|
||
models.BooleanField(default=False, help_text="Preferred supplier"),
|
||
),
|
||
(
|
||
"certifications",
|
||
models.JSONField(default=list, help_text="Supplier certifications"),
|
||
),
|
||
(
|
||
"contract_start_date",
|
||
models.DateField(
|
||
blank=True, help_text="Contract start date", null=True
|
||
),
|
||
),
|
||
(
|
||
"contract_end_date",
|
||
models.DateField(
|
||
blank=True, help_text="Contract end date", null=True
|
||
),
|
||
),
|
||
(
|
||
"notes",
|
||
models.TextField(blank=True, help_text="Supplier notes", null=True),
|
||
),
|
||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||
("updated_at", models.DateTimeField(auto_now=True)),
|
||
(
|
||
"created_by",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="User who created the supplier",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="created_suppliers",
|
||
to=settings.AUTH_USER_MODEL,
|
||
),
|
||
),
|
||
(
|
||
"tenant",
|
||
models.ForeignKey(
|
||
help_text="Organization tenant",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="suppliers",
|
||
to="core.tenant",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Supplier",
|
||
"verbose_name_plural": "Suppliers",
|
||
"db_table": "inventory_supplier",
|
||
"ordering": ["name"],
|
||
},
|
||
),
|
||
migrations.AddField(
|
||
model_name="purchaseorder",
|
||
name="supplier",
|
||
field=models.ForeignKey(
|
||
help_text="Supplier",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="purchase_orders",
|
||
to="inventory.supplier",
|
||
),
|
||
),
|
||
migrations.CreateModel(
|
||
name="InventoryStock",
|
||
fields=[
|
||
(
|
||
"id",
|
||
models.BigAutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name="ID",
|
||
),
|
||
),
|
||
(
|
||
"stock_id",
|
||
models.UUIDField(
|
||
default=uuid.uuid4,
|
||
editable=False,
|
||
help_text="Unique stock identifier",
|
||
unique=True,
|
||
),
|
||
),
|
||
(
|
||
"lot_number",
|
||
models.CharField(
|
||
blank=True,
|
||
help_text="Lot/batch number",
|
||
max_length=50,
|
||
null=True,
|
||
),
|
||
),
|
||
(
|
||
"serial_number",
|
||
models.CharField(
|
||
blank=True, help_text="Serial number", max_length=50, null=True
|
||
),
|
||
),
|
||
(
|
||
"quantity_on_hand",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Quantity on hand"
|
||
),
|
||
),
|
||
(
|
||
"quantity_reserved",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Quantity reserved"
|
||
),
|
||
),
|
||
(
|
||
"quantity_available",
|
||
models.PositiveIntegerField(
|
||
default=0, help_text="Quantity available (on hand - reserved)"
|
||
),
|
||
),
|
||
(
|
||
"received_date",
|
||
models.DateField(blank=True, help_text="Date received", null=True),
|
||
),
|
||
(
|
||
"expiration_date",
|
||
models.DateField(
|
||
blank=True, help_text="Expiration date", null=True
|
||
),
|
||
),
|
||
(
|
||
"unit_cost",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Unit cost for this lot",
|
||
max_digits=10,
|
||
),
|
||
),
|
||
(
|
||
"total_cost",
|
||
models.DecimalField(
|
||
decimal_places=2,
|
||
default=Decimal("0.00"),
|
||
help_text="Total cost (quantity × unit cost)",
|
||
max_digits=12,
|
||
),
|
||
),
|
||
(
|
||
"quality_status",
|
||
models.CharField(
|
||
choices=[
|
||
("GOOD", "Good"),
|
||
("QUARANTINE", "Quarantine"),
|
||
("EXPIRED", "Expired"),
|
||
("DAMAGED", "Damaged"),
|
||
("RECALLED", "Recalled"),
|
||
("REJECTED", "Rejected"),
|
||
],
|
||
default="GOOD",
|
||
help_text="Quality status",
|
||
max_length=20,
|
||
),
|
||
),
|
||
(
|
||
"notes",
|
||
models.TextField(blank=True, help_text="Stock notes", null=True),
|
||
),
|
||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||
("updated_at", models.DateTimeField(auto_now=True)),
|
||
(
|
||
"inventory_item",
|
||
models.ForeignKey(
|
||
help_text="Inventory item",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="inventory_stocks",
|
||
to="inventory.inventoryitem",
|
||
),
|
||
),
|
||
(
|
||
"location",
|
||
models.ForeignKey(
|
||
help_text="Storage location",
|
||
on_delete=django.db.models.deletion.CASCADE,
|
||
related_name="inventory_stocks",
|
||
to="inventory.inventorylocation",
|
||
),
|
||
),
|
||
(
|
||
"purchase_order",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="Related purchase order",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="inventory_stocks",
|
||
to="inventory.purchaseorder",
|
||
),
|
||
),
|
||
(
|
||
"supplier",
|
||
models.ForeignKey(
|
||
blank=True,
|
||
help_text="Supplier for this stock",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="inventory_stocks",
|
||
to="inventory.supplier",
|
||
),
|
||
),
|
||
],
|
||
options={
|
||
"verbose_name": "Inventory Stock",
|
||
"verbose_name_plural": "Inventory Stocks",
|
||
"db_table": "inventory_stock",
|
||
"ordering": ["expiration_date"],
|
||
},
|
||
),
|
||
migrations.AddField(
|
||
model_name="inventoryitem",
|
||
name="primary_supplier",
|
||
field=models.ForeignKey(
|
||
blank=True,
|
||
help_text="Primary supplier",
|
||
null=True,
|
||
on_delete=django.db.models.deletion.SET_NULL,
|
||
related_name="primary_items",
|
||
to="inventory.supplier",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorylocation",
|
||
index=models.Index(
|
||
fields=["tenant", "location_type"],
|
||
name="inventory_l_tenant__ed8a91_idx",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorylocation",
|
||
index=models.Index(
|
||
fields=["location_code"], name="inventory_l_locatio_cac77e_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorylocation",
|
||
index=models.Index(fields=["name"], name="inventory_l_name_93296b_idx"),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorylocation",
|
||
index=models.Index(
|
||
fields=["is_active"], name="inventory_l_is_acti_a5da3a_idx"
|
||
),
|
||
),
|
||
migrations.AlterUniqueTogether(
|
||
name="inventorylocation",
|
||
unique_together={("tenant", "location_code")},
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorderitem",
|
||
index=models.Index(
|
||
fields=["purchase_order", "line_number"],
|
||
name="inventory_p_purchas_6822eb_idx",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorderitem",
|
||
index=models.Index(
|
||
fields=["inventory_item"], name="inventory_p_invento_29a3e0_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorderitem",
|
||
index=models.Index(fields=["status"], name="inventory_p_status_5c95f6_idx"),
|
||
),
|
||
migrations.AlterUniqueTogether(
|
||
name="purchaseorderitem",
|
||
unique_together={("purchase_order", "line_number")},
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="supplier",
|
||
index=models.Index(
|
||
fields=["tenant", "supplier_type"],
|
||
name="inventory_s_tenant__6e7a3e_idx",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="supplier",
|
||
index=models.Index(
|
||
fields=["supplier_code"], name="inventory_s_supplie_ad4a4a_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="supplier",
|
||
index=models.Index(fields=["name"], name="inventory_s_name_d435cf_idx"),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="supplier",
|
||
index=models.Index(
|
||
fields=["is_active"], name="inventory_s_is_acti_3743da_idx"
|
||
),
|
||
),
|
||
migrations.AlterUniqueTogether(
|
||
name="supplier",
|
||
unique_together={("tenant", "supplier_code")},
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorder",
|
||
index=models.Index(
|
||
fields=["tenant", "status"], name="inventory_p_tenant__664733_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorder",
|
||
index=models.Index(
|
||
fields=["po_number"], name="inventory_p_po_numb_12ed00_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorder",
|
||
index=models.Index(
|
||
fields=["supplier"], name="inventory_p_supplie_d056c0_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorder",
|
||
index=models.Index(
|
||
fields=["order_date"], name="inventory_p_order_d_c8909e_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="purchaseorder",
|
||
index=models.Index(
|
||
fields=["requested_delivery_date"],
|
||
name="inventory_p_request_6ccfe8_idx",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorystock",
|
||
index=models.Index(
|
||
fields=["inventory_item", "location"],
|
||
name="inventory_s_invento_2c87e7_idx",
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorystock",
|
||
index=models.Index(
|
||
fields=["lot_number"], name="inventory_s_lot_num_6fbc51_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorystock",
|
||
index=models.Index(
|
||
fields=["serial_number"], name="inventory_s_serial__78281b_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorystock",
|
||
index=models.Index(
|
||
fields=["expiration_date"], name="inventory_s_expirat_4a74b1_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventorystock",
|
||
index=models.Index(
|
||
fields=["quality_status"], name="inventory_s_quality_3cbb6d_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventoryitem",
|
||
index=models.Index(
|
||
fields=["tenant", "category"], name="inventory_i_tenant__160581_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventoryitem",
|
||
index=models.Index(
|
||
fields=["item_code"], name="inventory_i_item_co_e61695_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventoryitem",
|
||
index=models.Index(
|
||
fields=["item_name"], name="inventory_i_item_na_83e4bd_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventoryitem",
|
||
index=models.Index(
|
||
fields=["manufacturer"], name="inventory_i_manufac_3c7e24_idx"
|
||
),
|
||
),
|
||
migrations.AddIndex(
|
||
model_name="inventoryitem",
|
||
index=models.Index(
|
||
fields=["is_active"], name="inventory_i_is_acti_fd4174_idx"
|
||
),
|
||
),
|
||
migrations.AlterUniqueTogether(
|
||
name="inventoryitem",
|
||
unique_together={("tenant", "item_code")},
|
||
),
|
||
]
|