177 lines
8.2 KiB
Python
177 lines
8.2 KiB
Python
"""Seed demo data: roles, users, facilities/schedules/slots, a complete default
|
|
workflow (with approval + payment + invoice + Odoo-sync steps), pricing rules,
|
|
notification templates and a report definition.
|
|
|
|
The workflow seeded here is demo DATA ONLY — the engine itself remains fully
|
|
dynamic and knows nothing about pistachio processing.
|
|
"""
|
|
from decimal import Decimal
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.core.management.base import BaseCommand
|
|
from django.db import transaction
|
|
from django.utils import timezone
|
|
|
|
from apps.accounts.models import Customer
|
|
from apps.notifications.models import NotificationTemplate
|
|
from apps.pricing.models import PricingRule
|
|
from apps.rbac.models import Permission, Role
|
|
from apps.reports.models import ReportDefinition, ReportField
|
|
from apps.reservations.models import Facility, FacilitySchedule
|
|
from apps.reservations.slot_service import SlotService
|
|
from apps.workflows.engine import WorkflowEngine
|
|
from apps.workflows.models import Workflow, WorkflowStep, WorkflowTransition, WorkflowVersion
|
|
|
|
User = get_user_model()
|
|
|
|
PERMISSIONS = [
|
|
"reservation.view", "reservation.create", "reservation.update", "reservation.cancel", "reservation.approve",
|
|
"slot.manage",
|
|
"weighing.view", "weighing.create", "weighing.update",
|
|
"workflow.view", "workflow.create", "workflow.update", "workflow.publish",
|
|
"processing.view", "processing.start", "processing.pause", "processing.complete",
|
|
"invoice.view", "invoice.create", "invoice.cancel",
|
|
"report.view", "report.create",
|
|
"user.view", "user.manage", "role.view", "role.manage",
|
|
"settings.manage",
|
|
]
|
|
|
|
ROLE_SPECS = {
|
|
"administrator": ("Administrator", PERMISSIONS),
|
|
"facility_manager": ("Facility Manager", [
|
|
"reservation.view", "reservation.update", "reservation.cancel", "reservation.approve", "slot.manage",
|
|
"weighing.view", "weighing.create", "weighing.update",
|
|
"processing.view", "processing.start", "processing.complete",
|
|
"invoice.view", "report.view", "report.create", "user.view",
|
|
]),
|
|
"operator": ("Operator", [
|
|
"reservation.view", "weighing.view", "weighing.create",
|
|
"processing.view", "processing.start", "processing.pause", "processing.complete",
|
|
]),
|
|
"customer": ("Customer", ["reservation.view", "reservation.create", "processing.view", "report.view"]),
|
|
"accountant": ("Accountant", ["invoice.view", "invoice.create", "invoice.cancel", "report.view"]),
|
|
"quality_controller": ("Quality Controller", ["processing.view", "processing.complete", "report.create"]),
|
|
}
|
|
|
|
# (key, name, flags)
|
|
STEP_DEFS = [
|
|
("reservation", "Reservation", {"is_start": True}),
|
|
("approval", "Approval", {}),
|
|
("weighing", "Weighing", {"required": True}),
|
|
("processing_selection", "Processing Selection", {"required": True}),
|
|
("customer_confirmation", "Customer Confirmation", {"requires_customer_confirmation": True}),
|
|
("payment", "Payment", {"requires_payment": True}),
|
|
("sorting", "Sorting", {}),
|
|
("washing", "Washing", {}),
|
|
("drying", "Drying", {}),
|
|
("quality_control", "Quality Control", {}),
|
|
("packaging", "Packaging", {}),
|
|
("final_weighing", "Final Weighing", {}),
|
|
("final_report", "Final Report", {}),
|
|
("invoice", "Invoice", {"triggers_invoice": True, "triggers_odoo_sync": True}),
|
|
("completed", "Completed", {"is_terminal": True}),
|
|
]
|
|
|
|
REPORT_FIELDS = [
|
|
("final_moisture", "Final Moisture", "decimal"),
|
|
("quality_grade", "Quality Grade", "choice"),
|
|
("broken_percentage", "Broken Percentage", "percentage"),
|
|
("category_a_weight", "Category A Weight", "weight"),
|
|
("category_b_weight", "Category B Weight", "weight"),
|
|
("category_c_weight", "Category C Weight", "weight"),
|
|
]
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Seed demo roles, users, facilities, a default workflow, pricing, templates and reports."
|
|
|
|
@transaction.atomic
|
|
def handle(self, *args, **options):
|
|
perms = {}
|
|
for code in PERMISSIONS:
|
|
perms[code], _ = Permission.objects.get_or_create(code=code, defaults={"description": code})
|
|
|
|
roles = {}
|
|
for slug, (name, codes) in ROLE_SPECS.items():
|
|
role, _ = Role.objects.get_or_create(slug=slug, defaults={"name": name})
|
|
role.permissions.set([perms[c] for c in codes])
|
|
roles[slug] = role
|
|
|
|
def make_user(username, role_slug, **kw):
|
|
user, _ = User.objects.get_or_create(username=username, defaults=kw)
|
|
user.set_password("demo1234")
|
|
user.save()
|
|
if not user.user_roles.filter(role=roles[role_slug]).exists():
|
|
user.user_roles.create(role=roles[role_slug])
|
|
return user
|
|
|
|
make_user("admin", "administrator", is_staff=True, is_superuser=True, email="admin@example.com")
|
|
make_user("manager", "facility_manager")
|
|
make_user("operator", "operator")
|
|
make_user("accountant", "accountant")
|
|
make_user("qc", "quality_controller")
|
|
cust_user = make_user("customer", "customer", email="customer@example.com", phone="09120000000")
|
|
Customer.objects.get_or_create(user=cust_user, defaults={"company_name": "Demo Pistachio Co."})
|
|
|
|
# Facility + schedule + slots
|
|
facility, _ = Facility.objects.get_or_create(name="Main Facility")
|
|
tomorrow = timezone.localdate() + timezone.timedelta(days=1)
|
|
FacilitySchedule.objects.update_or_create(
|
|
facility=facility, date=tomorrow, defaults={"is_open": True, "capacity": 3}
|
|
)
|
|
SlotService().generate_slots(facility, tomorrow)
|
|
|
|
# Default workflow
|
|
workflow, _ = Workflow.objects.get_or_create(
|
|
slug="pistachio-processing",
|
|
defaults={"name": "Pistachio Processing", "description": "Default configurable workflow"},
|
|
)
|
|
version = WorkflowVersion.objects.create(workflow=workflow, version=workflow.versions.count() + 1)
|
|
steps = {}
|
|
for order, (key, name, flags) in enumerate(STEP_DEFS):
|
|
steps[key] = WorkflowStep.objects.create(
|
|
version=version, key=key, name=name, order=order,
|
|
position_x=80 + (order % 5) * 200, position_y=40 + (order // 5) * 160,
|
|
**flags,
|
|
)
|
|
chain = [d[0] for d in STEP_DEFS]
|
|
for a, b in zip(chain, chain[1:]):
|
|
WorkflowTransition.objects.create(version=version, from_step=steps[a], to_step=steps[b])
|
|
|
|
# Pricing
|
|
PricingRule.objects.get_or_create(step=steps["washing"], name="Washing per kg", rule_type="per_kg", amount=Decimal("5000"))
|
|
PricingRule.objects.get_or_create(step=steps["drying"], name="Drying per kg", rule_type="per_kg", amount=Decimal("8000"))
|
|
PricingRule.objects.get_or_create(step=steps["sorting"], name="Sorting per kg", rule_type="per_kg", amount=Decimal("12000"))
|
|
|
|
# Report definition
|
|
definition, _ = ReportDefinition.objects.get_or_create(
|
|
workflow=workflow, name="Default Processing Report", defaults={"active": True}
|
|
)
|
|
for order, (key, label, field_type) in enumerate(REPORT_FIELDS):
|
|
ReportField.objects.get_or_create(
|
|
definition=definition, key=key,
|
|
defaults={"label": label, "field_type": field_type, "order": order},
|
|
)
|
|
|
|
# Notification templates
|
|
NotificationTemplate.objects.get_or_create(
|
|
event="reservation.approved", channel="sms",
|
|
defaults={"body": "Cetana: reservation {{reservation_title}} on {{slot_date}} at {{slot_time}} is confirmed."},
|
|
)
|
|
NotificationTemplate.objects.get_or_create(
|
|
event="order.completed", channel="sms",
|
|
defaults={"body": "Cetana: order {{order_number}} is complete. Final report is ready."},
|
|
)
|
|
|
|
try:
|
|
WorkflowEngine().publish(workflow, version)
|
|
except Exception as exc: # pragma: no cover
|
|
self.stdout.write(self.style.WARNING(f"Workflow publish skipped: {exc}"))
|
|
|
|
# Pre-fill the Odoo server URL (credentials are entered in Settings).
|
|
from apps.core.models import SystemSetting
|
|
|
|
SystemSetting.objects.get_or_create(key="odoo.url", defaults={"value": "https://rayayazd.ir"})
|
|
|
|
self.stdout.write(self.style.SUCCESS("Seed data created. Logins use password 'demo1234'."))
|