fix(accounting): auto-sync published llm to product on publish; skip PAY leg for owner self-use
This commit is contained in:
parent
86057eaa3b
commit
9f37ec4717
@ -105,6 +105,12 @@ async def llm_id_to_product_id(llm_id):
|
||||
return await manager.llm_id_to_product_id(llm_id)
|
||||
|
||||
|
||||
async def sync_llm_product(llm_id):
|
||||
"""Auto-sync a published llm model as product (idempotent)."""
|
||||
manager = get_manager()
|
||||
return await manager.sync_llm_product(llm_id)
|
||||
|
||||
|
||||
async def product_accounting(llmusage):
|
||||
"""Full product accounting for one llmusage record."""
|
||||
manager = get_manager()
|
||||
|
||||
@ -1114,6 +1114,91 @@ class ProductManager:
|
||||
f'跳过 {skipped_cats} 个已有类别, {skipped_prods} 个已有产品'
|
||||
}
|
||||
|
||||
# ─── Auto-sync a published llm model as product ───
|
||||
|
||||
async def sync_llm_product(self, llm_id):
|
||||
"""llmage 上架模型后自动同步为产品(幂等)。
|
||||
|
||||
复用 import_categories_and_products 的增量导入(已存在的产品跳过),
|
||||
顺带补齐其他已上架但缺产品的模型。
|
||||
"""
|
||||
if not llm_id:
|
||||
return {'success': False, 'error': '缺少 llm_id'}
|
||||
|
||||
dbname = self._get_dbname()
|
||||
lmage_dbname = ServerEnv().get_module_dbname('llmage')
|
||||
|
||||
# 1. llm 记录必须存在且已上架
|
||||
async with DBPools().sqlorContext(lmage_dbname) as sor:
|
||||
llm_rows = await sor.sqlExe(
|
||||
"SELECT id, status, ownerid FROM llm WHERE id = ${lid}$",
|
||||
{'lid': llm_id})
|
||||
if not llm_rows:
|
||||
return {'success': False, 'error': '模型不存在: %s' % llm_id}
|
||||
llm_rec = llm_rows[0]
|
||||
if getattr(llm_rec, 'status', '') != 'published':
|
||||
return {'success': False, 'error': '模型未上架,无需同步'}
|
||||
ownerid = getattr(llm_rec, 'ownerid', None) or '0'
|
||||
|
||||
# 2. 幂等:产品已存在则跳过
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
existing = await sor.sqlExe(
|
||||
"""SELECT id FROM product
|
||||
WHERE resource_ref_id = ${ref_id}$ AND org_id = ${org_id}$""",
|
||||
{'ref_id': llm_id, 'org_id': ownerid})
|
||||
if existing:
|
||||
return {'success': True, 'skipped': True, 'product_id': existing[0].id}
|
||||
|
||||
# 3. 确定导入父类别:沿用已有 llmage 类别的父级,避免类别树分裂;
|
||||
# 完全没有 llmage 类别时才新建根类别
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
cat_rows = await sor.sqlExe(
|
||||
"""SELECT parent_id FROM product_category
|
||||
WHERE resource_module = 'llmage' AND org_id = ${org_id}$
|
||||
LIMIT 1""",
|
||||
{'org_id': ownerid})
|
||||
parent_category_id = None
|
||||
if cat_rows:
|
||||
parent_category_id = getattr(cat_rows[0], 'parent_id', None)
|
||||
if not parent_category_id:
|
||||
parent_category_id = getID()
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
await sor.C('product_category', {
|
||||
'id': parent_category_id,
|
||||
'parent_id': '0',
|
||||
'name': 'AI模型',
|
||||
'description': 'llmage 模型产品类别',
|
||||
'has_product': '1',
|
||||
'product_type': 'llm_model',
|
||||
'product_type_title': '',
|
||||
'sort_order': '0',
|
||||
'icon': '',
|
||||
'status': '1',
|
||||
'resource_module': 'llmage',
|
||||
'org_id': ownerid,
|
||||
'created_by': 'system',
|
||||
'created_at': now,
|
||||
'updated_at': now
|
||||
})
|
||||
|
||||
# 4. 增量导入(跳过已有类别/产品)
|
||||
result = await self.import_categories_and_products(
|
||||
resource_module='llmage', org_id=ownerid,
|
||||
parent_category_id=parent_category_id, user_id='system')
|
||||
if not result or not result.get('success'):
|
||||
err = result.get('error', '导入失败') if result else '导入无返回'
|
||||
return {'success': False, 'error': err}
|
||||
|
||||
# 5. 确认产品已落库
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
new_prod = await sor.sqlExe(
|
||||
"""SELECT id FROM product
|
||||
WHERE resource_ref_id = ${ref_id}$ AND org_id = ${org_id}$""",
|
||||
{'ref_id': llm_id, 'org_id': ownerid})
|
||||
if new_prod:
|
||||
return {'success': True, 'product_id': new_prod[0].id}
|
||||
return {'success': False, 'error': '导入后仍未找到产品(模型可能不在已上架列表)'}
|
||||
|
||||
# ─── Resource Module Interface Dispatcher ───
|
||||
|
||||
async def _get_product_interface(self, product_id=None, product_code=None):
|
||||
@ -1367,6 +1452,12 @@ WHERE resource_ref_id = ${llm_id}$ AND product_type = 'llm_model' LIMIT 1"""
|
||||
ownerid = getattr(llm, 'ownerid', '0') or '0'
|
||||
providerid = getattr(llm, 'providerid', '0') or '0'
|
||||
|
||||
# 2b. Self-use detection: customer org == owner org (e.g. owner-side users
|
||||
# or platform users with NULL/'0' userorgid using owner '0' models).
|
||||
# Self-use skips the PAY leg — merchant never opens a customer account
|
||||
# for itself, so PAY would fail account lookup. PAY* legs stay balanced.
|
||||
is_self_use = (userorgid or '0') == (ownerid or '0')
|
||||
|
||||
# 3. Parse usage data
|
||||
usages = llmusage.usages
|
||||
if isinstance(usages, str):
|
||||
@ -1446,12 +1537,14 @@ WHERE resource_ref_id = ${llm_id}$ AND product_type = 'llm_model' LIMIT 1"""
|
||||
if len(chain) == 1:
|
||||
# Direct: customer → product_owner → supplier
|
||||
# PAY: customer → owner, amount = customer_amount
|
||||
accounting_items.append(DictObject(
|
||||
action='PAY', customerid=userorgid, resellerid=ownerid,
|
||||
providerid=providerid, biz_date=biz_date, timestamp=timestamp,
|
||||
productid=product_id, transamt=customer_amount,
|
||||
currency=user_currency, base_amount=round(customer_amount, 2),
|
||||
variable={"交易金额": customer_amount, "交易币种": user_currency, "交易手续费": 0}))
|
||||
# (skipped for self-use: no customer account exists for owner itself)
|
||||
if not is_self_use:
|
||||
accounting_items.append(DictObject(
|
||||
action='PAY', customerid=userorgid, resellerid=ownerid,
|
||||
providerid=providerid, biz_date=biz_date, timestamp=timestamp,
|
||||
productid=product_id, transamt=customer_amount,
|
||||
currency=user_currency, base_amount=round(customer_amount, 2),
|
||||
variable={"交易金额": customer_amount, "交易币种": user_currency, "交易手续费": 0}))
|
||||
# PAY*: owner → supplier, amount = raw_cost * supplier_discount
|
||||
supplier_amount = raw_cost * last_link_discount
|
||||
accounting_items.append(DictObject(
|
||||
@ -1466,12 +1559,14 @@ WHERE resource_ref_id = ${llm_id}$ AND product_type = 'llm_model' LIMIT 1"""
|
||||
is_last = (i == len(chain) - 1)
|
||||
if is_last:
|
||||
# Last link (closest to customer): PAY
|
||||
accounting_items.append(DictObject(
|
||||
action='PAY', customerid=userorgid, resellerid=orgid,
|
||||
providerid=providerid, biz_date=biz_date, timestamp=timestamp,
|
||||
productid=product_id, transamt=customer_amount,
|
||||
currency=user_currency, base_amount=round(customer_amount, 2),
|
||||
variable={"交易金额": customer_amount, "交易币种": user_currency, "交易手续费": 0}))
|
||||
# (skipped for self-use: no customer account for owner itself)
|
||||
if not is_self_use:
|
||||
accounting_items.append(DictObject(
|
||||
action='PAY', customerid=userorgid, resellerid=orgid,
|
||||
providerid=providerid, biz_date=biz_date, timestamp=timestamp,
|
||||
productid=product_id, transamt=customer_amount,
|
||||
currency=user_currency, base_amount=round(customer_amount, 2),
|
||||
variable={"交易金额": customer_amount, "交易币种": user_currency, "交易手续费": 0}))
|
||||
else:
|
||||
# Middle link: PAY*
|
||||
next_orgid = chain[i + 1]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user