diff --git a/models/distribution_agreement_items.json b/models/distribution_agreement_items.json index 5000b0d..2bbe0fd 100644 --- a/models/distribution_agreement_items.json +++ b/models/distribution_agreement_items.json @@ -87,6 +87,18 @@ "table": "distribution_agreements", "valuefield": "id", "textfield": "agreement_name" + }, + { + "field": "prodtypeid", + "table": "product_types", + "valuefield": "id", + "textfield": "type_name" + }, + { + "field": "productid", + "table": "products", + "valuefield": "id", + "textfield": "product_name" } ] } diff --git a/models/supply_contract_items.json b/models/supply_contract_items.json index 4432865..f16ecb2 100644 --- a/models/supply_contract_items.json +++ b/models/supply_contract_items.json @@ -87,6 +87,18 @@ "table": "supply_contracts", "valuefield": "id", "textfield": "contract_name" + }, + { + "field": "prodtypeid", + "table": "product_types", + "valuefield": "id", + "textfield": "type_name" + }, + { + "field": "productid", + "table": "products", + "valuefield": "id", + "textfield": "product_name" } ] } diff --git a/models/supplychain_accounting.json b/models/supplychain_accounting.json index a52eeb3..b860a3e 100644 --- a/models/supplychain_accounting.json +++ b/models/supplychain_accounting.json @@ -223,6 +223,18 @@ "valuefield": "id", "textfield": "supplier_name" }, + { + "field": "prodtypeid", + "table": "product_types", + "valuefield": "id", + "textfield": "type_name" + }, + { + "field": "productid", + "table": "products", + "valuefield": "id", + "textfield": "product_name" + }, { "field": "resellerid", "table": "organization", diff --git a/scripts/load_path.py b/scripts/load_path.py new file mode 100755 index 0000000..a02aab4 --- /dev/null +++ b/scripts/load_path.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +""" +supplychain 模块 RBAC 权限管理脚本 + +使用方法: + cd ~/repos/sage + ./py3/bin/python ~/repos/supplychain/scripts/load_path.py + +此脚本注册 supplychain 模块的所有 RBAC 权限路径。 +每次代码变更如有新 path 出现,需同步更新本脚本的 paths 列表。 + +路径分类: + - any: 静态资源/菜单,无需登录 + - logined: 需要认证的页面和 API + - reseller.operator: 运营角色 — 供应商、供销合同管理 + - reseller.sale: 销售角色 — 二级分销商、分销协议管理 +""" + +import subprocess +import os +import sys + +# 查找 Sage 根目录 +def find_sage_root(): + candidates = [ + os.path.expanduser("~/repos/sage"), + os.path.expanduser("~/sage"), + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + ] + for c in candidates: + if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")): + return c + return None + +SAGE_ROOT = find_sage_root() +if not SAGE_ROOT: + print("ERROR: Cannot find Sage root directory") + sys.exit(1) + +SET_PERM = os.path.join(SAGE_ROOT, "py3", "bin", "python") +SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py") + +if not os.path.exists(SET_PERM_SCRIPT): + print(f"ERROR: set_role_perm.py not found at {SET_PERM_SCRIPT}") + sys.exit(1) + +# ============================================================ +# 权限路径定义 +# ============================================================ + +# any — 无需登录,静态资源/CRUD 别名目录 +PATHS_ANY = [ + "/supplychain/menu.ui", + "/supplychain/suppliers_list", + "/supplychain/supply_contracts_list", + "/supplychain/supply_contract_items_list", + "/supplychain/sub_distributors_list", + "/supplychain/distribution_agreements_list", + "/supplychain/distribution_agreement_items_list", + "/supplychain/supplychain_accounting_list", +] + +# logined — 需要认证的页面和 API +PATHS_LOGINED = [ + # 模块入口 + "/supplychain", + "/supplychain/index.ui", + # 功能页面 + "/supplychain/suppliers.ui", + "/supplychain/supply_contracts.ui", + "/supplychain/sub_distributors.ui", + "/supplychain/distribution_agreements.ui", + "/supplychain/accounting.ui", + # CRUD 列表页 + "/supplychain/suppliers_list/index.ui", + "/supplychain/supply_contracts_list/index.ui", + "/supplychain/supply_contract_items_list/index.ui", + "/supplychain/sub_distributors_list/index.ui", + "/supplychain/distribution_agreements_list/index.ui", + "/supplychain/distribution_agreement_items_list/index.ui", + "/supplychain/supplychain_accounting_list/index.ui", + # CRUD API — suppliers + "/supplychain/api/suppliers_create.dspy", + "/supplychain/api/suppliers_update.dspy", + "/supplychain/api/suppliers_delete.dspy", + # CRUD API — supply_contracts + "/supplychain/api/supply_contracts_create.dspy", + "/supplychain/api/supply_contracts_update.dspy", + "/supplychain/api/supply_contracts_delete.dspy", + # CRUD API — supply_contract_items + "/supplychain/api/supply_contract_items_create.dspy", + "/supplychain/api/supply_contract_items_update.dspy", + "/supplychain/api/supply_contract_items_delete.dspy", + # CRUD API — sub_distributors + "/supplychain/api/sub_distributors_create.dspy", + "/supplychain/api/sub_distributors_update.dspy", + "/supplychain/api/sub_distributors_delete.dspy", + # CRUD API — distribution_agreements + "/supplychain/api/distribution_agreements_create.dspy", + "/supplychain/api/distribution_agreements_update.dspy", + "/supplychain/api/distribution_agreements_delete.dspy", + # CRUD API — distribution_agreement_items + "/supplychain/api/distribution_agreement_items_create.dspy", + "/supplychain/api/distribution_agreement_items_update.dspy", + "/supplychain/api/distribution_agreement_items_delete.dspy", + # CRUD API — supplychain_accounting + "/supplychain/api/supplychain_accounting_create.dspy", + "/supplychain/api/supplychain_accounting_update.dspy", + "/supplychain/api/supplychain_accounting_delete.dspy", + # 业务 API + "/supplychain/api/calculate_accounting.dspy", + "/supplychain/api/query_supply_discount.dspy", + "/supplychain/api/query_dist_discount.dspy", +] + +# 角色专属权限 +PATHS_OPERATOR = [ + "/supplychain/suppliers.ui", + "/supplychain/suppliers_list", + "/supplychain/suppliers_list/index.ui", + "/supplychain/api/suppliers_create.dspy", + "/supplychain/api/suppliers_update.dspy", + "/supplychain/api/suppliers_delete.dspy", + "/supplychain/supply_contracts.ui", + "/supplychain/supply_contracts_list", + "/supplychain/supply_contracts_list/index.ui", + "/supplychain/api/supply_contracts_create.dspy", + "/supplychain/api/supply_contracts_update.dspy", + "/supplychain/api/supply_contracts_delete.dspy", + "/supplychain/supply_contract_items_list", + "/supplychain/supply_contract_items_list/index.ui", + "/supplychain/api/supply_contract_items_create.dspy", + "/supplychain/api/supply_contract_items_update.dspy", + "/supplychain/api/supply_contract_items_delete.dspy", +] + +PATHS_SALE = [ + "/supplychain/sub_distributors.ui", + "/supplychain/sub_distributors_list", + "/supplychain/sub_distributors_list/index.ui", + "/supplychain/api/sub_distributors_create.dspy", + "/supplychain/api/sub_distributors_update.dspy", + "/supplychain/api/sub_distributors_delete.dspy", + "/supplychain/distribution_agreements.ui", + "/supplychain/distribution_agreements_list", + "/supplychain/distribution_agreements_list/index.ui", + "/supplychain/api/distribution_agreements_create.dspy", + "/supplychain/api/distribution_agreements_update.dspy", + "/supplychain/api/distribution_agreements_delete.dspy", + "/supplychain/distribution_agreement_items_list", + "/supplychain/distribution_agreement_items_list/index.ui", + "/supplychain/api/distribution_agreement_items_create.dspy", + "/supplychain/api/distribution_agreement_items_update.dspy", + "/supplychain/api/distribution_agreement_items_delete.dspy", +] + +# ============================================================ +# 执行注册 +# ============================================================ + +def run_set_perm(role, path, verbose=True): + """Register a single permission path.""" + cmd = [SET_PERM, SET_PERM_SCRIPT, role, path] + result = subprocess.run(cmd, capture_output=True, text=True) + if verbose: + output = result.stdout.strip() + if output: + print(f" {role}: {path} -> {output}") + return result.returncode == 0 + + +def register_role_paths(role, paths): + """Register all paths for a role.""" + count = 0 + for path in paths: + if run_set_perm(role, path, verbose=False): + count += 1 + print(f" {role}: {count}/{len(paths)} paths registered") + return count + + +def main(): + print(f"Sage root: {SAGE_ROOT}") + print(f"set_role_perm.py: {SET_PERM_SCRIPT}") + print() + + total = 0 + + print("[1/4] Registering 'any' role paths...") + total += register_role_paths("any", PATHS_ANY) + + print("[2/4] Registering 'logined' role paths...") + total += register_role_paths("logined", PATHS_LOGINED) + + print("[3/4] Registering 'reseller.operator' role paths...") + total += register_role_paths("reseller.operator", PATHS_OPERATOR) + + print("[4/4] Registering 'reseller.sale' role paths...") + total += register_role_paths("reseller.sale", PATHS_SALE) + + print() + print(f"Done. Total {total} permission entries registered.") + print() + print("NOTE: Restart Sage after permission changes to reload RBAC cache.") + + +if __name__ == "__main__": + main() diff --git a/supplychain/init.py b/supplychain/init.py index 7c83edc..8ef0243 100644 --- a/supplychain/init.py +++ b/supplychain/init.py @@ -10,7 +10,7 @@ MODULE_NAME = "supplychain" MODULE_VERSION = "1.0.0" -def get_module_dbname(): +def _get_dbname(): """Get the database name for the supplychain module.""" env = ServerEnv() return env.get_module_dbname('supplychain') @@ -20,7 +20,7 @@ def get_db_context(): """Get a database context manager for the supplychain module.""" config = getConfig('.') DBPools(config.databases) - dbname = get_module_dbname() + dbname = _get_dbname() return db.sqlorContext(dbname) @@ -249,5 +249,4 @@ def load_supplychain(): env.get_active_supply_discount = get_active_supply_discount env.get_active_dist_discount = get_active_dist_discount env.calculate_sale_accounting = calculate_sale_accounting - env.get_module_dbname = get_module_dbname return True diff --git a/wwwroot/api/distribution_agreement_items_create.dspy b/wwwroot/api/distribution_agreement_items_create.dspy index 784b7fd..6162ce9 100644 --- a/wwwroot/api/distribution_agreement_items_create.dspy +++ b/wwwroot/api/distribution_agreement_items_create.dspy @@ -7,26 +7,15 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid - data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "distribution_agreement_items" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "distribution_agreement_items" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "distribution_agreement_items" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "distribution_agreement_items" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/api/distribution_agreements_create.dspy b/wwwroot/api/distribution_agreements_create.dspy index b841b1a..4d6924d 100644 --- a/wwwroot/api/distribution_agreements_create.dspy +++ b/wwwroot/api/distribution_agreements_create.dspy @@ -7,26 +7,20 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "distribution_agreements" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "distribution_agreements" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "distribution_agreements" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "distribution_agreements" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + + # Auto-generate agreement code + if not data.get("agreement_code"): + data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/api/sub_distributors_create.dspy b/wwwroot/api/sub_distributors_create.dspy index 4065c72..9d9db23 100644 --- a/wwwroot/api/sub_distributors_create.dspy +++ b/wwwroot/api/sub_distributors_create.dspy @@ -7,26 +7,20 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "sub_distributors" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "sub_distributors" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "sub_distributors" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "sub_distributors" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + + # Auto-generate sub distributor code + if not data.get("sub_dist_code"): + data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/api/suppliers_create.dspy b/wwwroot/api/suppliers_create.dspy index 576d634..5dc69c6 100644 --- a/wwwroot/api/suppliers_create.dspy +++ b/wwwroot/api/suppliers_create.dspy @@ -7,26 +7,20 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "suppliers" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "suppliers" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "suppliers" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "suppliers" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + + # Auto-generate supplier code + if not data.get("supplier_code"): + data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/api/supply_contract_items_create.dspy b/wwwroot/api/supply_contract_items_create.dspy index 22bea28..058db72 100644 --- a/wwwroot/api/supply_contract_items_create.dspy +++ b/wwwroot/api/supply_contract_items_create.dspy @@ -7,26 +7,15 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid - data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "supply_contract_items" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supply_contract_items" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supply_contract_items" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supply_contract_items" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/api/supply_contracts_create.dspy b/wwwroot/api/supply_contracts_create.dspy index b5d4342..66f3392 100644 --- a/wwwroot/api/supply_contracts_create.dspy +++ b/wwwroot/api/supply_contracts_create.dspy @@ -7,26 +7,20 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "supply_contracts" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supply_contracts" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supply_contracts" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supply_contracts" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + + # Auto-generate contract code + if not data.get("contract_code"): + data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: diff --git a/wwwroot/api/supplychain_accounting_create.dspy b/wwwroot/api/supplychain_accounting_create.dspy index ba301fb..229ef7b 100644 --- a/wwwroot/api/supplychain_accounting_create.dspy +++ b/wwwroot/api/supplychain_accounting_create.dspy @@ -7,26 +7,16 @@ async def main(request, params_kw): user_id = await get_user() user_orgid = await get_userorgid() dbname = get_module_dbname('supplychain') - + data = params_kw.get("data", "{}") if isinstance(data, str): data = json.loads(data) - + data["id"] = getID() data["resellerid"] = user_orgid data["created_by"] = user_id data["created_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # Auto-generate codes if needed - if "supplychain_accounting" == "suppliers" and not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supplychain_accounting" == "sub_distributors" and not data.get("sub_dist_code"): - data["sub_dist_code"] = f"SUB-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supplychain_accounting" == "supply_contracts" and not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - if "supplychain_accounting" == "distribution_agreements" and not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4]}" - + config = getConfig(".") DBPools(config.databases) async with db.sqlorContext(dbname) as sor: