feat(purchase): 一次性直购计费 storage_direct_charging(选A购买即记账)
存储买断模式:不依赖采样,按购买容量×月数直接计费出账。 - storage_direct_charging(spec, storage_gb, valid_months):package定价×月数 - calculate_product_cost 支持 direct 模式复用 package 定价 + 月数乘法 - 注册 env.storage_direct_charging + __init__ 导出
This commit is contained in:
parent
3e6f448a39
commit
577f1f1561
@ -5,6 +5,7 @@ from .init import (
|
||||
get_storage_ppid,
|
||||
record_storage_sample,
|
||||
storage_charging,
|
||||
storage_direct_charging,
|
||||
resolve_ppid,
|
||||
)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@ -312,6 +312,95 @@ async def storage_charging(spec_id_or_code, meter_mode, userid, userorgid,
|
||||
return rec
|
||||
|
||||
|
||||
async def storage_direct_charging(spec_id_or_code, storage_gb, userid, userorgid,
|
||||
valid_months=1, subscription_id='', discount_rate=None):
|
||||
"""一次性直购计费(购买即记账,选 A 模式):不依赖采样,按购买容量 × 有效期出账。
|
||||
|
||||
定价单位 GB月:买 N 个月容量 = storage_gb × valid_months 个 GB月。
|
||||
usages 里 sample_count=0 标记直购(区别于采样出账),计费因子 = storage_gb。
|
||||
"""
|
||||
storage_gb = float(storage_gb or 0)
|
||||
if storage_gb <= 0:
|
||||
raise Exception('购买容量必须大于 0')
|
||||
valid_months = max(1, int(valid_months or 1))
|
||||
|
||||
spec = await get_storage_spec(spec_id_or_code)
|
||||
if not spec:
|
||||
raise Exception('存储规格不存在: %s' % spec_id_or_code)
|
||||
spec_id = _val(spec, 'id')
|
||||
min_gb = float(_val(spec, 'min_gb') or 0)
|
||||
if min_gb and storage_gb < min_gb:
|
||||
raise Exception('购买容量不能低于最小规格 %sGB' % min_gb)
|
||||
|
||||
# 直购按 package(包月)定价解析;无 package 则取缺省映射
|
||||
ppid = await get_storage_ppid(spec_id_or_code, 'package') \
|
||||
or await get_storage_ppid(spec_id_or_code)
|
||||
usages = {
|
||||
'storage_gb': storage_gb,
|
||||
'valid_months': valid_months,
|
||||
'sample_count': 0,
|
||||
'meter_mode': 'direct',
|
||||
'purchase': True,
|
||||
}
|
||||
|
||||
env = ServerEnv()
|
||||
amount = 0.0
|
||||
if ppid:
|
||||
charging = getattr(env, 'buffered_charging', None)
|
||||
if charging is None:
|
||||
logger.warning('pricing 模块未加载,直购计费金额记 0(ppid=%s)', ppid)
|
||||
else:
|
||||
try:
|
||||
prices = await charging(ppid, usages)
|
||||
if prices is None:
|
||||
logger.error('直购定价计算返回空 ppid=%s', ppid)
|
||||
else:
|
||||
amount = float(sum(getattr(p, 'amount', 0) or 0 for p in prices))
|
||||
# 多个月:单价按 GB月,购买 N 个月按 N 倍计
|
||||
if valid_months > 1:
|
||||
amount = amount * valid_months
|
||||
except Exception as e:
|
||||
logger.error('直购计费失败 ppid=%s: %s', ppid, e)
|
||||
raise Exception('计费失败: %s' % e)
|
||||
else:
|
||||
raise Exception('存储无定价方案,无法购买(缺 storres_pricing_map 映射)')
|
||||
|
||||
rate = float(discount_rate) if discount_rate not in (None, '') else 1.0
|
||||
final_amount = round(amount * rate, 4)
|
||||
|
||||
today = datetime.date.today()
|
||||
end = today + datetime.timedelta(days=30 * valid_months)
|
||||
rec = {
|
||||
'id': getID(),
|
||||
'spec_id': spec_id,
|
||||
'meter_mode': 'direct',
|
||||
'subscription_id': subscription_id,
|
||||
'userid': userid,
|
||||
'userorgid': userorgid,
|
||||
'use_date': _today(),
|
||||
'use_time': _now(),
|
||||
'period_start': today.strftime('%Y-%m-%d'),
|
||||
'period_end': end.strftime('%Y-%m-%d'),
|
||||
'sample_count': 0,
|
||||
'avg_gb': storage_gb,
|
||||
'max_gb': storage_gb,
|
||||
'billable_gb': storage_gb,
|
||||
'usages': json.dumps(usages, ensure_ascii=False),
|
||||
'transno': getID(),
|
||||
'amount': final_amount,
|
||||
'discount_rate': rate,
|
||||
'accounting_status': 'pending',
|
||||
'status': 'active',
|
||||
'org_id': userorgid,
|
||||
'created_at': _now(),
|
||||
}
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.C('storres_usage', rec)
|
||||
logger.info('存储直购: spec=%s 容量=%sGB 有效=%s月 原价=%s 折扣=%s 应收=%s',
|
||||
spec_id_or_code, storage_gb, valid_months, amount, rate, final_amount)
|
||||
return rec
|
||||
|
||||
|
||||
async def get_accounting_storres_usages(limit=200):
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
return await sor.sqlExe(
|
||||
@ -349,6 +438,7 @@ def load_storage_resource():
|
||||
env.record_storage_sample = record_storage_sample
|
||||
env.summarize_samples = summarize_samples
|
||||
env.storage_charging = storage_charging
|
||||
env.storage_direct_charging = storage_direct_charging
|
||||
env.get_accounting_storres_usages = get_accounting_storres_usages
|
||||
env.mark_storres_accounted = mark_storres_accounted
|
||||
|
||||
|
||||
@ -141,7 +141,9 @@ async def calculate_product_cost(resource_ref_id, usage_data, user_org_id=None):
|
||||
|
||||
usage_data = usage_data or {}
|
||||
meter_mode = usage_data.get('meter_mode', 'ondemand')
|
||||
ppid = await get_storage_ppid(getattr(spec, 'id'), meter_mode)
|
||||
# direct(直购) 复用 package 定价(买断=包月价×月数)
|
||||
ppid_mode = 'package' if meter_mode == 'direct' else meter_mode
|
||||
ppid = await get_storage_ppid(getattr(spec, 'id'), ppid_mode)
|
||||
if not ppid:
|
||||
return {'success': False, 'message': '无定价方案(ppid为空)'}
|
||||
|
||||
@ -154,6 +156,10 @@ async def calculate_product_cost(resource_ref_id, usage_data, user_org_id=None):
|
||||
return {'success': False, 'message': '定价计算失败: %s' % e}
|
||||
|
||||
amount = sum(getattr(p, 'amount', 0) for p in prices)
|
||||
# 直购多个月:单价按 GB月,购买 N 个月按 N 倍计(与 storage_direct_charging 一致)
|
||||
valid_months = int(usage_data.get('valid_months', 1) or 1)
|
||||
if meter_mode == 'direct' and valid_months > 1:
|
||||
amount = amount * valid_months
|
||||
|
||||
# 资源层只算真实定价(原价)。产品级折扣(客户/分销商/供应商)由产品层
|
||||
# calculate_product_cost 按 product_id 精确计算,见 product_management.core。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user