baseline: 批次A-D 成果 + membership 半成品(测试红)
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"""往来单位服务:开单取价 + 信用额度。
|
||||
|
||||
取价优先级(quote_price):
|
||||
1. 客户专属价 CustomerProductPrice
|
||||
2. 价格等级价 PriceLevel.discount_rate × 默认售价(rate>0 且 <1 时生效)
|
||||
3. 最近成交价(该客户+商品最近一张已过账销售单行单价)
|
||||
4. 默认售价 Product.sale_price
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from .models import Customer, CustomerProductPrice, PriceLevel
|
||||
|
||||
|
||||
def quote_price(*, tenant, customer: Customer, product) -> dict:
|
||||
"""返回 {price, source, last_price, min_price, max_price}。
|
||||
|
||||
price 为基本单位价;last/min/max 为该客户历史成交参考(基本单位口径)。
|
||||
"""
|
||||
# 1. 客户专属价
|
||||
cpp = CustomerProductPrice.objects.filter(
|
||||
tenant=tenant, customer=customer, product=product
|
||||
).first()
|
||||
if cpp is not None:
|
||||
price, source = cpp.price, "customer"
|
||||
else:
|
||||
# 2. 价格等级价
|
||||
level: PriceLevel | None = customer.price_level
|
||||
rate = level.discount_rate if level else Decimal("0")
|
||||
if rate and Decimal("0") < rate < Decimal("1"):
|
||||
price, source = product.sale_price * rate, "level"
|
||||
else:
|
||||
# 3. 最近成交价
|
||||
last = _history(tenant, customer, product).first()
|
||||
if last is not None:
|
||||
price, source = last.unit_price, "last"
|
||||
else:
|
||||
price, source = product.sale_price, "default"
|
||||
|
||||
hist = list(_history(tenant, customer, product).values_list(
|
||||
"unit_price", flat=True
|
||||
)[:50])
|
||||
return {
|
||||
"price": price,
|
||||
"source": source,
|
||||
"last_price": hist[0] if hist else None,
|
||||
"min_price": min(hist) if hist else None,
|
||||
"max_price": max(hist) if hist else None,
|
||||
}
|
||||
|
||||
|
||||
def _history(tenant, customer: Customer, product):
|
||||
"""该客户+商品的已过账销售行(新→旧)。"""
|
||||
from apps.sales.models import SalesBillLine
|
||||
|
||||
return (
|
||||
SalesBillLine.objects.filter(
|
||||
tenant=tenant,
|
||||
bill__customer=customer,
|
||||
bill__state="confirmed",
|
||||
product=product,
|
||||
)
|
||||
.order_by("-bill_id", "-id")
|
||||
)
|
||||
|
||||
|
||||
def credit_usage(*, tenant, customer: Customer) -> dict:
|
||||
"""客户信用额度占用:{limit, outstanding, available}。
|
||||
|
||||
limit=0 表示未启用管控;available = limit - outstanding(未启用时为 None)。
|
||||
"""
|
||||
from django.db.models import Sum
|
||||
|
||||
from apps.finance.models import Receivable
|
||||
|
||||
agg = Receivable.objects.filter(
|
||||
tenant=tenant,
|
||||
customer=customer,
|
||||
status__in=["open", "partial"],
|
||||
is_deleted=False,
|
||||
).aggregate(total=Sum("total_amount"), paid=Sum("paid_amount"))
|
||||
outstanding = ((agg["total"] or Decimal("0")) - (agg["paid"] or Decimal("0"))).quantize(
|
||||
Decimal("0.0001")
|
||||
)
|
||||
limit = customer.credit_limit or Decimal("0")
|
||||
return {
|
||||
"limit": limit,
|
||||
"outstanding": outstanding,
|
||||
"available": (limit - outstanding) if limit > 0 else None,
|
||||
}
|
||||
|
||||
|
||||
def check_credit(*, tenant, customer: Customer, extra_amount: Decimal) -> dict:
|
||||
"""下单/过账前检查额度。返回 usage;超限抛 CreditLimitExceeded。"""
|
||||
from apps.sales.services import CreditLimitExceeded
|
||||
|
||||
usage = credit_usage(tenant=tenant, customer=customer)
|
||||
if usage["limit"] <= 0:
|
||||
return usage
|
||||
if usage["outstanding"] + extra_amount > usage["limit"]:
|
||||
raise CreditLimitExceeded(
|
||||
customer.code, usage["outstanding"], usage["limit"], extra_amount
|
||||
)
|
||||
return usage
|
||||
Reference in New Issue
Block a user