diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/dingdingflow/dingtalk_client.py b/dingdingflow/dingtalk_client.py index 7d3bdbf..696e797 100644 --- a/dingdingflow/dingtalk_client.py +++ b/dingdingflow/dingtalk_client.py @@ -120,7 +120,8 @@ class DingTalkClient: ) return "" - def create_approval_instance(self, process_code, form_data, originator_user_id): + def create_approval_instance(self, process_code, form_data, originator_user_id, + approvers_v2=None, cc_list=None, cc_position="START"): """ Create a DingTalk approval instance. @@ -129,6 +130,13 @@ class DingTalkClient: form_data: List of form component values, e.g.: [{"name": "审批类型", "value": "内容发布"}, ...] originator_user_id: DingTalk user ID of the applicant + approvers_v2: Optional dynamic approver chain, overrides the template's + default nodes. List of node dicts, each: + {"user_ids": ["dingtalk_uid1", ...], + "action_type": "NONE" | "OR" | "AND"} + action_type: NONE=单人节点;OR=会签(一人通过即过);AND=或签(须全部通过) + cc_list: Optional list of DingTalk user IDs to cc. + cc_position: cc timing: "START" / "FINISH" / "START_FINISH". Returns: dict with keys: @@ -139,9 +147,10 @@ class DingTalkClient: if self.is_dev_mode: mock_instance_id = f"mock_instance_{int(time.time())}" logger.info( - "Dev mode: mock approval instance created: %s (process_code=%s)", + "Dev mode: mock approval instance created: %s (process_code=%s, approvers=%d nodes)", mock_instance_id, process_code, + len(approvers_v2) if approvers_v2 else 0, ) return { "success": True, @@ -161,6 +170,12 @@ class DingTalkClient: "dept_id": -1, "form_component_values": form_data, } + # 动态审批节点链(系统内配置,覆盖模板默认审批人) + if approvers_v2: + payload["approvers_v2"] = approvers_v2 + if cc_list: + payload["cc_list"] = cc_list + payload["cc_position"] = cc_position result = self._http_post(url, data=payload, params={"access_token": token}) diff --git a/dingdingflow/init.py b/dingdingflow/init.py index ae02d18..5207faa 100644 --- a/dingdingflow/init.py +++ b/dingdingflow/init.py @@ -193,6 +193,22 @@ async def submit_approval(biz_type, biz_id, title, applicant_id, org_id='0'): process_code = config.get('process_code', '') if isinstance(config, dict) else getattr(config, 'process_code', '') or '' form_config_raw = config.get('form_config', '') if isinstance(config, dict) else getattr(config, 'form_config', '') or '' + approvers_raw = config.get('approvers_config', '') if isinstance(config, dict) else getattr(config, 'approvers_config', '') or '' + + # 解析系统内配置的审批节点链(覆盖钉钉模板默认审批人) + # 格式: [{"user_ids": ["钉钉uid", ...], "action_type": "NONE|OR|AND"}, ...] + approvers_v2 = None + if approvers_raw: + try: + _av = json.loads(approvers_raw) if isinstance(approvers_raw, str) else approvers_raw + if isinstance(_av, list) and _av: + approvers_v2 = [ + {"user_ids": n.get("user_ids", []) if isinstance(n.get("user_ids"), list) else [n.get("user_ids", "")], + "action_type": n.get("action_type", "NONE")} + for n in _av if n.get("user_ids") + ] or None + except (json.JSONDecodeError, TypeError) as e: + logger.warning('Failed to parse approvers_config: %s', str(e)) # 从form_config构建表单数据 form_data = [] @@ -212,7 +228,8 @@ async def submit_approval(biz_type, biz_id, title, applicant_id, org_id='0'): ] # 调用钉钉API - result = client.create_approval_instance(process_code, form_data, applicant_id) + result = client.create_approval_instance(process_code, form_data, applicant_id, + approvers_v2=approvers_v2) if not result['success']: # API失败仍然创建记录 diff --git a/i18n/en/msg.txt b/i18n/en/msg.txt index c1d9318..81d99ee 100644 --- a/i18n/en/msg.txt +++ b/i18n/en/msg.txt @@ -1,10 +1,27 @@ -钉钉审批: DingTalk Approval -审批记录: Approval Records -审批流程配置: Approval Flow Config -审批标题: Approval Title -审批意见: Approval Comment -业务类型: Business Type -钉钉审批模板编码: DingTalk Process Code -待审批: Pending -已通过: Approved -已驳回: Rejected +钉钉审批=DingTalk Approval +审批记录=Approval Records +审批流程配置=Approval Flow Config +审批标题=Approval Title +审批意见=Approval Comment +业务类型=Business Type +钉钉审批模板编码=DingTalk Process Code +待审批=Pending +已通过=Approved +已驳回=Rejected +审批节点配置=Approval Node Config +审批节点链JSON=Approver Chain JSON +钉钉应用AgentId=DingTalk Agent ID +表单字段配置JSON=Form Field Config JSON +业务类型名称=Business Type Name +启用=Active +停用=Inactive +组织ID=Org ID +产线步骤审批=Pipeline Step Approval +产线部署审批=Pipeline Deploy Approval +商机研发审批=Opportunity R&D Approval +内容发布=Content Publish +发起人=Initiated By +审批人=Resolved By +钉钉审批实例ID=DingTalk Instance ID +完成时间=Completed At +已取消=Cancelled diff --git a/i18n/zh/msg.txt b/i18n/zh/msg.txt index b9cfc3d..6ff1553 100644 --- a/i18n/zh/msg.txt +++ b/i18n/zh/msg.txt @@ -1,10 +1,27 @@ -钉钉审批: 钉钉审批 -审批记录: 审批记录 -审批流程配置: 审批流程配置 -审批标题: 审批标题 -审批意见: 审批意见 -业务类型: 业务类型 -钉钉审批模板编码: 钉钉审批模板编码 -待审批: 待审批 -已通过: 已通过 -已驳回: 已驳回 +钉钉审批=钉钉审批 +审批记录=审批记录 +审批流程配置=审批流程配置 +审批标题=审批标题 +审批意见=审批意见 +业务类型=业务类型 +钉钉审批模板编码=钉钉审批模板编码 +待审批=待审批 +已通过=已通过 +已驳回=已驳回 +审批节点配置=审批节点配置 +审批节点链JSON=审批节点链JSON +钉钉应用AgentId=钉钉应用AgentId +表单字段配置JSON=表单字段配置JSON +业务类型名称=业务类型名称 +启用=启用 +停用=停用 +组织ID=组织ID +产线步骤审批=产线步骤审批 +产线部署审批=产线部署审批 +商机研发审批=商机研发审批 +内容发布=内容发布 +发起人=发起人 +审批人=审批人 +钉钉审批实例ID=钉钉审批实例ID +完成时间=完成时间 +已取消=已取消 diff --git a/json/dda_approval_configs.json b/json/dda_approval_configs.json index 8fee1a4..dcba60c 100644 --- a/json/dda_approval_configs.json +++ b/json/dda_approval_configs.json @@ -1,16 +1,13 @@ { "tblname": "dda_approval_configs", "alias": "dda_approval_configs", - "title": "审批流程配置", + "title": "审批节点配置", "params": { "sortby": [ "biz_type" ], "browserfields": { - "exclouded": [ - "id", - "form_config" - ], + "exclouded": [], "alters": { "is_active": { "uitype": "code", @@ -27,10 +24,30 @@ } } }, + "editexclouded": [ + "id", + "org_id", + "created_at", + "updated_at" + ], "editable": { - "new_data_url": "{{entire_url('../api/dingdingflow/dd_approval_configs_create.dspy')}}", - "update_data_url": "{{entire_url('../api/dingdingflow/dd_approval_configs_update.dspy')}}", - "delete_data_url": "{{entire_url('../api/dingdingflow/dd_approval_configs_delete.dspy')}}" + "new_data_url": "{{entire_url('../api/dd_approval_configs_create.dspy')}}", + "update_data_url": "{{entire_url('../api/dd_approval_configs_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/dd_approval_configs_delete.dspy')}}" + }, + "data_filter": { + "AND": [ + { + "field": "biz_type", + "op": "LIKE", + "var": "biz_type_input" + }, + { + "field": "is_active", + "op": "=", + "var": "is_active_input" + } + ] } } } diff --git a/json/dda_approvals.json b/json/dda_approvals.json index 1cda1aa..1bd9dfc 100644 --- a/json/dda_approvals.json +++ b/json/dda_approvals.json @@ -59,21 +59,25 @@ "text": "内容发布" }, { - "value": "content_update", - "text": "内容修改" + "value": "pipeline_approval", + "text": "产线步骤审批" }, { - "value": "content_delete", - "text": "内容删除" + "value": "pipeline_deploy", + "text": "产线部署审批" + }, + { + "value": "opp_dev_approval", + "text": "商机研发审批" } ] } } }, "editable": { - "new_data_url": "{{entire_url('../api/dingdingflow/dd_approvals_create.dspy')}}", - "update_data_url": "{{entire_url('../api/dingdingflow/dd_approvals_update.dspy')}}", - "delete_data_url": "{{entire_url('../api/dingdingflow/dd_approvals_delete.dspy')}}" + "new_data_url": "{{entire_url('../api/dd_approvals_create.dspy')}}", + "update_data_url": "{{entire_url('../api/dd_approvals_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/dd_approvals_delete.dspy')}}" } } } diff --git a/models/dda_approval_configs.json b/models/dda_approval_configs.json index 20ecdc3..e88c0fd 100644 --- a/models/dda_approval_configs.json +++ b/models/dda_approval_configs.json @@ -53,6 +53,11 @@ "title": "表单字段配置JSON", "type": "text" }, + { + "name": "approvers_config", + "title": "审批节点链JSON", + "type": "text" + }, { "name": "is_active", "title": "是否启用(1/0)", diff --git a/scripts/load_path.py b/scripts/load_path.py index 847dc37..2a29956 100644 --- a/scripts/load_path.py +++ b/scripts/load_path.py @@ -24,6 +24,17 @@ PATHS_LOGINED = [ f"/{MOD}/api/dd_approvals_delete.dspy", f"/{MOD}/api/dd_approvals_list.dspy", f"/{MOD}/api/dd_approvals_update.dspy", + # 审批节点配置 / 审批记录 管理页(xls2ui 生成) + f"/{MOD}/dda_approval_configs/index.ui", + f"/{MOD}/dda_approval_configs/get_dda_approval_configs.dspy", + f"/{MOD}/dda_approval_configs/add_dda_approval_configs.dspy", + f"/{MOD}/dda_approval_configs/update_dda_approval_configs.dspy", + f"/{MOD}/dda_approval_configs/delete_dda_approval_configs.dspy", + f"/{MOD}/dda_approvals/index.ui", + f"/{MOD}/dda_approvals/get_dda_approvals.dspy", + f"/{MOD}/dda_approvals/add_dda_approvals.dspy", + f"/{MOD}/dda_approvals/update_dda_approvals.dspy", + f"/{MOD}/dda_approvals/delete_dda_approvals.dspy", ] diff --git a/scripts/migrate_approvers_config.sql b/scripts/migrate_approvers_config.sql new file mode 100644 index 0000000..8c8409b --- /dev/null +++ b/scripts/migrate_approvers_config.sql @@ -0,0 +1,3 @@ +-- dingdingflow 迁移:审批节点链字段(2026-08-28) +-- 幂等:列已存在时报错可忽略 +ALTER TABLE dda_approval_configs ADD COLUMN approvers_config TEXT; diff --git a/wwwroot/api/dd_approval_configs_create.dspy b/wwwroot/api/dd_approval_configs_create.dspy index d38ae9a..63d7705 100644 --- a/wwwroot/api/dd_approval_configs_create.dspy +++ b/wwwroot/api/dd_approval_configs_create.dspy @@ -15,7 +15,7 @@ try: dbname = get_module_dbname('dingdingflow') async with DBPools().sqlorContext(dbname) as sor: await sor.sqlExe( - "INSERT INTO dda_approval_configs (id, org_id, biz_type, biz_type_title, process_code, agent_id, form_config, is_active, created_at, updated_at) VALUES (${id}$, ${org_id}$, ${biz_type}$, ${biz_type_title}$, ${process_code}$, ${agent_id}$, ${form_config}$, ${is_active}$, ${created_at}$, ${updated_at}$)", + "INSERT INTO dda_approval_configs (id, org_id, biz_type, biz_type_title, process_code, agent_id, form_config, approvers_config, is_active, created_at, updated_at) VALUES (${id}$, ${org_id}$, ${biz_type}$, ${biz_type_title}$, ${process_code}$, ${agent_id}$, ${form_config}$, ${approvers_config}$, ${is_active}$, ${created_at}$, ${updated_at}$)", { 'id': new_id, 'org_id': org_id, @@ -24,6 +24,7 @@ try: 'process_code': process_code, 'agent_id': params_kw.get('agent_id', ''), 'form_config': params_kw.get('form_config', ''), + 'approvers_config': params_kw.get('approvers_config', ''), 'is_active': params_kw.get('is_active', '1'), 'created_at': now_str, 'updated_at': now_str diff --git a/wwwroot/api/dd_approval_configs_list.dspy b/wwwroot/api/dd_approval_configs_list.dspy index eec4d58..694b73f 100644 --- a/wwwroot/api/dd_approval_configs_list.dspy +++ b/wwwroot/api/dd_approval_configs_list.dspy @@ -34,7 +34,7 @@ try: 'rows': int(params_kw.get('rows', 20)), 'sort': params_kw.get('sort', 'biz_type') } - sql = "SELECT id, org_id, biz_type, biz_type_title, process_code, agent_id, form_config, is_active, created_at, updated_at FROM dda_approval_configs" + where_prefix + where_sql + sql = "SELECT id, org_id, biz_type, biz_type_title, process_code, agent_id, form_config, approvers_config, is_active, created_at, updated_at FROM dda_approval_configs" + where_prefix + where_sql query_ns = dict(list(ns.items()) + list(where_ns.items())) rows = await sor.sqlExe(sql, query_ns) diff --git a/wwwroot/api/dd_approval_configs_update.dspy b/wwwroot/api/dd_approval_configs_update.dspy index 59d2860..a023c33 100644 --- a/wwwroot/api/dd_approval_configs_update.dspy +++ b/wwwroot/api/dd_approval_configs_update.dspy @@ -10,7 +10,7 @@ try: update_fields = [] update_ns = {'id': record_id} - for field in ['biz_type', 'biz_type_title', 'process_code', 'agent_id', 'form_config', 'is_active']: + for field in ['biz_type', 'biz_type_title', 'process_code', 'agent_id', 'form_config', 'approvers_config', 'is_active']: val = params_kw.get(field) if val is not None: update_fields.append(f"{field}=${field}$") diff --git a/wwwroot/dda_approval_configs/add_dda_approval_configs.dspy b/wwwroot/dda_approval_configs/add_dda_approval_configs.dspy new file mode 100644 index 0000000..07dd8cc --- /dev/null +++ b/wwwroot/dda_approval_configs/add_dda_approval_configs.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('dda_approval_configs', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/dda_approval_configs/delete_dda_approval_configs.dspy b/wwwroot/dda_approval_configs/delete_dda_approval_configs.dspy new file mode 100644 index 0000000..fa65d87 --- /dev/null +++ b/wwwroot/dda_approval_configs/delete_dda_approval_configs.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('dda_approval_configs', ns) + debug('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/dda_approval_configs/get_dda_approval_configs.dspy b/wwwroot/dda_approval_configs/get_dda_approval_configs.dspy new file mode 100644 index 0000000..ce9462f --- /dev/null +++ b/wwwroot/dda_approval_configs/get_dda_approval_configs.dspy @@ -0,0 +1,122 @@ + +ns = params_kw.copy() + + +debug(f'get_dda_approval_configs.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = ["biz_type"] + + + +sql = '''select * from dda_approval_configs where 1=1 [[filterstr]]''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "biz_type", + "title": "业务类型", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "biz_type_title", + "title": "业务类型名称", + "type": "str", + "length": 100 + }, + { + "name": "process_code", + "title": "钉钉审批模板编码", + "type": "str", + "length": 100 + }, + { + "name": "agent_id", + "title": "钉钉应用AgentId", + "type": "str", + "length": 100 + }, + { + "name": "form_config", + "title": "表单字段配置JSON", + "type": "text" + }, + { + "name": "approvers_config", + "title": "审批节点链JSON", + "type": "text" + }, + { + "name": "is_active", + "title": "是否启用(1/0)", + "type": "str", + "length": 1, + "default": "1" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/dda_approval_configs/index.ui b/wwwroot/dda_approval_configs/index.ui new file mode 100644 index 0000000..2666fb2 --- /dev/null +++ b/wwwroot/dda_approval_configs/index.ui @@ -0,0 +1,221 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"dda_approval_configs_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"审批流程配置表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_dda_approval_configs.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_dda_approval_configs.dspy')}}", + + + "update_data_url":"{{entire_url('update_dda_approval_configs.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_dda_approval_configs.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [], + "alters": { + "is_active": { + "uitype": "code", + "data": [ + { + "value": "1", + "text": "启用" + }, + { + "value": "0", + "text": "停用" + } + ] + } + } +}, + + + "editexclouded":[ + "id", + "org_id", + "created_at", + "updated_at" +], + + "fields":[ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键ID" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "default": "0", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "组织ID" + }, + { + "name": "biz_type", + "title": "业务类型", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "业务类型" + }, + { + "name": "biz_type_title", + "title": "业务类型名称", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "业务类型名称" + }, + { + "name": "process_code", + "title": "钉钉审批模板编码", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "钉钉审批模板编码" + }, + { + "name": "agent_id", + "title": "钉钉应用AgentId", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "钉钉应用AgentId" + }, + { + "name": "form_config", + "title": "表单字段配置JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "表单字段配置JSON" + }, + { + "name": "approvers_config", + "title": "审批节点链JSON", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "审批节点链JSON" + }, + { + "name": "is_active", + "title": "是否启用(1/0)", + "type": "str", + "length": 1, + "default": "1", + "cwidth": 4, + "uitype": "code", + "datatype": "str", + "label": "是否启用(1/0)", + "data": [ + { + "value": "1", + "text": "启用" + }, + { + "value": "0", + "text": "停用" + } + ] + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "更新时间" + } +] + }, + + + + "data_filter":{ + "AND": [ + { + "field": "biz_type", + "op": "LIKE", + "var": "biz_type_input" + }, + { + "field": "is_active", + "op": "=", + "var": "is_active_input" + } + ] +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/wwwroot/dda_approval_configs/update_dda_approval_configs.dspy b/wwwroot/dda_approval_configs/update_dda_approval_configs.dspy new file mode 100644 index 0000000..b1e2459 --- /dev/null +++ b/wwwroot/dda_approval_configs/update_dda_approval_configs.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('dda_approval_configs', ns) + debug('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/dda_approvals/add_dda_approvals.dspy b/wwwroot/dda_approvals/add_dda_approvals.dspy new file mode 100644 index 0000000..f2854c8 --- /dev/null +++ b/wwwroot/dda_approvals/add_dda_approvals.dspy @@ -0,0 +1,37 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id + + + +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('dda_approvals', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "cwidth":16, + "cheight":9, + "title":"Add Success", + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/dda_approvals/delete_dda_approvals.dspy b/wwwroot/dda_approvals/delete_dda_approvals.dspy new file mode 100644 index 0000000..dcb04a5 --- /dev/null +++ b/wwwroot/dda_approvals/delete_dda_approvals.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('dda_approvals', ns) + debug('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"ok" + } + } + +debug('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/dda_approvals/get_dda_approvals.dspy b/wwwroot/dda_approvals/get_dda_approvals.dspy new file mode 100644 index 0000000..6b7d4db --- /dev/null +++ b/wwwroot/dda_approvals/get_dda_approvals.dspy @@ -0,0 +1,132 @@ + +ns = params_kw.copy() + + +debug(f'get_dda_approvals.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = ["created_at desc"] + + + +sql = '''select * from dda_approvals where 1=1 [[filterstr]]''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "biz_type", + "title": "业务类型(content_publish等)", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "biz_id", + "title": "业务数据ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "title", + "title": "审批标题", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "applicant_id", + "title": "申请人ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "approver_id", + "title": "审批人ID", + "type": "str", + "length": 32 + }, + { + "name": "dingtalk_instance_id", + "title": "钉钉审批实例ID", + "type": "str", + "length": 100 + }, + { + "name": "status", + "title": "状态(pending/approved/rejected/cancelled)", + "type": "str", + "length": 32, + "default": "pending" + }, + { + "name": "comment", + "title": "审批意见", + "type": "text" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp" + }, + { + "name": "completed_at", + "title": "完成时间", + "type": "datetime" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +filterdic = ns.copy() +filterdic['filterstr'] = '' +filterdic['userorgid'] = '${userorgid}$' +filterdic['userid'] = '${userid}$' +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + conds = f' and {conds}' + filterdic['filterstr'] = conds +ac = ArgsConvert('[[', ']]') +vars = ac.findAllVariables(sql) +NameSpace = {v:'${' + v + '}$' for v in vars if v != 'filterstr' } +filterdic.update(NameSpace) +sql = ac.convert(sql, filterdic) + +debug(f'{sql=}') +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} \ No newline at end of file diff --git a/wwwroot/dda_approvals/index.ui b/wwwroot/dda_approvals/index.ui new file mode 100644 index 0000000..c08d5b3 --- /dev/null +++ b/wwwroot/dda_approvals/index.ui @@ -0,0 +1,290 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"dda_approvals_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"审批记录表", + + + + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_dda_approvals.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_dda_approvals.dspy')}}", + + + "update_data_url":"{{entire_url('update_dda_approvals.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_dda_approvals.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id" + ], + "alters": { + "status": { + "uitype": "code", + "data": [ + { + "value": "pending", + "text": "待审批" + }, + { + "value": "approved", + "text": "已通过" + }, + { + "value": "rejected", + "text": "已拒绝" + }, + { + "value": "cancelled", + "text": "已取消" + } + ] + }, + "biz_type": { + "uitype": "code", + "data": [ + { + "value": "content_publish", + "text": "内容发布" + }, + { + "value": "pipeline_approval", + "text": "产线步骤审批" + }, + { + "value": "pipeline_deploy", + "text": "产线部署审批" + }, + { + "value": "opp_dev_approval", + "text": "商机研发审批" + } + ] + } + } +}, + + + "fields":[ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "主键ID" + }, + { + "name": "org_id", + "title": "组织ID", + "type": "str", + "length": 32, + "default": "0", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "组织ID" + }, + { + "name": "biz_type", + "title": "业务类型(content_publish等)", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "code", + "datatype": "str", + "label": "业务类型(content_publish等)", + "data": [ + { + "value": "content_publish", + "text": "内容发布" + }, + { + "value": "pipeline_approval", + "text": "产线步骤审批" + }, + { + "value": "pipeline_deploy", + "text": "产线部署审批" + }, + { + "value": "opp_dev_approval", + "text": "商机研发审批" + } + ] + }, + { + "name": "biz_id", + "title": "业务数据ID", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "业务数据ID" + }, + { + "name": "title", + "title": "审批标题", + "type": "str", + "length": 255, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "审批标题" + }, + { + "name": "applicant_id", + "title": "申请人ID", + "type": "str", + "length": 32, + "nullable": "no", + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "申请人ID" + }, + { + "name": "approver_id", + "title": "审批人ID", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "审批人ID" + }, + { + "name": "dingtalk_instance_id", + "title": "钉钉审批实例ID", + "type": "str", + "length": 100, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "钉钉审批实例ID" + }, + { + "name": "status", + "title": "状态(pending/approved/rejected/cancelled)", + "type": "str", + "length": 32, + "default": "pending", + "cwidth": 18, + "uitype": "code", + "datatype": "str", + "label": "状态(pending/approved/rejected/cancelled)", + "data": [ + { + "value": "pending", + "text": "待审批" + }, + { + "value": "approved", + "text": "已通过" + }, + { + "value": "rejected", + "text": "已拒绝" + }, + { + "value": "cancelled", + "text": "已取消" + } + ] + }, + { + "name": "comment", + "title": "审批意见", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "审批意见" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "length": 0, + "uitype": "str", + "datatype": "timestamp", + "label": "创建时间" + }, + { + "name": "completed_at", + "title": "完成时间", + "type": "datetime", + "length": 0, + "uitype": "str", + "datatype": "datetime", + "label": "完成时间" + } +] + }, + + + + "data_filter":{ + "AND": [ + { + "field": "status", + "op": "=", + "var": "status_filter" + }, + { + "field": "biz_type", + "op": "=", + "var": "biz_type_filter" + }, + { + "field": "title", + "op": "LIKE", + "var": "title_filter" + } + ] +}, + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[] + +}] +} \ No newline at end of file diff --git a/wwwroot/dda_approvals/update_dda_approvals.dspy b/wwwroot/dda_approvals/update_dda_approvals.dspy new file mode 100644 index 0000000..54c6013 --- /dev/null +++ b/wwwroot/dda_approvals/update_dda_approvals.dspy @@ -0,0 +1,36 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + + + +db = DBPools() +dbname = get_module_dbname('dingdingflow') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('dda_approvals', ns) + debug('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} \ No newline at end of file