feat: account_resource 资源模块
参照 llmage 范式(llm主表无ppid + llm_api_map挂ppid + llmusage计费六件套): - 资源主表不带 ppid,定价挂在「资源×用法」映射表上 - 对外统一 resolve_ppid(resource_ref_id, ctx),load 时注册到产品层解析器注册表 - 含 models 四段式/json CRUD/i18n四语言/load_path/init 种子数据
This commit is contained in:
parent
029ce7ced6
commit
71576381bd
11
account_resource/__init__.py
Normal file
11
account_resource/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""账号资源模块 - 账号规格/权益配额/定价映射(spec×charge_mode→ppid)"""
|
||||
from .init import (
|
||||
load_account_resource,
|
||||
get_account_spec,
|
||||
get_account_privileges,
|
||||
get_account_ppid,
|
||||
account_charging,
|
||||
resolve_ppid,
|
||||
)
|
||||
|
||||
__version__ = '1.0.0'
|
||||
325
account_resource/init.py
Normal file
325
account_resource/init.py
Normal file
@ -0,0 +1,325 @@
|
||||
"""account_resource — 账号资源模块
|
||||
|
||||
参照 llmage 范式(资源模块的标准形态):
|
||||
llmage: llm(主表,无ppid) → llm_api_map(llmid×apiname→ppid) → llmusage(计费六件套)
|
||||
account_resource: acctres_spec → acctres_pricing_map(spec×charge_mode→ppid) → acctres_usage
|
||||
|
||||
**ppid 挂法各资源不同,但对外作用一致**:
|
||||
账号按「计费方式」挂价(试用/月付/年付),存储按「计量方式」挂价(按量/包月/峰值)。
|
||||
两者都通过 resolve_ppid(resource_ref_id, ctx) 对外暴露,产品层不必知道内部差异。
|
||||
|
||||
宿主集成:
|
||||
from account_resource.init import load_account_resource
|
||||
load_account_resource()
|
||||
|
||||
对外注册到 ServerEnv 的能力见 load_account_resource()。
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from ahserver.serverenv import ServerEnv
|
||||
from appPublic.uniqueID import getID
|
||||
from sqlor.dbpools import DBPools
|
||||
|
||||
MODULE_NAME = 'account_resource'
|
||||
MODULE_VERSION = '1.0.0'
|
||||
RESOURCE_TYPE = 'account_spec' # product_resource.resource_type 用这个值
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _get_dbname():
|
||||
"""动态取库名,禁硬编码(宿主通过 get_module_dbname 映射)。"""
|
||||
env = ServerEnv()
|
||||
return env.get_module_dbname(MODULE_NAME)
|
||||
|
||||
|
||||
def _now():
|
||||
return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
|
||||
def _val(rec, key, default=''):
|
||||
"""sqlor 行对象只支持属性访问,dict 也兼容。"""
|
||||
if isinstance(rec, dict):
|
||||
return rec.get(key, default)
|
||||
return getattr(rec, key, default)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# 规格(资源主表)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
async def acctres_spec_list(ns=None):
|
||||
"""规格列表。ns 可含 status / spec_type 过滤。"""
|
||||
ns = ns or {}
|
||||
sql = "SELECT * FROM acctres_spec WHERE 1=1"
|
||||
params = {}
|
||||
if ns.get('status'):
|
||||
sql += " AND status=${status}$"
|
||||
params['status'] = ns['status']
|
||||
if ns.get('spec_type'):
|
||||
sql += " AND spec_type=${spec_type}$"
|
||||
params['spec_type'] = ns['spec_type']
|
||||
sql += " ORDER BY sort_order, spec_code"
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
return await sor.sqlExe(sql, params)
|
||||
|
||||
|
||||
async def get_account_spec(spec_id_or_code):
|
||||
"""按 id 或 spec_code 取规格(产品层拿到 resource_ref_id 后调这个)。"""
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
recs = await sor.sqlExe(
|
||||
"SELECT * FROM acctres_spec WHERE id=${k}$ OR spec_code=${k}$",
|
||||
{"k": spec_id_or_code})
|
||||
return recs[0] if recs else None
|
||||
|
||||
|
||||
async def get_account_privileges(spec_id_or_code):
|
||||
"""取规格权益(开通账号时用它设置配额限制)。"""
|
||||
spec = await get_account_spec(spec_id_or_code)
|
||||
if not spec:
|
||||
return None
|
||||
priv = {
|
||||
'workspace_max': int(_val(spec, 'workspace_max', 1) or 1),
|
||||
'workspace_gb': float(_val(spec, 'workspace_gb', 0) or 0),
|
||||
'concurrent_task': int(_val(spec, 'concurrent_task', 1) or 1),
|
||||
'gpu_allowed': str(_val(spec, 'gpu_allowed', '0')) == '1',
|
||||
'member_max': int(_val(spec, 'member_max', 1) or 1),
|
||||
}
|
||||
extra = _val(spec, 'privilege_json', '')
|
||||
if extra:
|
||||
try:
|
||||
priv.update(json.loads(extra))
|
||||
except (ValueError, TypeError):
|
||||
logger.warning('privilege_json 解析失败: spec=%s', spec_id_or_code)
|
||||
return priv
|
||||
|
||||
|
||||
async def acctres_spec_create(data):
|
||||
data = dict(data or {})
|
||||
data.setdefault('id', getID())
|
||||
data.setdefault('created_at', _now())
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.C('acctres_spec', data)
|
||||
return data['id']
|
||||
|
||||
|
||||
async def acctres_spec_update(data):
|
||||
data = dict(data or {})
|
||||
data['updated_at'] = _now()
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.U('acctres_spec', data) # id 放在 data 里,sor.U 只接受 2 参数
|
||||
return True
|
||||
|
||||
|
||||
async def acctres_spec_delete(data):
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.D('acctres_spec', {'id': (data or {}).get('id', '')})
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# 定价映射(spec × charge_mode → ppid)—— 本模块的 ppid 挂法
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
async def acctres_pricing_map_list(ns=None):
|
||||
ns = ns or {}
|
||||
sql = ("SELECT m.*, s.spec_code, s.spec_name FROM acctres_pricing_map m "
|
||||
"LEFT JOIN acctres_spec s ON m.spec_id=s.id WHERE 1=1")
|
||||
params = {}
|
||||
if ns.get('spec_id'):
|
||||
sql += " AND m.spec_id=${spec_id}$"
|
||||
params['spec_id'] = ns['spec_id']
|
||||
sql += " ORDER BY s.sort_order, m.charge_mode"
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
return await sor.sqlExe(sql, params)
|
||||
|
||||
|
||||
async def acctres_pricing_map_create(data):
|
||||
data = dict(data or {})
|
||||
data.setdefault('id', getID())
|
||||
data.setdefault('created_at', _now())
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.C('acctres_pricing_map', data)
|
||||
return data['id']
|
||||
|
||||
|
||||
async def acctres_pricing_map_update(data):
|
||||
data = dict(data or {})
|
||||
data['updated_at'] = _now()
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.U('acctres_pricing_map', data)
|
||||
return True
|
||||
|
||||
|
||||
async def acctres_pricing_map_delete(data):
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.D('acctres_pricing_map', {'id': (data or {}).get('id', '')})
|
||||
return True
|
||||
|
||||
|
||||
async def get_account_ppid(spec_id_or_code, charge_mode=None):
|
||||
"""**统一解析器契约**:给定资源 + 用法上下文 → ppid。
|
||||
|
||||
charge_mode 为空时取 is_default='1' 的那条。
|
||||
产品层不直接调这个,而是调 resolve_resource_ppid(resource_type, ...)(见 init 注册)。
|
||||
"""
|
||||
spec = await get_account_spec(spec_id_or_code)
|
||||
if not spec:
|
||||
logger.warning('规格不存在: %s', spec_id_or_code)
|
||||
return None
|
||||
spec_id = _val(spec, 'id')
|
||||
|
||||
sql = ("SELECT ppid, charge_mode, charge_unit, valid_days FROM acctres_pricing_map "
|
||||
"WHERE spec_id=${spec_id}$ AND status='active'")
|
||||
params = {'spec_id': spec_id}
|
||||
if charge_mode:
|
||||
sql += " AND charge_mode=${charge_mode}$"
|
||||
params['charge_mode'] = charge_mode
|
||||
else:
|
||||
sql += " AND is_default='1'"
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
recs = await sor.sqlExe(sql, params)
|
||||
if not recs:
|
||||
logger.warning('无定价映射: spec=%s charge_mode=%s', spec_id, charge_mode)
|
||||
return None
|
||||
return _val(recs[0], 'ppid') or None
|
||||
|
||||
|
||||
async def resolve_ppid(resource_ref_id, ctx=None):
|
||||
"""资源定价解析器(注册给产品层统一调用)。
|
||||
|
||||
ctx: {'charge_mode': 'month'|'year'|'trial', ...}
|
||||
"""
|
||||
ctx = ctx or {}
|
||||
return await get_account_ppid(resource_ref_id, ctx.get('charge_mode'))
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# 用量与计费
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
async def account_charging(spec_id_or_code, charge_mode, userid, userorgid,
|
||||
subscription_id='', quantity=1, usages=None,
|
||||
period_start=None, period_end=None, discount_rate=None):
|
||||
"""账号资源计费:解析 ppid → 调 pricing 引擎 → 落用量记录。
|
||||
|
||||
折扣由产品层传入(discount_rate),本模块只按资源真实定价算原价再乘折扣。
|
||||
返回 usage 记录 dict(含 amount)。
|
||||
"""
|
||||
env = ServerEnv()
|
||||
ppid = await get_account_ppid(spec_id_or_code, charge_mode)
|
||||
|
||||
usages = dict(usages or {})
|
||||
usages.setdefault('duration', quantity)
|
||||
usages.setdefault('flat', quantity)
|
||||
usages.setdefault('charge_mode', charge_mode)
|
||||
|
||||
amount = 0.0
|
||||
if ppid:
|
||||
charging = getattr(env, 'pricing_program_charging', None)
|
||||
if charging is None:
|
||||
logger.warning('pricing 模块未加载,账号计费金额记 0(ppid=%s)', ppid)
|
||||
else:
|
||||
try:
|
||||
r = await charging(ppid, usages)
|
||||
if isinstance(r, dict):
|
||||
amount = float(r.get('amount', 0) or 0)
|
||||
elif isinstance(r, (int, float)):
|
||||
amount = float(r)
|
||||
except Exception as e:
|
||||
logger.error('账号计费失败 ppid=%s: %s', ppid, e)
|
||||
else:
|
||||
logger.warning('无 ppid,账号计费金额记 0: spec=%s mode=%s',
|
||||
spec_id_or_code, charge_mode)
|
||||
|
||||
rate = float(discount_rate) if discount_rate not in (None, '') else 1.0
|
||||
final_amount = round(amount * rate, 4)
|
||||
|
||||
spec = await get_account_spec(spec_id_or_code)
|
||||
rec = {
|
||||
'id': getID(),
|
||||
'spec_id': _val(spec, 'id') if spec else '',
|
||||
'charge_mode': charge_mode,
|
||||
'subscription_id': subscription_id,
|
||||
'userid': userid,
|
||||
'userorgid': userorgid,
|
||||
'use_date': datetime.date.today().strftime('%Y-%m-%d'),
|
||||
'use_time': _now(),
|
||||
'period_start': period_start,
|
||||
'period_end': period_end,
|
||||
'quantity': quantity,
|
||||
'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('acctres_usage', rec)
|
||||
logger.info('账号计费: spec=%s mode=%s 原价=%s 折扣=%s 应收=%s',
|
||||
spec_id_or_code, charge_mode, amount, rate, final_amount)
|
||||
return rec
|
||||
|
||||
|
||||
async def get_accounting_acctres_usages(limit=200):
|
||||
"""待记账用量(供 accounting 模块出账扫描)。"""
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
return await sor.sqlExe(
|
||||
"SELECT * FROM acctres_usage WHERE accounting_status='pending' "
|
||||
"ORDER BY use_time LIMIT " + str(int(limit)), {})
|
||||
|
||||
|
||||
async def mark_acctres_accounted(usage_id, status='accounted'):
|
||||
async with DBPools().sqlorContext(_get_dbname()) as sor:
|
||||
await sor.U('acctres_usage', {'id': usage_id, 'accounting_status': status,
|
||||
'updated_at': _now()})
|
||||
return True
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Module Loader
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
def load_account_resource():
|
||||
"""注册账号资源能力到 ServerEnv。"""
|
||||
env = ServerEnv()
|
||||
|
||||
# 规格 CRUD
|
||||
env.acctres_spec_list = acctres_spec_list
|
||||
env.acctres_spec_create = acctres_spec_create
|
||||
env.acctres_spec_update = acctres_spec_update
|
||||
env.acctres_spec_delete = acctres_spec_delete
|
||||
env.get_account_spec = get_account_spec
|
||||
env.get_account_privileges = get_account_privileges
|
||||
|
||||
# 定价映射 CRUD
|
||||
env.acctres_pricing_map_list = acctres_pricing_map_list
|
||||
env.acctres_pricing_map_create = acctres_pricing_map_create
|
||||
env.acctres_pricing_map_update = acctres_pricing_map_update
|
||||
env.acctres_pricing_map_delete = acctres_pricing_map_delete
|
||||
env.get_account_ppid = get_account_ppid
|
||||
|
||||
# 计费
|
||||
env.account_charging = account_charging
|
||||
env.get_accounting_acctres_usages = get_accounting_acctres_usages
|
||||
env.mark_acctres_accounted = mark_acctres_accounted
|
||||
|
||||
# 向产品层注册「资源定价解析器」——各资源模块挂法不同,接口统一
|
||||
reg = getattr(env, 'register_resource_pricing_resolver', None)
|
||||
if reg:
|
||||
reg(RESOURCE_TYPE, resolve_ppid)
|
||||
else:
|
||||
# 产品层未加载时兜底:直接挂到 env,产品层加载后可自行拾取
|
||||
existing = getattr(env, '_resource_pricing_resolvers', None) or {}
|
||||
existing[RESOURCE_TYPE] = resolve_ppid
|
||||
env._resource_pricing_resolvers = existing
|
||||
logger.info('产品层解析器注册表未就绪,已暂存 resolver: %s', RESOURCE_TYPE)
|
||||
|
||||
logger.info('account_resource module loaded (v%s, resource_type=%s)',
|
||||
MODULE_VERSION, RESOURCE_TYPE)
|
||||
return True
|
||||
7
i18n/en/msg.txt
Normal file
7
i18n/en/msg.txt
Normal file
@ -0,0 +1,7 @@
|
||||
账号规格: Account Spec
|
||||
账号资源: Account Resource
|
||||
权益配额: Privilege Quota
|
||||
计费方式: Charge Mode
|
||||
试用: Trial
|
||||
月租: Monthly
|
||||
年租: Yearly
|
||||
7
i18n/jp/msg.txt
Normal file
7
i18n/jp/msg.txt
Normal file
@ -0,0 +1,7 @@
|
||||
账号规格: アカウント仕様
|
||||
账号资源: アカウントリソース
|
||||
权益配额: 権益クォータ
|
||||
计费方式: 課金方式
|
||||
试用: トライアル
|
||||
月租: 月額
|
||||
年租: 年額
|
||||
7
i18n/ko/msg.txt
Normal file
7
i18n/ko/msg.txt
Normal file
@ -0,0 +1,7 @@
|
||||
账号规格: 계정 사양
|
||||
账号资源: 계정 리소스
|
||||
权益配额: 권익 할당량
|
||||
计费方式: 과금 방식
|
||||
试用: 체험
|
||||
月租: 월정액
|
||||
年租: 연정액
|
||||
7
i18n/zh/msg.txt
Normal file
7
i18n/zh/msg.txt
Normal file
@ -0,0 +1,7 @@
|
||||
账号规格: 账号规格
|
||||
账号资源: 账号资源
|
||||
权益配额: 权益配额
|
||||
计费方式: 计费方式
|
||||
试用: 试用
|
||||
月租: 月租
|
||||
年租: 年租
|
||||
68
init/data.json
Normal file
68
init/data.json
Normal file
@ -0,0 +1,68 @@
|
||||
{
|
||||
"acctres_spec": [
|
||||
{
|
||||
"spec_code": "ACC-TRIAL-7D",
|
||||
"spec_name": "试用账号(7天)",
|
||||
"spec_type": "trial",
|
||||
"workspace_max": 1,
|
||||
"workspace_gb": 5,
|
||||
"concurrent_task": 1,
|
||||
"gpu_allowed": "0",
|
||||
"member_max": 1,
|
||||
"sort_order": 10,
|
||||
"status": "active",
|
||||
"description": "新用户试用,每机构限领一次"
|
||||
},
|
||||
{
|
||||
"spec_code": "ACC-MONTHLY",
|
||||
"spec_name": "月租账号",
|
||||
"spec_type": "standard",
|
||||
"workspace_max": 5,
|
||||
"workspace_gb": 50,
|
||||
"concurrent_task": 3,
|
||||
"gpu_allowed": "1",
|
||||
"member_max": 5,
|
||||
"sort_order": 20,
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"spec_code": "ACC-YEARLY",
|
||||
"spec_name": "年租账号",
|
||||
"spec_type": "standard",
|
||||
"workspace_max": 20,
|
||||
"workspace_gb": 200,
|
||||
"concurrent_task": 10,
|
||||
"gpu_allowed": "1",
|
||||
"member_max": 20,
|
||||
"sort_order": 30,
|
||||
"status": "active"
|
||||
}
|
||||
],
|
||||
"acctres_pricing_map": [
|
||||
{
|
||||
"spec_code": "ACC-TRIAL-7D",
|
||||
"charge_mode": "trial",
|
||||
"charge_unit": "次",
|
||||
"valid_days": 7,
|
||||
"is_default": "1",
|
||||
"ppid": ""
|
||||
},
|
||||
{
|
||||
"spec_code": "ACC-MONTHLY",
|
||||
"charge_mode": "month",
|
||||
"charge_unit": "月",
|
||||
"valid_days": 30,
|
||||
"is_default": "1",
|
||||
"ppid": ""
|
||||
},
|
||||
{
|
||||
"spec_code": "ACC-YEARLY",
|
||||
"charge_mode": "year",
|
||||
"charge_unit": "年",
|
||||
"valid_days": 365,
|
||||
"is_default": "1",
|
||||
"ppid": ""
|
||||
}
|
||||
],
|
||||
"_note": "ppid 待建定价项目后回填;charge_mode 是本模块的 ppid 挂载维度"
|
||||
}
|
||||
70
json/acctres_pricing_map.json
Normal file
70
json/acctres_pricing_map.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"tblname": "acctres_pricing_map",
|
||||
"params": {
|
||||
"title": "账号定价映射",
|
||||
"browserfields": [
|
||||
{
|
||||
"field": "spec_id",
|
||||
"title": "规格ID"
|
||||
},
|
||||
{
|
||||
"field": "charge_mode",
|
||||
"title": "计费方式"
|
||||
},
|
||||
{
|
||||
"field": "charge_unit",
|
||||
"title": "计费单位"
|
||||
},
|
||||
{
|
||||
"field": "valid_days",
|
||||
"title": "有效天数"
|
||||
},
|
||||
{
|
||||
"field": "ppid",
|
||||
"title": "定价项目ID"
|
||||
},
|
||||
{
|
||||
"field": "is_default",
|
||||
"title": "缺省方式"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"title": "状态"
|
||||
}
|
||||
],
|
||||
"editfields": [
|
||||
{
|
||||
"field": "spec_id",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "charge_mode",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "charge_unit",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "valid_days",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "ppid",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "is_default",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"uitype": "Text"
|
||||
}
|
||||
],
|
||||
"searchfields": [
|
||||
"spec_id",
|
||||
"charge_mode"
|
||||
]
|
||||
}
|
||||
}
|
||||
94
json/acctres_spec.json
Normal file
94
json/acctres_spec.json
Normal file
@ -0,0 +1,94 @@
|
||||
{
|
||||
"tblname": "acctres_spec",
|
||||
"params": {
|
||||
"title": "账号规格",
|
||||
"browserfields": [
|
||||
{
|
||||
"field": "spec_code",
|
||||
"title": "规格编码"
|
||||
},
|
||||
{
|
||||
"field": "spec_name",
|
||||
"title": "规格名称"
|
||||
},
|
||||
{
|
||||
"field": "spec_type",
|
||||
"title": "规格类型"
|
||||
},
|
||||
{
|
||||
"field": "workspace_max",
|
||||
"title": "可建工作空间数"
|
||||
},
|
||||
{
|
||||
"field": "workspace_gb",
|
||||
"title": "赠送容量GB"
|
||||
},
|
||||
{
|
||||
"field": "concurrent_task",
|
||||
"title": "并发任务数"
|
||||
},
|
||||
{
|
||||
"field": "gpu_allowed",
|
||||
"title": "允许GPU"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"title": "状态"
|
||||
}
|
||||
],
|
||||
"editfields": [
|
||||
{
|
||||
"field": "spec_code",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "spec_name",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "spec_type",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "description",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "workspace_max",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "workspace_gb",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "concurrent_task",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "gpu_allowed",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "member_max",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "privilege_json",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "sort_order",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"uitype": "Text"
|
||||
}
|
||||
],
|
||||
"searchfields": [
|
||||
"spec_code",
|
||||
"spec_name"
|
||||
]
|
||||
}
|
||||
}
|
||||
130
json/acctres_usage.json
Normal file
130
json/acctres_usage.json
Normal file
@ -0,0 +1,130 @@
|
||||
{
|
||||
"tblname": "acctres_usage",
|
||||
"params": {
|
||||
"title": "账号用量",
|
||||
"browserfields": [
|
||||
{
|
||||
"field": "spec_id",
|
||||
"title": "规格ID"
|
||||
},
|
||||
{
|
||||
"field": "charge_mode",
|
||||
"title": "计费方式"
|
||||
},
|
||||
{
|
||||
"field": "userorgid",
|
||||
"title": "用户机构"
|
||||
},
|
||||
{
|
||||
"field": "use_date",
|
||||
"title": "使用日期"
|
||||
},
|
||||
{
|
||||
"field": "quantity",
|
||||
"title": "数量"
|
||||
},
|
||||
{
|
||||
"field": "amount",
|
||||
"title": "交易金额"
|
||||
},
|
||||
{
|
||||
"field": "discount_rate",
|
||||
"title": "折扣率"
|
||||
},
|
||||
{
|
||||
"field": "accounting_status",
|
||||
"title": "记账状态"
|
||||
}
|
||||
],
|
||||
"editfields": [
|
||||
{
|
||||
"field": "spec_id",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "charge_mode",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "subscription_id",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "userid",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "userorgid",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "use_date",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "use_time",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "period_start",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "period_end",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "quantity",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "usages",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "transno",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "amount",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "amount_currency",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "amount_base",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "cost",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "cost_currency",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "cost_base",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "discount_rate",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "accounting_status",
|
||||
"uitype": "Text"
|
||||
},
|
||||
{
|
||||
"field": "status",
|
||||
"uitype": "Text"
|
||||
}
|
||||
],
|
||||
"searchfields": [
|
||||
"spec_id",
|
||||
"charge_mode"
|
||||
]
|
||||
}
|
||||
}
|
||||
120
models/acctres_pricing_map.json
Normal file
120
models/acctres_pricing_map.json
Normal file
@ -0,0 +1,120 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "acctres_pricing_map",
|
||||
"title": "账号规格定价映射",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "spec_id",
|
||||
"title": "规格ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "charge_mode",
|
||||
"title": "计费方式",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "charge_unit",
|
||||
"title": "计费单位",
|
||||
"type": "str",
|
||||
"length": 16,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "valid_days",
|
||||
"title": "有效天数",
|
||||
"type": "int",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "ppid",
|
||||
"title": "定价项目ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "is_default",
|
||||
"title": "缺省方式",
|
||||
"type": "str",
|
||||
"length": 1,
|
||||
"nullable": "yes",
|
||||
"default": "0"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"title": "状态",
|
||||
"type": "str",
|
||||
"length": 16,
|
||||
"nullable": "yes",
|
||||
"default": "active"
|
||||
},
|
||||
{
|
||||
"name": "org_id",
|
||||
"title": "所属机构",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "created_by",
|
||||
"title": "创建人",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "创建时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"title": "更新时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_apm_spec",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"spec_id"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_apm_mode",
|
||||
"idxtype": "unique",
|
||||
"idxfields": [
|
||||
"spec_id",
|
||||
"charge_mode"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_apm_ppid",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"ppid"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
149
models/acctres_spec.json
Normal file
149
models/acctres_spec.json
Normal file
@ -0,0 +1,149 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "acctres_spec",
|
||||
"title": "账号规格",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "spec_code",
|
||||
"title": "规格编码",
|
||||
"type": "str",
|
||||
"length": 64,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "spec_name",
|
||||
"title": "规格名称",
|
||||
"type": "str",
|
||||
"length": 255,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "spec_type",
|
||||
"title": "规格类型",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes",
|
||||
"default": "standard"
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"title": "说明",
|
||||
"type": "text",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "workspace_max",
|
||||
"title": "可建工作空间数",
|
||||
"type": "int",
|
||||
"nullable": "yes",
|
||||
"default": "1"
|
||||
},
|
||||
{
|
||||
"name": "workspace_gb",
|
||||
"title": "赠送容量GB",
|
||||
"type": "double",
|
||||
"length": 18,
|
||||
"dec": 2,
|
||||
"nullable": "yes",
|
||||
"default": "0"
|
||||
},
|
||||
{
|
||||
"name": "concurrent_task",
|
||||
"title": "并发任务数",
|
||||
"type": "int",
|
||||
"nullable": "yes",
|
||||
"default": "1"
|
||||
},
|
||||
{
|
||||
"name": "gpu_allowed",
|
||||
"title": "允许GPU",
|
||||
"type": "str",
|
||||
"length": 1,
|
||||
"nullable": "yes",
|
||||
"default": "0"
|
||||
},
|
||||
{
|
||||
"name": "member_max",
|
||||
"title": "成员数上限",
|
||||
"type": "int",
|
||||
"nullable": "yes",
|
||||
"default": "1"
|
||||
},
|
||||
{
|
||||
"name": "privilege_json",
|
||||
"title": "其他权益JSON",
|
||||
"type": "text",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "sort_order",
|
||||
"title": "排序",
|
||||
"type": "int",
|
||||
"nullable": "yes",
|
||||
"default": "0"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"title": "状态",
|
||||
"type": "str",
|
||||
"length": 16,
|
||||
"nullable": "yes",
|
||||
"default": "active"
|
||||
},
|
||||
{
|
||||
"name": "org_id",
|
||||
"title": "所属机构",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "created_by",
|
||||
"title": "创建人",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "创建时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"title": "更新时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_spec_code",
|
||||
"idxtype": "unique",
|
||||
"idxfields": [
|
||||
"spec_code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_spec_status",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
229
models/acctres_usage.json
Normal file
229
models/acctres_usage.json
Normal file
@ -0,0 +1,229 @@
|
||||
{
|
||||
"summary": [
|
||||
{
|
||||
"name": "acctres_usage",
|
||||
"title": "账号资源用量",
|
||||
"primary": [
|
||||
"id"
|
||||
]
|
||||
}
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"name": "id",
|
||||
"title": "ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "no"
|
||||
},
|
||||
{
|
||||
"name": "spec_id",
|
||||
"title": "规格ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "charge_mode",
|
||||
"title": "计费方式",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "subscription_id",
|
||||
"title": "订购ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "userid",
|
||||
"title": "用户ID",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "userorgid",
|
||||
"title": "用户机构",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "use_date",
|
||||
"title": "使用日期",
|
||||
"type": "date",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "use_time",
|
||||
"title": "使用时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "period_start",
|
||||
"title": "计费周期起",
|
||||
"type": "date",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "period_end",
|
||||
"title": "计费周期止",
|
||||
"type": "date",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "quantity",
|
||||
"title": "数量",
|
||||
"type": "double",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"nullable": "yes",
|
||||
"default": "1"
|
||||
},
|
||||
{
|
||||
"name": "usages",
|
||||
"title": "用量信息JSON",
|
||||
"type": "text",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "transno",
|
||||
"title": "交易号",
|
||||
"type": "str",
|
||||
"length": 64,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "amount",
|
||||
"title": "交易金额",
|
||||
"type": "double",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "amount_currency",
|
||||
"title": "计费币种",
|
||||
"type": "str",
|
||||
"length": 8,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "amount_base",
|
||||
"title": "折本位币金额",
|
||||
"type": "double",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "cost",
|
||||
"title": "成本",
|
||||
"type": "double",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "cost_currency",
|
||||
"title": "成本币种",
|
||||
"type": "str",
|
||||
"length": 8,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "cost_base",
|
||||
"title": "折本位币成本",
|
||||
"type": "double",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "discount_rate",
|
||||
"title": "折扣率",
|
||||
"type": "double",
|
||||
"length": 8,
|
||||
"dec": 4,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "accounting_status",
|
||||
"title": "记账状态",
|
||||
"type": "str",
|
||||
"length": 16,
|
||||
"nullable": "yes",
|
||||
"default": "pending"
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"title": "状态",
|
||||
"type": "str",
|
||||
"length": 16,
|
||||
"nullable": "yes",
|
||||
"default": "active"
|
||||
},
|
||||
{
|
||||
"name": "org_id",
|
||||
"title": "所属机构",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "created_by",
|
||||
"title": "创建人",
|
||||
"type": "str",
|
||||
"length": 32,
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"title": "创建时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"title": "更新时间",
|
||||
"type": "timestamp",
|
||||
"nullable": "yes"
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"name": "idx_au_user",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"userid",
|
||||
"use_date"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_au_org",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"userorgid",
|
||||
"use_date"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_au_acct",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"accounting_status"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "idx_au_sub",
|
||||
"idxtype": "index",
|
||||
"idxfields": [
|
||||
"subscription_id"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
17
pyproject.toml
Normal file
17
pyproject.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=45", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "account-resource"
|
||||
version = "1.0.0"
|
||||
description = "账号资源模块 - 账号规格/权益配额/定价映射(spec×charge_mode→ppid)"
|
||||
requires-python = ">=3.8"
|
||||
dependencies = [
|
||||
"sqlor",
|
||||
"bricks_for_python",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["."]
|
||||
include = ["account_resource*"]
|
||||
33
scripts/load_path.py
Normal file
33
scripts/load_path.py
Normal file
@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
"""RBAC path registration for account_resource module.
|
||||
|
||||
宿主根目录执行:py3/bin/python pkgs/account_resource/scripts/load_path.py
|
||||
(pipeline-app 的 load_path.sh 自动扫描 pkgs/*/scripts/load_path.py)
|
||||
"""
|
||||
import subprocess
|
||||
|
||||
MOD = "account_resource"
|
||||
|
||||
PATHS_ANY = []
|
||||
|
||||
PATHS_LOGINED = [
|
||||
f"/{MOD}",
|
||||
f"/{MOD}/acctres_spec/index.ui",
|
||||
f"/{MOD}/acctres_pricing_map/index.ui",
|
||||
f"/{MOD}/acctres_usage/index.ui",
|
||||
]
|
||||
|
||||
|
||||
def register_paths():
|
||||
for path in PATHS_ANY:
|
||||
subprocess.run(["py3/bin/python", "set_role_perm.py", "any", path])
|
||||
print(f" any: {path}")
|
||||
for path in PATHS_LOGINED:
|
||||
subprocess.run(["py3/bin/python", "set_role_perm.py", "logined", path])
|
||||
print(f" logined: {path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"=== {MOD} RBAC registration ===")
|
||||
register_paths()
|
||||
print(f"Done. any={len(PATHS_ANY)} logined={len(PATHS_LOGINED)}")
|
||||
Loading…
x
Reference in New Issue
Block a user