No Description

seed_ecoloop.py 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from __future__ import annotations
  2. from decimal import Decimal
  3. from django.core.management.base import BaseCommand
  4. from django.contrib.auth import get_user_model
  5. from django.utils import timezone
  6. from orgs.models import Organization, UserProfile
  7. from recycle_core.models import (
  8. MaterialCategory,
  9. Material,
  10. PriceList,
  11. PriceListItem,
  12. Customer,
  13. CustomerSite,
  14. PickupOrder,
  15. PickupItem,
  16. WeighTicket,
  17. WeighLine,
  18. ScrapListing,
  19. ScrapListingItem,
  20. ScrapBid,
  21. )
  22. from recycle_core.services.billing import generate_invoice_for_pickup
  23. User = get_user_model()
  24. class Command(BaseCommand):
  25. help = "Seed demo data for Ecoloop: org, materials, price list, customer, pickup, weigh ticket, invoice"
  26. def add_arguments(self, parser):
  27. parser.add_argument("--org", default="DEMO", help="Organization code/id/name to seed (default: DEMO)")
  28. parser.add_argument("--bidder-org", dest="bidder_org", default="REC1", help="Bidder org code/id/name (default: REC1)")
  29. def handle(self, *args, **options):
  30. now = timezone.now()
  31. def _resolve_org(ident: str, *, default_name: str) -> Organization:
  32. if ident and ident.isdigit():
  33. org = Organization.objects.filter(pk=int(ident)).first()
  34. if org:
  35. return org
  36. org = (
  37. Organization.objects.filter(code=ident).first()
  38. or Organization.objects.filter(name=ident).first()
  39. )
  40. if org:
  41. return org
  42. # Create with defaults if not found
  43. return Organization.objects.create(code=ident, name=default_name, timezone="UTC", currency_code="THB")
  44. org_ident = options.get("org") or "DEMO"
  45. bidder_ident = options.get("bidder_org") or "REC1"
  46. org = _resolve_org(org_ident, default_name=("Ecoloop " + str(org_ident)))
  47. bidder_org = _resolve_org(bidder_ident, default_name="Recycler Co.")
  48. # Users
  49. manager = User.objects.filter(username="manager").first()
  50. if not manager:
  51. manager = User.objects.create_user(username="manager", email="manager@example.com", password="manager123")
  52. driver = User.objects.filter(username="driver").first()
  53. if not driver:
  54. driver = User.objects.create_user(username="driver", email="driver@example.com", password="driver123")
  55. buyer = User.objects.filter(username="buyer").first()
  56. if not buyer:
  57. buyer = User.objects.create_user(username="buyer", email="buyer@example.com", password="buyer123")
  58. # Ensure recycle_core user profiles and roles
  59. UserProfile.objects.get_or_create(user=manager, defaults={"organization": org, "role": UserProfile.ROLE_MANAGER})
  60. UserProfile.objects.get_or_create(user=driver, defaults={"organization": org, "role": UserProfile.ROLE_DRIVER})
  61. UserProfile.objects.get_or_create(user=buyer, defaults={"organization": bidder_org, "role": UserProfile.ROLE_MANAGER})
  62. # Materials and categories
  63. plastics, _ = MaterialCategory.objects.get_or_create(organization=org, name="Plastics")
  64. metals, _ = MaterialCategory.objects.get_or_create(organization=org, name="Metals")
  65. paper, _ = MaterialCategory.objects.get_or_create(organization=org, name="Paper")
  66. pet, _ = Material.objects.get_or_create(organization=org, category=plastics, name="PET", defaults={"default_unit": Material.UNIT_KG})
  67. hdpe, _ = Material.objects.get_or_create(organization=org, category=plastics, name="HDPE", defaults={"default_unit": Material.UNIT_KG})
  68. can, _ = Material.objects.get_or_create(organization=org, category=metals, name="Aluminum Can", defaults={"default_unit": Material.UNIT_KG})
  69. cardboard, _ = Material.objects.get_or_create(organization=org, category=paper, name="Cardboard", defaults={"default_unit": Material.UNIT_KG})
  70. # Price list
  71. pl, _ = PriceList.objects.get_or_create(
  72. organization=org,
  73. name="Standard",
  74. defaults={"currency_code": "THB"},
  75. )
  76. # Sell prices (invoice customer)
  77. PriceListItem.objects.get_or_create(price_list=pl, material=pet, unit=Material.UNIT_KG, direction=PriceListItem.DIRECTION_SELL, defaults={"unit_price": Decimal("5.00")})
  78. PriceListItem.objects.get_or_create(price_list=pl, material=hdpe, unit=Material.UNIT_KG, direction=PriceListItem.DIRECTION_SELL, defaults={"unit_price": Decimal("4.00")})
  79. PriceListItem.objects.get_or_create(price_list=pl, material=can, unit=Material.UNIT_KG, direction=PriceListItem.DIRECTION_SELL, defaults={"unit_price": Decimal("12.00")})
  80. PriceListItem.objects.get_or_create(price_list=pl, material=cardboard, unit=Material.UNIT_KG, direction=PriceListItem.DIRECTION_SELL, defaults={"unit_price": Decimal("2.00")})
  81. # Buy prices (pay customer)
  82. PriceListItem.objects.get_or_create(price_list=pl, material=pet, unit=Material.UNIT_KG, direction=PriceListItem.DIRECTION_BUY, defaults={"unit_price": Decimal("1.50")})
  83. PriceListItem.objects.get_or_create(price_list=pl, material=hdpe, unit=Material.UNIT_KG, direction=PriceListItem.DIRECTION_BUY, defaults={"unit_price": Decimal("1.20")})
  84. # Customer and site
  85. customer, _ = Customer.objects.get_or_create(
  86. organization=org,
  87. name="Acme Factory",
  88. defaults={
  89. "email": "ops@acme.example",
  90. "phone": "+66 000 0000",
  91. "billing_address": "123 Demo Rd, Bangkok",
  92. "price_list": pl,
  93. },
  94. )
  95. site, _ = CustomerSite.objects.get_or_create(
  96. customer=customer,
  97. name="Acme Plant #1",
  98. defaults={
  99. "address": "123 Demo Rd, Bangkok",
  100. "contact_name": "Somchai",
  101. "contact_phone": "+66 111 1111",
  102. "contact_email": "somchai@acme.example",
  103. },
  104. )
  105. pickup = PickupOrder.objects.create(
  106. organization=org,
  107. customer=customer,
  108. site=site,
  109. status=PickupOrder.STATUS_SCHEDULED,
  110. scheduled_at=now + timezone.timedelta(days=1),
  111. assigned_driver=driver,
  112. created_by=manager,
  113. notes="Demo pickup order",
  114. )
  115. PickupItem.objects.create(pickup=pickup, material=pet, estimated_qty=Decimal("100.0"), unit=Material.UNIT_KG)
  116. PickupItem.objects.create(pickup=pickup, material=can, estimated_qty=Decimal("50.0"), unit=Material.UNIT_KG)
  117. ticket = WeighTicket.objects.create(
  118. pickup=pickup,
  119. ticket_number=f"WT-{pickup.id}",
  120. gross_weight=Decimal("200.000"),
  121. tare_weight=Decimal("40.000"),
  122. net_weight=Decimal("160.000"),
  123. unit=Material.UNIT_KG,
  124. recorded_by=manager,
  125. )
  126. WeighLine.objects.create(ticket=ticket, material=pet, quantity=Decimal("110.000"), unit=Material.UNIT_KG)
  127. WeighLine.objects.create(ticket=ticket, material=can, quantity=Decimal("50.000"), unit=Material.UNIT_KG)
  128. pickup.status = PickupOrder.STATUS_WEIGHED
  129. pickup.save(update_fields=["status"])
  130. invoice = generate_invoice_for_pickup(pickup)
  131. # Create a demo scrap listing and a bid
  132. listing = ScrapListing.objects.create(
  133. organization=org,
  134. customer=customer,
  135. site=site,
  136. title="Monthly PET + Cans lot",
  137. description="Estimated quantities of PET and aluminum cans available",
  138. auction_type=ScrapListing.TYPE_OPEN,
  139. currency_code="THB",
  140. reserve_price=Decimal("500.00"),
  141. min_increment=Decimal("50.00"),
  142. status=ScrapListing.STATUS_OPEN,
  143. is_public=False,
  144. starts_at=now,
  145. created_by=manager,
  146. )
  147. ScrapListingItem.objects.create(listing=listing, material=pet, quantity_estimate=Decimal("100.0"), unit=Material.UNIT_KG)
  148. ScrapListingItem.objects.create(listing=listing, material=can, quantity_estimate=Decimal("50.0"), unit=Material.UNIT_KG)
  149. # Invite-only demo: invite bidder_org then place bid
  150. from recycle_core.models import ScrapListingInvite
  151. ScrapListingInvite.objects.get_or_create(listing=listing, invited_org=bidder_org, invited_user=buyer)
  152. ScrapBid.objects.create(listing=listing, bidder_org=bidder_org, bidder_user=buyer, price_total=Decimal("550.00"), message="Ready to collect within 48h")
  153. self.stdout.write(self.style.SUCCESS("Seeded Ecoloop demo data"))
  154. self.stdout.write(f"Organization: {org.name} ({org.code})")
  155. self.stdout.write(f"Customer: {customer.name}")
  156. self.stdout.write(f"Pickup: {pickup.id} status={pickup.status}")
  157. self.stdout.write(f"WeighTicket: {ticket.ticket_number}")
  158. self.stdout.write(f"Invoice: {invoice.id} total={invoice.total_amount} {invoice.currency_code}")
  159. self.stdout.write(f"Scrap Listing: {listing.id} status={listing.status}")