New enterprise feature
This commit is contained in:
@@ -41,14 +41,18 @@ from shared.schemas.reasoning_types import (
|
||||
create_po_reasoning_low_stock,
|
||||
create_po_reasoning_supplier_contract
|
||||
)
|
||||
from shared.utils.demo_dates import BASE_REFERENCE_DATE
|
||||
|
||||
# Configure logging
|
||||
logger = structlog.get_logger()
|
||||
|
||||
# Demo tenant IDs (match those from orders service)
|
||||
# Demo tenant IDs (match those from tenant service)
|
||||
DEMO_TENANT_IDS = [
|
||||
uuid.UUID("a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6"), # San Pablo
|
||||
uuid.UUID("b2c3d4e5-f6a7-48b9-c0d1-e2f3a4b5c6d7") # La Espiga
|
||||
uuid.UUID("a1b2c3d4-e5f6-47a8-b9c0-d1e2f3a4b5c6"), # Professional Bakery (standalone)
|
||||
uuid.UUID("c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8"), # Enterprise Chain (parent)
|
||||
uuid.UUID("d4e5f6a7-b8c9-40d1-e2f3-a4b5c6d7e8f9"), # Enterprise Child 1 (Madrid)
|
||||
uuid.UUID("e5f6a7b8-c9d0-41e2-f3a4-b5c6d7e8f9a0"), # Enterprise Child 2 (Barcelona)
|
||||
uuid.UUID("f6a7b8c9-d0e1-42f3-a4b5-c6d7e8f9a0b1"), # Enterprise Child 3 (Valencia)
|
||||
]
|
||||
|
||||
# System user ID for auto-approvals
|
||||
@@ -252,12 +256,12 @@ async def create_purchase_order(
|
||||
) -> PurchaseOrder:
|
||||
"""Create a purchase order with items"""
|
||||
|
||||
created_at = datetime.now(timezone.utc) + timedelta(days=created_offset_days)
|
||||
created_at = BASE_REFERENCE_DATE + timedelta(days=created_offset_days)
|
||||
required_delivery = created_at + timedelta(days=random.randint(3, 7))
|
||||
|
||||
# Generate unique PO number
|
||||
while True:
|
||||
po_number = f"PO-{datetime.now().year}-{random.randint(100, 999)}"
|
||||
po_number = f"PO-{BASE_REFERENCE_DATE.year}-{random.randint(100, 999)}"
|
||||
# Check if PO number already exists in the database
|
||||
existing_po = await db.execute(
|
||||
select(PurchaseOrder).where(PurchaseOrder.po_number == po_number).limit(1)
|
||||
@@ -599,7 +603,7 @@ async def seed_purchase_orders_for_tenant(db: AsyncSession, tenant_id: uuid.UUID
|
||||
pos_created.append(po10)
|
||||
|
||||
# 11. DELIVERY OVERDUE - Expected delivery is 4 hours late (URGENT dashboard alert)
|
||||
delivery_overdue_time = datetime.now(timezone.utc) - timedelta(hours=4)
|
||||
delivery_overdue_time = BASE_REFERENCE_DATE - timedelta(hours=4)
|
||||
po11 = await create_purchase_order(
|
||||
db, tenant_id, supplier_high_trust,
|
||||
PurchaseOrderStatus.sent_to_supplier,
|
||||
@@ -617,7 +621,7 @@ async def seed_purchase_orders_for_tenant(db: AsyncSession, tenant_id: uuid.UUID
|
||||
pos_created.append(po11)
|
||||
|
||||
# 12. DELIVERY ARRIVING SOON - Arriving in 8 hours (TODAY dashboard alert)
|
||||
arriving_soon_time = datetime.now(timezone.utc) + timedelta(hours=8)
|
||||
arriving_soon_time = BASE_REFERENCE_DATE + timedelta(hours=8)
|
||||
po12 = await create_purchase_order(
|
||||
db, tenant_id, supplier_medium_trust,
|
||||
PurchaseOrderStatus.sent_to_supplier,
|
||||
@@ -652,12 +656,162 @@ async def seed_purchase_orders_for_tenant(db: AsyncSession, tenant_id: uuid.UUID
|
||||
return pos_created
|
||||
|
||||
|
||||
async def seed_internal_transfer_pos_for_child(
|
||||
db: AsyncSession,
|
||||
child_tenant_id: uuid.UUID,
|
||||
parent_tenant_id: uuid.UUID,
|
||||
child_name: str
|
||||
) -> List[PurchaseOrder]:
|
||||
"""
|
||||
Seed internal transfer purchase orders from child to parent tenant
|
||||
|
||||
These are POs where:
|
||||
- tenant_id = child (the requesting outlet)
|
||||
- supplier_id = parent (the supplier)
|
||||
- is_internal = True
|
||||
- transfer_type = 'finished_goods'
|
||||
"""
|
||||
logger.info(
|
||||
"Seeding internal transfer POs for child tenant",
|
||||
child_tenant_id=str(child_tenant_id),
|
||||
parent_tenant_id=str(parent_tenant_id),
|
||||
child_name=child_name
|
||||
)
|
||||
|
||||
internal_pos = []
|
||||
|
||||
# Create 5-7 internal transfer POs per child for realistic history
|
||||
num_transfers = random.randint(5, 7)
|
||||
|
||||
# Common finished goods that children request from parent
|
||||
finished_goods_items = [
|
||||
[
|
||||
{"name": "Baguette Tradicional", "quantity": 50, "unit_price": 1.20, "uom": "unidad"},
|
||||
{"name": "Pan de Molde Integral", "quantity": 30, "unit_price": 2.50, "uom": "unidad"},
|
||||
],
|
||||
[
|
||||
{"name": "Croissant Mantequilla", "quantity": 40, "unit_price": 1.80, "uom": "unidad"},
|
||||
{"name": "Napolitana Chocolate", "quantity": 25, "unit_price": 2.00, "uom": "unidad"},
|
||||
],
|
||||
[
|
||||
{"name": "Pan de Masa Madre", "quantity": 20, "unit_price": 3.50, "uom": "unidad"},
|
||||
{"name": "Pan Rústico", "quantity": 30, "unit_price": 2.80, "uom": "unidad"},
|
||||
],
|
||||
[
|
||||
{"name": "Ensaimada", "quantity": 15, "unit_price": 3.20, "uom": "unidad"},
|
||||
{"name": "Palmera", "quantity": 20, "unit_price": 2.50, "uom": "unidad"},
|
||||
],
|
||||
[
|
||||
{"name": "Bollo Suizo", "quantity": 30, "unit_price": 1.50, "uom": "unidad"},
|
||||
{"name": "Donut Glaseado", "quantity": 25, "unit_price": 1.80, "uom": "unidad"},
|
||||
]
|
||||
]
|
||||
|
||||
for i in range(num_transfers):
|
||||
# Vary creation dates: some recent, some from past weeks
|
||||
created_offset = -random.randint(0, 21) # Last 3 weeks
|
||||
|
||||
# Select items for this transfer
|
||||
items = finished_goods_items[i % len(finished_goods_items)]
|
||||
|
||||
# Calculate total
|
||||
total_amount = sum(Decimal(str(item["quantity"] * item["unit_price"])) for item in items)
|
||||
|
||||
# Vary status: most completed, some in progress
|
||||
if i < num_transfers - 2:
|
||||
status = PurchaseOrderStatus.completed
|
||||
elif i == num_transfers - 2:
|
||||
status = PurchaseOrderStatus.approved
|
||||
else:
|
||||
status = PurchaseOrderStatus.pending_approval
|
||||
|
||||
created_at = BASE_REFERENCE_DATE + timedelta(days=created_offset)
|
||||
|
||||
# Generate unique internal transfer PO number
|
||||
while True:
|
||||
po_number = f"INT-{child_name[:3].upper()}-{random.randint(1000, 9999)}"
|
||||
existing_po = await db.execute(
|
||||
select(PurchaseOrder).where(PurchaseOrder.po_number == po_number).limit(1)
|
||||
)
|
||||
if not existing_po.scalar_one_or_none():
|
||||
break
|
||||
|
||||
# Delivery typically 2-3 days for internal transfers
|
||||
required_delivery = created_at + timedelta(days=random.randint(2, 3))
|
||||
|
||||
# Create internal transfer PO
|
||||
po = PurchaseOrder(
|
||||
tenant_id=child_tenant_id, # PO belongs to child
|
||||
supplier_id=parent_tenant_id, # Parent is the "supplier"
|
||||
po_number=po_number,
|
||||
status=status,
|
||||
is_internal=True, # CRITICAL: Mark as internal transfer
|
||||
source_tenant_id=parent_tenant_id, # Source is parent
|
||||
destination_tenant_id=child_tenant_id, # Destination is child
|
||||
transfer_type="finished_goods", # Transfer finished products
|
||||
subtotal=total_amount,
|
||||
tax_amount=Decimal("0.00"), # No tax on internal transfers
|
||||
shipping_cost=Decimal("0.00"), # No shipping cost for internal
|
||||
total_amount=total_amount,
|
||||
required_delivery_date=required_delivery,
|
||||
expected_delivery_date=required_delivery if status != PurchaseOrderStatus.pending_approval else None,
|
||||
notes=f"Internal transfer request from {child_name} outlet",
|
||||
created_at=created_at,
|
||||
updated_at=created_at,
|
||||
created_by=SYSTEM_USER_ID,
|
||||
updated_by=SYSTEM_USER_ID
|
||||
)
|
||||
|
||||
if status == PurchaseOrderStatus.completed:
|
||||
po.approved_at = created_at + timedelta(hours=2)
|
||||
po.sent_to_supplier_at = created_at + timedelta(hours=3)
|
||||
po.delivered_at = required_delivery
|
||||
po.completed_at = required_delivery
|
||||
|
||||
db.add(po)
|
||||
await db.flush() # Get PO ID
|
||||
|
||||
# Add items
|
||||
for item_data in items:
|
||||
item = PurchaseOrderItem(
|
||||
purchase_order_id=po.id,
|
||||
tenant_id=child_tenant_id, # Set tenant_id for the item
|
||||
inventory_product_id=uuid.uuid4(), # Would link to actual inventory items
|
||||
product_name=item_data["name"],
|
||||
ordered_quantity=Decimal(str(item_data["quantity"])),
|
||||
unit_price=Decimal(str(item_data["unit_price"])),
|
||||
unit_of_measure=item_data["uom"],
|
||||
line_total=Decimal(str(item_data["quantity"] * item_data["unit_price"]))
|
||||
)
|
||||
db.add(item)
|
||||
|
||||
internal_pos.append(po)
|
||||
|
||||
await db.commit()
|
||||
|
||||
logger.info(
|
||||
f"Successfully created {len(internal_pos)} internal transfer POs",
|
||||
child_tenant_id=str(child_tenant_id),
|
||||
child_name=child_name
|
||||
)
|
||||
|
||||
return internal_pos
|
||||
|
||||
|
||||
async def seed_all(db: AsyncSession):
|
||||
"""Seed all demo tenants with purchase orders"""
|
||||
logger.info("Starting demo purchase orders seed process")
|
||||
|
||||
all_pos = []
|
||||
|
||||
# Enterprise parent and children IDs
|
||||
ENTERPRISE_PARENT = uuid.UUID("c3d4e5f6-a7b8-49c0-d1e2-f3a4b5c6d7e8")
|
||||
ENTERPRISE_CHILDREN = [
|
||||
(uuid.UUID("d4e5f6a7-b8c9-40d1-e2f3-a4b5c6d7e8f9"), "Madrid Centro"),
|
||||
(uuid.UUID("e5f6a7b8-c9d0-41e2-f3a4-b5c6d7e8f9a0"), "Barcelona Gràcia"),
|
||||
(uuid.UUID("f6a7b8c9-d0e1-42f3-a4b5-c6d7e8f9a0b1"), "Valencia Ruzafa"),
|
||||
]
|
||||
|
||||
for tenant_id in DEMO_TENANT_IDS:
|
||||
# Check if POs already exist
|
||||
result = await db.execute(
|
||||
@@ -669,12 +823,29 @@ async def seed_all(db: AsyncSession):
|
||||
logger.info(f"Purchase orders already exist for tenant {tenant_id}, skipping")
|
||||
continue
|
||||
|
||||
# Seed regular external POs for all tenants
|
||||
pos = await seed_purchase_orders_for_tenant(db, tenant_id)
|
||||
all_pos.extend(pos)
|
||||
|
||||
# Additionally, seed internal transfer POs for enterprise children
|
||||
for child_id, child_name in ENTERPRISE_CHILDREN:
|
||||
if tenant_id == child_id:
|
||||
internal_pos = await seed_internal_transfer_pos_for_child(
|
||||
db, child_id, ENTERPRISE_PARENT, child_name
|
||||
)
|
||||
all_pos.extend(internal_pos)
|
||||
logger.info(
|
||||
f"Added {len(internal_pos)} internal transfer POs for {child_name}",
|
||||
child_id=str(child_id)
|
||||
)
|
||||
|
||||
return {
|
||||
"total_pos_created": len(all_pos),
|
||||
"tenants_seeded": len(DEMO_TENANT_IDS),
|
||||
"internal_transfers_created": sum(
|
||||
1 for child_id, _ in ENTERPRISE_CHILDREN
|
||||
if any(po.tenant_id == child_id and po.is_internal for po in all_pos)
|
||||
),
|
||||
"status": "completed"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user