From 0d841f078e9b9916e87be3ce8b1435ca25ae2968 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 18 Jun 2026 17:36:40 +0800 Subject: [PATCH] feat: pricing_program discount field validation [0-1] - Added rules to discount field: required, number, min:0, max:1 - Frontend: Form validates on submit, shows error below field - Backend: auto-generated dspy validates before DB write --- json/pricing_program.json | 18 +- .../pricing_program/add_pricing_program.dspy | 99 ++++++ .../delete_pricing_program.dspy | 47 +++ .../pricing_program/get_pricing_program.dspy | 135 ++++++++ wwwroot/pricing_program/index.ui | 289 ++++++++++++++++++ .../update_pricing_program.dspy | 118 +++++++ .../add_pricing_program_timing.dspy | 38 +++ .../delete_pricing_program_timing.dspy | 33 ++ .../get_pricing_program_timing.dspy | 93 ++++++ wwwroot/pricing_program_timing/index.ui | 260 ++++++++++++++++ .../update_pricing_program_timing.dspy | 37 +++ 11 files changed, 1162 insertions(+), 5 deletions(-) create mode 100644 wwwroot/pricing_program/add_pricing_program.dspy create mode 100644 wwwroot/pricing_program/delete_pricing_program.dspy create mode 100644 wwwroot/pricing_program/get_pricing_program.dspy create mode 100644 wwwroot/pricing_program/index.ui create mode 100644 wwwroot/pricing_program/update_pricing_program.dspy create mode 100644 wwwroot/pricing_program_timing/add_pricing_program_timing.dspy create mode 100644 wwwroot/pricing_program_timing/delete_pricing_program_timing.dspy create mode 100644 wwwroot/pricing_program_timing/get_pricing_program_timing.dspy create mode 100644 wwwroot/pricing_program_timing/index.ui create mode 100644 wwwroot/pricing_program_timing/update_pricing_program_timing.dspy diff --git a/json/pricing_program.json b/json/pricing_program.json index fc1de61..70d2883 100644 --- a/json/pricing_program.json +++ b/json/pricing_program.json @@ -7,11 +7,19 @@ "browserfields": { "exclouded": ["id", "ownerid", "pricing_spec" ], "alters": { - "providerid":{ - "valueField": "id", - "textField": "orgname", - "dataurl":"{{entire_url('/rbac/get_provider.dspy')}}" - } + "providerid": { + "valueField": "id", + "textField": "orgname", + "dataurl": "{{entire_url('/rbac/get_provider.dspy')}}" + }, + "discount": { + "rules": [ + {"type": "required", "message": "折扣不能为空"}, + {"type": "number", "message": "折扣必须是数字"}, + {"type": "min", "value": 0, "message": "折扣不能小于0"}, + {"type": "max", "value": 1, "message": "折扣不能大于1"} + ] + } } }, "editexclouded": [ diff --git a/wwwroot/pricing_program/add_pricing_program.dspy b/wwwroot/pricing_program/add_pricing_program.dspy new file mode 100644 index 0000000..c2e8805 --- /dev/null +++ b/wwwroot/pricing_program/add_pricing_program.dspy @@ -0,0 +1,99 @@ + +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 + + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid + + +_validation_rules = json.loads(r'''{"discount": [{"type": "required", "message": "折扣不能为空"}, {"type": "number", "message": "折扣必须是数字"}, {"type": "min", "value": 0, "message": "折扣不能小于0"}, {"type": "max", "value": 1, "message": "折扣不能大于1"}]}''') +import re as _re +_errors = [] +for _fname, _rules in _validation_rules.items(): + _val = params_kw.get(_fname, '') + if _val is None: _val = '' + _val = str(_val) + for _rule in _rules: + _rt = _rule.get('type', '') + _rm = _rule.get('message', _fname) + _rv = _rule.get('value') + if _rt == 'required': + if not _val or _val.strip() == '': + _errors.append(_rm) + break + elif _rt == 'minlength': + if _val and len(_val) < int(_rv): + _errors.append(_rm) + break + elif _rt == 'maxlength': + if len(_val) > int(_rv): + _errors.append(_rm) + break + elif _rt in ('min', 'max'): + if _val: + try: + _n = float(_val) + if _rt == 'min' and _n < float(_rv): _errors.append(_rm); break + if _rt == 'max' and _n > float(_rv): _errors.append(_rm); break + except (ValueError, TypeError): + _errors.append(_rm) + break + elif _rt == 'pattern': + if _val and not _re.match(_rv, _val): + _errors.append(_rm) + break + elif _rt == 'email': + if _val and not _re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', _val): + _errors.append(_rm) + break + elif _rt == 'number': + if _val: + try: float(_val) + except (ValueError, TypeError): _errors.append(_rm); break +if _errors: + return {"widgettype":"Error","options":{"title":"Validation Failed","cwidth":16,"cheight":9,"timeout":3,"message":"; ".join(_errors)}} + +db = DBPools() +dbname = get_module_dbname('pricing') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pricing_program', 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/pricing_program/delete_pricing_program.dspy b/wwwroot/pricing_program/delete_pricing_program.dspy new file mode 100644 index 0000000..4b667c3 --- /dev/null +++ b/wwwroot/pricing_program/delete_pricing_program.dspy @@ -0,0 +1,47 @@ + +ns = { + 'id':params_kw['id'], +} + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid + +db = DBPools() +dbname = get_module_dbname('pricing') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pricing_program', 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/pricing_program/get_pricing_program.dspy b/wwwroot/pricing_program/get_pricing_program.dspy new file mode 100644 index 0000000..59b9459 --- /dev/null +++ b/wwwroot/pricing_program/get_pricing_program.dspy @@ -0,0 +1,135 @@ + +ns = params_kw.copy() + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid +ns['userorgid'] = userorgid + +debug(f'get_pricing_program.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + + ns['sort'] = 'name' + + + +sql = '''select a.*, b.ownerid_text, c.providerid_text, d.pricing_belong_text +from (select * from pricing_program where 1=1 [[filterstr]]) a left join (select id as ownerid, + orgname as ownerid_text from organization where 1 = 1) b on a.ownerid = b.ownerid left join (select id as providerid, + orgname as providerid_text from organization where 1 = 1) c on a.providerid = c.providerid left join (select k as pricing_belong, + v as pricing_belong_text from appcodes_kv where parentid='pricing_belong') d on a.pricing_belong = d.pricing_belong''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +# data_filter可能是CRUD字段定义({"fields":[...]}),不是过滤条件,忽略 +if filterjson and isinstance(filterjson, dict) and 'fields' in filterjson: + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "项目名称", + "type": "str", + "length": 256 + }, + { + "name": "ownerid", + "title": "所属机构", + "type": "str", + "length": 32 + }, + { + "name": "providerid", + "title": "供应商", + "type": "str", + "length": 32 + }, + { + "name": "pricing_belong", + "title": "定价属于", + "type": "str", + "length": 32 + }, + { + "name": "discount", + "title": "供应商折扣", + "type": "float", + "length": 18, + "dec": 2 + }, + { + "name": "description", + "title": "描述", + "type": "text" + }, + { + "name": "pricing_spec", + "title": "规格明细", + "type": "text" + } +]''' +ori_fields = json.loads(fields_str) +if not filterjson: + fields = [ f['name'] for f in ori_fields ] + filterjson = default_filterjson(fields, ns) + +# 确保 logined 过滤条件始终生效 +if filterjson: + if not isinstance(filterjson, dict) or 'AND' not in filterjson: + filterjson = {'AND': [filterjson] if filterjson else []} + + filterjson['AND'].append({'field': 'ownerid', 'op': '=', 'var': '__logined_orgid__'}) + ns['__logined_orgid__'] = userorgid + + + +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('pricing') +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/pricing_program/index.ui b/wwwroot/pricing_program/index.ui new file mode 100644 index 0000000..3375278 --- /dev/null +++ b/wwwroot/pricing_program/index.ui @@ -0,0 +1,289 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pricing_program_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"定价项目", + + + + + "toolbar":{ + "tools": [ + { + "name": "test", + "label": "测试", + "selected_row": true, + "icon": "{{entire_url('/bricks/imgs/test.svg')}}" + }, + { + "selected_row": true, + "name": "pricing_program_timing", + "icon": "{{entire_url('/imgs/pricing_program_timing.svg')}}", + "label": "定价项目时序" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pricing_program.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pricing_program.dspy')}}", + + + "update_data_url":"{{entire_url('update_pricing_program.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pricing_program.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "ownerid", + "pricing_spec" + ], + "alters": { + "providerid": { + "valueField": "id", + "textField": "orgname", + "dataurl": "{{entire_url('/rbac/get_provider.dspy')}}" + }, + "discount": { + "rules": [ + { + "type": "required", + "message": "折扣不能为空" + }, + { + "type": "number", + "message": "折扣必须是数字" + }, + { + "type": "min", + "value": 0, + "message": "折扣不能小于0" + }, + { + "type": "max", + "value": 1, + "message": "折扣不能大于1" + } + ] + } + } +}, + + + "editexclouded":[ + "id", + "ownerid" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "name", + "title": "项目名称", + "type": "str", + "length": 256, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "项目名称" + }, + { + "name": "ownerid", + "title": "所属机构", + "type": "str", + "length": 32, + "label": "所属机构", + "uitype": "code", + "valueField": "ownerid", + "textField": "ownerid_text", + "params": { + "dbname": "{{get_module_dbname('pricing')}}", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "ownerid", + "textField": "ownerid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "providerid", + "title": "供应商", + "type": "str", + "length": 32, + "label": "供应商", + "uitype": "code", + "valueField": "id", + "textField": "orgname", + "params": { + "dbname": "{{get_module_dbname('pricing')}}", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "providerid", + "textField": "providerid_text" + }, + "dataurl": "{{entire_url('/rbac/get_provider.dspy')}}" + }, + { + "name": "pricing_belong", + "title": "定价属于", + "type": "str", + "length": 32, + "label": "定价属于", + "uitype": "code", + "valueField": "pricing_belong", + "textField": "pricing_belong_text", + "params": { + "dbname": "{{get_module_dbname('pricing')}}", + "table": "appcodes_kv", + "tblvalue": "k", + "tbltext": "v", + "valueField": "pricing_belong", + "textField": "pricing_belong_text", + "cond": "parentid='pricing_belong'" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "discount", + "title": "供应商折扣", + "type": "float", + "length": 18, + "dec": 2, + "cwidth": 18, + "uitype": "float", + "datatype": "float", + "label": "供应商折扣", + "rules": [ + { + "type": "required", + "message": "折扣不能为空" + }, + { + "type": "number", + "message": "折扣必须是数字" + }, + { + "type": "min", + "value": 0, + "message": "折扣不能小于0" + }, + { + "type": "max", + "value": 1, + "message": "折扣不能大于1" + } + ] + }, + { + "name": "description", + "title": "描述", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "描述" + }, + { + "name": "pricing_spec", + "title": "规格明细", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "规格明细" + } +] + }, + + + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "test", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "width": "70%", + "height": "70%", + "auto_open": true, + "archor": "cc", + "title": "定价测试" + }, + "options": { + "url": "{{entire_url('../test_pricing_program.ui')}}", + "params": {} + } + }, + { + "wid": "self", + "event": "pricing_program_timing", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "定价项目时序", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=pricing_program_timing", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "ppid", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../pricing_program_timing')}}" + } + } +] + +}] +} \ No newline at end of file diff --git a/wwwroot/pricing_program/update_pricing_program.dspy b/wwwroot/pricing_program/update_pricing_program.dspy new file mode 100644 index 0000000..0a006df --- /dev/null +++ b/wwwroot/pricing_program/update_pricing_program.dspy @@ -0,0 +1,118 @@ + +ns = params_kw.copy() +for k,v in ns.items(): + if v == 'NaN' or v == 'null': + ns[k] = None + + +userorgid = await get_userorgid() +if not userorgid: + return { + "widgettype":"Error", + "options":{ + "title":"Authorization Error", + "timeout":3, + "cwidth":16, + "cheight":9, + "message":"Please login" + } + } +ns['ownerid'] = userorgid + + + +_validation_rules = json.loads(r'''{"discount": [{"type": "required", "message": "折扣不能为空"}, {"type": "number", "message": "折扣必须是数字"}, {"type": "min", "value": 0, "message": "折扣不能小于0"}, {"type": "max", "value": 1, "message": "折扣不能大于1"}]}''') +import re as _re +_errors = [] +for _fname, _rules in _validation_rules.items(): + _val = params_kw.get(_fname, '') + if _val is None: _val = '' + _val = str(_val) + for _rule in _rules: + _rt = _rule.get('type', '') + _rm = _rule.get('message', _fname) + _rv = _rule.get('value') + if _rt == 'required': + if not _val or _val.strip() == '': + _errors.append(_rm) + break + elif _rt == 'minlength': + if _val and len(_val) < int(_rv): + _errors.append(_rm) + break + elif _rt == 'maxlength': + if len(_val) > int(_rv): + _errors.append(_rm) + break + elif _rt in ('min', 'max'): + if _val: + try: + _n = float(_val) + if _rt == 'min' and _n < float(_rv): _errors.append(_rm); break + if _rt == 'max' and _n > float(_rv): _errors.append(_rm); break + except (ValueError, TypeError): + _errors.append(_rm) + break + elif _rt == 'pattern': + if _val and not _re.match(_rv, _val): + _errors.append(_rm) + break + elif _rt == 'email': + if _val and not _re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', _val): + _errors.append(_rm) + break + elif _rt == 'number': + if _val: + try: float(_val) + except (ValueError, TypeError): _errors.append(_rm); break +if _errors: + return {"widgettype":"Error","options":{"title":"Validation Failed","cwidth":16,"cheight":9,"timeout":3,"message":"; ".join(_errors)}} + + +db = DBPools() +dbname = get_module_dbname('pricing') +async with db.sqlorContext(dbname) as sor: + + ns1 = { + + "ownerid": userorgid, + + + "id": params_kw.id + } + recs = await sor.R('pricing_program', ns1) + if len(recs) < 1: + return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"Record no exist or with wrong ownership" + } + } + + r = await sor.U('pricing_program', 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/pricing_program_timing/add_pricing_program_timing.dspy b/wwwroot/pricing_program_timing/add_pricing_program_timing.dspy new file mode 100644 index 0000000..07dfb83 --- /dev/null +++ b/wwwroot/pricing_program_timing/add_pricing_program_timing.dspy @@ -0,0 +1,38 @@ + +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('pricing') +async with db.sqlorContext(dbname) as sor: + r = await sor.C('pricing_program_timing', 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/pricing_program_timing/delete_pricing_program_timing.dspy b/wwwroot/pricing_program_timing/delete_pricing_program_timing.dspy new file mode 100644 index 0000000..97aa5a7 --- /dev/null +++ b/wwwroot/pricing_program_timing/delete_pricing_program_timing.dspy @@ -0,0 +1,33 @@ + +ns = { + 'id':params_kw['id'], +} + + +db = DBPools() +dbname = get_module_dbname('pricing') +async with db.sqlorContext(dbname) as sor: + r = await sor.D('pricing_program_timing', 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/pricing_program_timing/get_pricing_program_timing.dspy b/wwwroot/pricing_program_timing/get_pricing_program_timing.dspy new file mode 100644 index 0000000..f038d80 --- /dev/null +++ b/wwwroot/pricing_program_timing/get_pricing_program_timing.dspy @@ -0,0 +1,93 @@ + +ns = params_kw.copy() + + +debug(f'get_pricing_program_timing.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + + +sql = '''select a.*, b.ppid_text +from (select * from pricing_program_timing where 1=1 [[filterstr]]) a left join (select id as ppid, + name as ppid_text from pricing_program where 1 = 1) b on a.ppid = b.ppid''' + +filterjson = params_kw.get('data_filter') +if filterjson and isinstance(filterjson, str): + try: + filterjson = json.loads(filterjson) + except (json.JSONDecodeError, TypeError): + filterjson = None +# data_filter可能是CRUD字段定义({"fields":[...]}),不是过滤条件,忽略 +if filterjson and isinstance(filterjson, dict) and 'fields' in filterjson: + filterjson = None +fields_str=r'''[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "ppid", + "title": "定价项目id", + "type": "str", + "length": 32 + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 256 + }, + { + "name": "pricing_data", + "title": "定价数据", + "type": "text" + }, + { + "name": "enabled_date", + "title": "启用日期", + "type": "date" + }, + { + "name": "expired_date", + "title": "失效日期", + "type": "date", + "default": "9999-12-31" + } +]''' +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('pricing') +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/pricing_program_timing/index.ui b/wwwroot/pricing_program_timing/index.ui new file mode 100644 index 0000000..01b543f --- /dev/null +++ b/wwwroot/pricing_program_timing/index.ui @@ -0,0 +1,260 @@ + +{ + "widgettype":"VBox", + "options":{"cheight":40,"width":"100%"}, + "subwidgets":[{ + "id":"pricing_program_timing_tbl", + "widgettype":"Tabular", + "options":{ + "width":"100%", + "height":"100%", + + + "title":"定价项目时序", + + + + + "toolbar":{ + "tools": [ + { + "name": "download_pattern", + "label": "定价模版", + "selected_row": true, + "icon": "{{entire_url('/bricks/imgs/download.svg')}}" + }, + { + "name": "upload_pricing_data", + "label": "上传定价数据", + "selected_row": true, + "icon": "{{entire_url('/bricks/imgs/upload.svg')}}" + }, + { + "name": "download_pricing_data", + "label": "下载定价数据", + "selected_row": true, + "icon": "{{entire_url('/bricks/imgs/download.svg')}}" + }, + { + "name": "test", + "selected_row": true, + "label": "验证定价", + "icon": "{{entire_url('/bricks/imgs/test.svg')}}" + }, + { + "selected_row": true, + "name": "pricing_item", + "icon": "{{entire_url('/imgs/pricing_item.svg')}}", + "label": "定价细项" + } + ] +}, + + "css":"card", + + + "editable":{ + + "new_data_url":"{{entire_url('add_pricing_program_timing.dspy')}}", + + + "delete_data_url":"{{entire_url('delete_pricing_program_timing.dspy')}}", + + + "update_data_url":"{{entire_url('update_pricing_program_timing.dspy')}}" + + }, + + + "data_url":"{{entire_url('./get_pricing_program_timing.dspy')}}", + + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4, ensure_ascii=False)}}, + "row_options":{ + + + + "browserfields": { + "exclouded": [ + "id", + "ppid" + ], + "alters": {} +}, + + + "editexclouded":[ + "id", + "ppid", + "name" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "ppid", + "title": "定价项目id", + "type": "str", + "length": 32, + "label": "定价项目id", + "uitype": "code", + "valueField": "ppid", + "textField": "ppid_text", + "params": { + "dbname": "{{get_module_dbname('pricing')}}", + "table": "pricing_program", + "tblvalue": "id", + "tbltext": "name", + "valueField": "ppid", + "textField": "ppid_text" + }, + "dataurl": "{{entire_url('/appbase/get_code.dspy')}}" + }, + { + "name": "name", + "title": "名称", + "type": "str", + "length": 256, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "名称" + }, + { + "name": "pricing_data", + "title": "定价数据", + "type": "text", + "length": 0, + "uitype": "text", + "datatype": "text", + "label": "定价数据" + }, + { + "name": "enabled_date", + "title": "启用日期", + "type": "date", + "length": 0, + "uitype": "date", + "datatype": "date", + "label": "启用日期" + }, + { + "name": "expired_date", + "title": "失效日期", + "type": "date", + "default": "9999-12-31", + "length": 0, + "uitype": "date", + "datatype": "date", + "label": "失效日期" + } +] + }, + + + + + + + + "page_rows":160, + "cache_limit":5 + } + + ,"binds":[ + { + "wid": "self", + "event": "download_pattern", + "actiontype": "newwindow", + "target": "self", + "options": { + "params": { + "ppid": "{{params_kw.ppid}}" + }, + "method": "POST", + "url": "{{entire_url('../download_pricing_pattern.dspy')}}" + } + }, + { + "wid": "self", + "event": "upload_pricing_data", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "上传定价数据" + }, + "options": { + "params": { + "ppid": "{{params_kw.ppid}}" + }, + "method": "POST", + "url": "{{entire_url('../load_pricing_data.ui')}}" + } + }, + { + "wid": "self", + "event": "download_pricing_data", + "actiontype": "newwindow", + "target": "self", + "options": { + "params": { + "id": "{{params_kw.id}}" + }, + "method": "POST", + "url": "{{entire_url('../download_pricing_data.dspy')}}" + } + }, + { + "wid": "self", + "event": "test", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "验证定价" + }, + "options": { + "params": { + "id": "{{params_kw.id}}" + }, + "method": "POST", + "url": "{{entire_url('../pricing_test.ui')}}" + } + }, + { + "wid": "self", + "event": "pricing_item", + "actiontype": "urlwidget", + "target": "PopupWindow", + "popup_options": { + "title": "定价细项", + "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=pricing_item", + "resizable": true, + "height": "70%", + "width": "70%" + }, + "params_mapping": { + "mapping": { + "id": "pptid", + "referer_widget": "referer_widget" + }, + "need_other": false + }, + "options": { + "method": "POST", + "params": {}, + "url": "{{entire_url('../pricing_item')}}" + } + } +] + +}] +} \ No newline at end of file diff --git a/wwwroot/pricing_program_timing/update_pricing_program_timing.dspy b/wwwroot/pricing_program_timing/update_pricing_program_timing.dspy new file mode 100644 index 0000000..f6c468a --- /dev/null +++ b/wwwroot/pricing_program_timing/update_pricing_program_timing.dspy @@ -0,0 +1,37 @@ + +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('pricing') +async with db.sqlorContext(dbname) as sor: + + r = await sor.U('pricing_program_timing', 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