diff --git a/storage_resource/__init__.py b/storage_resource/__init__.py index 9b786f6..bc8edc9 100644 --- a/storage_resource/__init__.py +++ b/storage_resource/__init__.py @@ -5,6 +5,7 @@ from .init import ( get_storage_ppid, record_storage_sample, storage_charging, + storage_direct_charging, resolve_ppid, ) diff --git a/storage_resource/__pycache__/init.cpython-310.pyc b/storage_resource/__pycache__/init.cpython-310.pyc index 1c5a6c6..6a8fc8a 100644 Binary files a/storage_resource/__pycache__/init.cpython-310.pyc and b/storage_resource/__pycache__/init.cpython-310.pyc differ diff --git a/storage_resource/__pycache__/product_interface.cpython-310.pyc b/storage_resource/__pycache__/product_interface.cpython-310.pyc index 5909653..f8108b0 100644 Binary files a/storage_resource/__pycache__/product_interface.cpython-310.pyc and b/storage_resource/__pycache__/product_interface.cpython-310.pyc differ diff --git a/storage_resource/init.py b/storage_resource/init.py index 93cbd63..2c85b47 100644 --- a/storage_resource/init.py +++ b/storage_resource/init.py @@ -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 diff --git a/storage_resource/product_interface.py b/storage_resource/product_interface.py index 1bee681..a8e538e 100644 --- a/storage_resource/product_interface.py +++ b/storage_resource/product_interface.py @@ -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。