| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- from __future__ import annotations
- from decimal import Decimal
- from typing import Optional
- from django.utils import timezone
- from ..models import (
- PickupOrder,
- PriceList,
- PriceListItem,
- WeighTicket,
- Invoice,
- InvoiceLine,
- Payout,
- )
- def _get_price(price_list: PriceList, material_id: int, unit: str, direction: str) -> Optional[Decimal]:
- item = (
- PriceListItem.objects.filter(
- price_list=price_list, material_id=material_id, unit=unit, direction=direction
- )
- .order_by("id")
- .first()
- )
- return item.unit_price if item else None
- def generate_invoice_for_pickup(pickup: PickupOrder) -> Invoice:
- if getattr(pickup, "weigh_ticket", None) is None:
- raise ValueError("Pickup has no weigh ticket")
- # If invoice already exists and not void, return it (idempotent)
- existing = pickup.invoices.filter(status__in=[Invoice.STATUS_DRAFT, Invoice.STATUS_ISSUED, Invoice.STATUS_PAID]).first()
- if existing:
- return existing
- price_list = pickup.customer.price_list
- if not price_list:
- # Fallback to any org pricelist
- price_list = PriceList.objects.filter(organization=pickup.organization).order_by("id").first()
- if not price_list:
- raise ValueError("No price list found for customer or organization")
- inv = Invoice.objects.create(
- organization=pickup.organization,
- customer=pickup.customer,
- pickup=pickup,
- currency_code=price_list.currency_code,
- status=Invoice.STATUS_DRAFT,
- )
- total = Decimal("0.00")
- payout_total = Decimal("0.00")
- for line in pickup.weigh_ticket.lines.all():
- sell_price = _get_price(price_list, line.material_id, line.unit, PriceListItem.DIRECTION_SELL)
- buy_price = _get_price(price_list, line.material_id, line.unit, PriceListItem.DIRECTION_BUY)
- if sell_price is not None and sell_price > 0:
- line_total = (Decimal(line.quantity) * Decimal(sell_price)).quantize(Decimal("0.01"))
- InvoiceLine.objects.create(
- invoice=inv,
- description=f"{line.material.name} ({line.unit})",
- material=line.material,
- quantity=line.quantity,
- unit=line.unit,
- unit_price=sell_price,
- line_total=line_total,
- )
- total += line_total
- if buy_price is not None and buy_price > 0:
- payout_total += (Decimal(line.quantity) * Decimal(buy_price)).quantize(Decimal("0.01"))
- inv.total_amount = total
- inv.issued_at = timezone.now()
- inv.status = Invoice.STATUS_ISSUED if total > 0 else Invoice.STATUS_DRAFT
- inv.due_at = inv.issued_at + timezone.timedelta(days=14)
- inv.save()
- if payout_total > 0:
- Payout.objects.create(
- organization=pickup.organization,
- customer=pickup.customer,
- pickup=pickup,
- amount=payout_total,
- currency_code=price_list.currency_code,
- paid_at=timezone.now(),
- reference=f"PAYOUT-{pickup.id}",
- )
- # Update pickup status
- pickup.status = PickupOrder.STATUS_INVOICED
- pickup.save(update_fields=["status"])
- return inv
|