baseline: 批次A-D 成果 + membership 半成品(测试红)

This commit is contained in:
agent
2026-09-11 23:11:35 +08:00
commit b3f3095d53
311 changed files with 40540 additions and 0 deletions
+106
View File
@@ -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