diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..966fc4e --- /dev/null +++ b/build.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +# Find Sage root directory +SAGE_ROOT="" +for candidate in "$SCRIPT_DIR/../.." "$HOME/repos/sage" "$HOME/sage"; do + if [ -d "$candidate/wwwroot" ] && [ -d "$candidate/py3/bin" ]; then + SAGE_ROOT="$(cd "$candidate" && pwd)" + break + fi +done + +if [ -z "$SAGE_ROOT" ]; then + echo "ERROR: Cannot find Sage root directory" + exit 1 +fi + +echo "Sage root: $SAGE_ROOT" + +# Install module +cd "$SCRIPT_DIR" +$SAGE_ROOT/py3/bin/pip install -e . + +# Generate DDL from models +if [ -d "$SCRIPT_DIR/models" ]; then + echo "Generating DDL..." + cd "$SCRIPT_DIR/models" + $SAGE_ROOT/py3/bin/json2ddl mysql . > mysql.ddl.sql + echo "DDL generated: $SCRIPT_DIR/models/mysql.ddl.sql" +fi + +# Generate CRUD UI from json definitions +if [ -d "$SCRIPT_DIR/json" ]; then + echo "Generating CRUD UI files..." + cd "$SCRIPT_DIR/json" + for f in *.json; do + echo " Processing $f..." + done + $SAGE_ROOT/py3/bin/xls2ui -m ../models -o ../wwwroot supplychain *.json + echo "CRUD UI files generated." +fi + +# Create symlink in Sage wwwroot +echo "Creating wwwroot symlink..." +rm -f "$SAGE_ROOT/wwwroot/supplychain" +ln -s "$SCRIPT_DIR/wwwroot" "$SAGE_ROOT/wwwroot/supplychain" + +echo "Build complete." diff --git a/init/data.json b/init/data.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/init/data.json @@ -0,0 +1 @@ +{} diff --git a/json/distribution_agreement_items_list.json b/json/distribution_agreement_items_list.json new file mode 100644 index 0000000..8a995f6 --- /dev/null +++ b/json/distribution_agreement_items_list.json @@ -0,0 +1,27 @@ +{ + "tblname": "distribution_agreement_items", + "alias": "distribution_agreement_items_list", + "title": "分销协议产品折扣", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_at" + ] + }, + "editexclouded": [ + "id", + "resellerid", + "agreement_id", + "created_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/distribution_agreement_items_create.dspy')}}", + "update_data_url": "{{entire_url('../api/distribution_agreement_items_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/distribution_agreement_items_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/json/distribution_agreements_list.json b/json/distribution_agreements_list.json new file mode 100644 index 0000000..fdc0e7d --- /dev/null +++ b/json/distribution_agreements_list.json @@ -0,0 +1,57 @@ +{ + "tblname": "distribution_agreements", + "alias": "distribution_agreements_list", + "title": "分销协议管理", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_by", + "created_at", + "updated_at" + ], + "alters": { + "status": { + "uitype": "code", + "data": [ + { + "value": "1", + "text": "有效" + }, + { + "value": "0", + "text": "无效" + }, + { + "value": "2", + "text": "已过期" + } + ] + } + } + }, + "editexclouded": [ + "id", + "resellerid", + "created_by", + "created_at", + "updated_at" + ], + "subtables": [ + { + "field": "id", + "title": "产品折扣", + "url": "{{entire_url('../distribution_agreement_items_list')}}", + "subtable": "distribution_agreement_items" + } + ], + "editable": { + "new_data_url": "{{entire_url('../api/distribution_agreements_create.dspy')}}", + "update_data_url": "{{entire_url('../api/distribution_agreements_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/distribution_agreements_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/json/sub_distributors_list.json b/json/sub_distributors_list.json new file mode 100644 index 0000000..bac1b75 --- /dev/null +++ b/json/sub_distributors_list.json @@ -0,0 +1,30 @@ +{ + "tblname": "sub_distributors", + "alias": "sub_distributors_list", + "title": "二级分销商管理", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_by", + "created_at", + "updated_at" + ] + }, + "editexclouded": [ + "id", + "resellerid", + "created_by", + "created_at", + "updated_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/sub_distributors_create.dspy')}}", + "update_data_url": "{{entire_url('../api/sub_distributors_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/sub_distributors_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/json/suppliers_list.json b/json/suppliers_list.json new file mode 100644 index 0000000..b115a59 --- /dev/null +++ b/json/suppliers_list.json @@ -0,0 +1,30 @@ +{ + "tblname": "suppliers", + "alias": "suppliers_list", + "title": "供应商管理", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_by", + "created_at", + "updated_at" + ] + }, + "editexclouded": [ + "id", + "resellerid", + "created_by", + "created_at", + "updated_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/suppliers_create.dspy')}}", + "update_data_url": "{{entire_url('../api/suppliers_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/suppliers_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/json/supply_contract_items_list.json b/json/supply_contract_items_list.json new file mode 100644 index 0000000..2189556 --- /dev/null +++ b/json/supply_contract_items_list.json @@ -0,0 +1,27 @@ +{ + "tblname": "supply_contract_items", + "alias": "supply_contract_items_list", + "title": "供销合同产品折扣", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_at" + ] + }, + "editexclouded": [ + "id", + "resellerid", + "contract_id", + "created_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/supply_contract_items_create.dspy')}}", + "update_data_url": "{{entire_url('../api/supply_contract_items_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/supply_contract_items_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/json/supply_contracts_list.json b/json/supply_contracts_list.json new file mode 100644 index 0000000..48c4d7c --- /dev/null +++ b/json/supply_contracts_list.json @@ -0,0 +1,57 @@ +{ + "tblname": "supply_contracts", + "alias": "supply_contracts_list", + "title": "供销合同管理", + "params": { + "sortby": [ + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_by", + "created_at", + "updated_at" + ], + "alters": { + "status": { + "uitype": "code", + "data": [ + { + "value": "1", + "text": "有效" + }, + { + "value": "0", + "text": "无效" + }, + { + "value": "2", + "text": "已过期" + } + ] + } + } + }, + "editexclouded": [ + "id", + "resellerid", + "created_by", + "created_at", + "updated_at" + ], + "subtables": [ + { + "field": "id", + "title": "产品折扣", + "url": "{{entire_url('../supply_contract_items_list')}}", + "subtable": "supply_contract_items" + } + ], + "editable": { + "new_data_url": "{{entire_url('../api/supply_contracts_create.dspy')}}", + "update_data_url": "{{entire_url('../api/supply_contracts_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/supply_contracts_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/json/supplychain_accounting_list.json b/json/supplychain_accounting_list.json new file mode 100644 index 0000000..9842747 --- /dev/null +++ b/json/supplychain_accounting_list.json @@ -0,0 +1,29 @@ +{ + "tblname": "supplychain_accounting", + "alias": "supplychain_accounting_list", + "title": "供销记账", + "params": { + "sortby": [ + "sale_date desc", + "created_at desc" + ], + "logined_userorgid": "resellerid", + "browserfields": { + "exclouded": [ + "created_by", + "created_at" + ] + }, + "editexclouded": [ + "id", + "resellerid", + "created_by", + "created_at" + ], + "editable": { + "new_data_url": "{{entire_url('../api/supplychain_accounting_create.dspy')}}", + "update_data_url": "{{entire_url('../api/supplychain_accounting_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/supplychain_accounting_delete.dspy')}}" + } + } +} \ No newline at end of file diff --git a/models/distribution_agreement_items.json b/models/distribution_agreement_items.json new file mode 100644 index 0000000..5000b0d --- /dev/null +++ b/models/distribution_agreement_items.json @@ -0,0 +1,92 @@ +{ + "summary": [ + { + "name": "distribution_agreement_items", + "title": "分销协议产品折扣明细表", + "primary": ["id"], + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "agreement_id", + "title": "分销协议ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属主分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "prodtypeid", + "title": "产品分类ID", + "type": "str", + "length": 32 + }, + { + "name": "productid", + "title": "产品ID", + "type": "str", + "length": 32 + }, + { + "name": "discount", + "title": "分销折扣", + "type": "double", + "length": 5, + "dec": 4, + "nullable": "no", + "default": "1.0000" + }, + { + "name": "settlement_price", + "title": "结算单价", + "type": "double", + "length": 15, + "dec": 4 + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_dai_agreement", + "idxtype": "index", + "idxfields": ["agreement_id"] + }, + { + "name": "idx_dai_product", + "idxtype": "index", + "idxfields": ["agreement_id", "prodtypeid", "productid"] + } + ], + "codes": [ + { + "field": "agreement_id", + "table": "distribution_agreements", + "valuefield": "id", + "textfield": "agreement_name" + } + ] +} diff --git a/models/distribution_agreements.json b/models/distribution_agreements.json new file mode 100644 index 0000000..3d8bc5b --- /dev/null +++ b/models/distribution_agreements.json @@ -0,0 +1,132 @@ +{ + "summary": [ + { + "name": "distribution_agreements", + "title": "分销协议表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属主分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "sub_distributor_id", + "title": "二级分销商ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "agreement_code", + "title": "协议编号", + "type": "str", + "length": 64, + "nullable": "no" + }, + { + "name": "agreement_name", + "title": "协议名称", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "sign_date", + "title": "签署日期", + "type": "date" + }, + { + "name": "start_date", + "title": "生效日期", + "type": "date", + "nullable": "no" + }, + { + "name": "end_date", + "title": "到期日期", + "type": "date" + }, + { + "name": "status", + "title": "状态", + "type": "char", + "length": 1, + "nullable": "no", + "default": "1" + }, + { + "name": "default_discount", + "title": "默认分销折扣", + "type": "double", + "length": 5, + "dec": 4, + "default": "1.0000" + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "datetime" + } + ], + "indexes": [ + { + "name": "idx_da_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_da_subdist", + "idxtype": "index", + "idxfields": ["sub_distributor_id"] + }, + { + "name": "idx_da_code", + "idxtype": "unique", + "idxfields": ["resellerid", "agreement_code"] + } + ], + "codes": [ + { + "field": "sub_distributor_id", + "table": "sub_distributors", + "valuefield": "id", + "textfield": "sub_dist_name" + }, + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/models/sub_distributors.json b/models/sub_distributors.json new file mode 100644 index 0000000..91a061a --- /dev/null +++ b/models/sub_distributors.json @@ -0,0 +1,143 @@ +{ + "summary": [ + { + "name": "sub_distributors", + "title": "二级分销商表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属主分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "sub_dist_code", + "title": "二级分销商编号", + "type": "str", + "length": 64, + "nullable": "no" + }, + { + "name": "sub_dist_name", + "title": "二级分销商名称", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "contact_person", + "title": "联系人", + "type": "str", + "length": 100 + }, + { + "name": "contact_phone", + "title": "联系电话", + "type": "str", + "length": 50 + }, + { + "name": "contact_email", + "title": "联系邮箱", + "type": "str", + "length": 255 + }, + { + "name": "address", + "title": "地址", + "type": "str", + "length": 500 + }, + { + "name": "tax_number", + "title": "税号", + "type": "str", + "length": 64 + }, + { + "name": "bank_name", + "title": "开户银行", + "type": "str", + "length": 255 + }, + { + "name": "bank_account", + "title": "银行账号", + "type": "str", + "length": 64 + }, + { + "name": "managed_by", + "title": "负责销售ID", + "type": "str", + "length": 32 + }, + { + "name": "status", + "title": "状态", + "type": "char", + "length": 1, + "nullable": "no", + "default": "1" + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "datetime" + } + ], + "indexes": [ + { + "name": "idx_sd_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_sd_code", + "idxtype": "unique", + "idxfields": ["resellerid", "sub_dist_code"] + }, + { + "name": "idx_sd_manager", + "idxtype": "index", + "idxfields": ["managed_by"] + } + ], + "codes": [ + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/models/suppliers.json b/models/suppliers.json new file mode 100644 index 0000000..22fd11a --- /dev/null +++ b/models/suppliers.json @@ -0,0 +1,132 @@ +{ + "summary": [ + { + "name": "suppliers", + "title": "供应商表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "supplier_code", + "title": "供应商编号", + "type": "str", + "length": 64, + "nullable": "no" + }, + { + "name": "supplier_name", + "title": "供应商名称", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "contact_person", + "title": "联系人", + "type": "str", + "length": 100 + }, + { + "name": "contact_phone", + "title": "联系电话", + "type": "str", + "length": 50 + }, + { + "name": "contact_email", + "title": "联系邮箱", + "type": "str", + "length": 255 + }, + { + "name": "address", + "title": "地址", + "type": "str", + "length": 500 + }, + { + "name": "tax_number", + "title": "税号", + "type": "str", + "length": 64 + }, + { + "name": "bank_name", + "title": "开户银行", + "type": "str", + "length": 255 + }, + { + "name": "bank_account", + "title": "银行账号", + "type": "str", + "length": 64 + }, + { + "name": "status", + "title": "状态", + "type": "char", + "length": 1, + "nullable": "no", + "default": "1" + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "datetime" + } + ], + "indexes": [ + { + "name": "idx_suppliers_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_suppliers_code", + "idxtype": "unique", + "idxfields": ["resellerid", "supplier_code"] + } + ], + "codes": [ + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/models/supply_contract_items.json b/models/supply_contract_items.json new file mode 100644 index 0000000..4432865 --- /dev/null +++ b/models/supply_contract_items.json @@ -0,0 +1,92 @@ +{ + "summary": [ + { + "name": "supply_contract_items", + "title": "供销合同产品折扣明细表", + "primary": ["id"], + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "contract_id", + "title": "供销合同ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "prodtypeid", + "title": "产品分类ID", + "type": "str", + "length": 32 + }, + { + "name": "productid", + "title": "产品ID", + "type": "str", + "length": 32 + }, + { + "name": "discount", + "title": "进货折扣", + "type": "double", + "length": 5, + "dec": 4, + "nullable": "no", + "default": "1.0000" + }, + { + "name": "settlement_price", + "title": "结算单价", + "type": "double", + "length": 15, + "dec": 4 + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_sci_contract", + "idxtype": "index", + "idxfields": ["contract_id"] + }, + { + "name": "idx_sci_product", + "idxtype": "index", + "idxfields": ["contract_id", "prodtypeid", "productid"] + } + ], + "codes": [ + { + "field": "contract_id", + "table": "supply_contracts", + "valuefield": "id", + "textfield": "contract_name" + } + ] +} diff --git a/models/supply_contracts.json b/models/supply_contracts.json new file mode 100644 index 0000000..97b1343 --- /dev/null +++ b/models/supply_contracts.json @@ -0,0 +1,132 @@ +{ + "summary": [ + { + "name": "supply_contracts", + "title": "供销合同表", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "supplier_id", + "title": "供应商ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "contract_code", + "title": "合同编号", + "type": "str", + "length": 64, + "nullable": "no" + }, + { + "name": "contract_name", + "title": "合同名称", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "sign_date", + "title": "签署日期", + "type": "date" + }, + { + "name": "start_date", + "title": "生效日期", + "type": "date", + "nullable": "no" + }, + { + "name": "end_date", + "title": "到期日期", + "type": "date" + }, + { + "name": "status", + "title": "状态", + "type": "char", + "length": 1, + "nullable": "no", + "default": "1" + }, + { + "name": "default_discount", + "title": "默认折扣", + "type": "double", + "length": 5, + "dec": 4, + "default": "1.0000" + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "datetime" + } + ], + "indexes": [ + { + "name": "idx_sc_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_sc_supplier", + "idxtype": "index", + "idxfields": ["supplier_id"] + }, + { + "name": "idx_sc_code", + "idxtype": "unique", + "idxfields": ["resellerid", "contract_code"] + } + ], + "codes": [ + { + "field": "supplier_id", + "table": "suppliers", + "valuefield": "id", + "textfield": "supplier_name" + }, + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/models/supplychain_accounting.json b/models/supplychain_accounting.json new file mode 100644 index 0000000..a52eeb3 --- /dev/null +++ b/models/supplychain_accounting.json @@ -0,0 +1,233 @@ +{ + "summary": [ + { + "name": "supplychain_accounting", + "title": "供销记账表", + "primary": ["id"], + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "主键ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "所属主分销商机构ID", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "supply_contract_id", + "title": "供销合同ID", + "type": "str", + "length": 32 + }, + { + "name": "supply_contract_item_id", + "title": "供销合同产品明细ID", + "type": "str", + "length": 32 + }, + { + "name": "distribution_agreement_id", + "title": "分销协议ID", + "type": "str", + "length": 32 + }, + { + "name": "distribution_agreement_item_id", + "title": "分销协议产品明细ID", + "type": "str", + "length": 32 + }, + { + "name": "sub_distributor_id", + "title": "二级分销商ID", + "type": "str", + "length": 32 + }, + { + "name": "supplier_id", + "title": "供应商ID", + "type": "str", + "length": 32 + }, + { + "name": "prodtypeid", + "title": "产品分类ID", + "type": "str", + "length": 32 + }, + { + "name": "productid", + "title": "产品ID", + "type": "str", + "length": 32 + }, + { + "name": "quantity", + "title": "数量", + "type": "double", + "length": 15, + "dec": 4, + "nullable": "no", + "default": "0" + }, + { + "name": "unit_price", + "title": "销售单价", + "type": "double", + "length": 15, + "dec": 4, + "nullable": "no", + "default": "0" + }, + { + "name": "supply_discount", + "title": "进货折扣", + "type": "double", + "length": 5, + "dec": 4 + }, + { + "name": "supply_amount", + "title": "进货金额(应付供应商)", + "type": "double", + "length": 15, + "dec": 2, + "nullable": "no", + "default": "0" + }, + { + "name": "dist_discount", + "title": "分销折扣", + "type": "double", + "length": 5, + "dec": 4 + }, + { + "name": "dist_amount", + "title": "分销金额(二级分销商应付)", + "type": "double", + "length": 15, + "dec": 2, + "nullable": "no", + "default": "0" + }, + { + "name": "profit_amount", + "title": "利润金额", + "type": "double", + "length": 15, + "dec": 2, + "nullable": "no", + "default": "0" + }, + { + "name": "sale_date", + "title": "销售日期", + "type": "date", + "nullable": "no" + }, + { + "name": "source_type", + "title": "来源类型", + "type": "char", + "length": 1, + "default": "1" + }, + { + "name": "source_id", + "title": "来源记录ID", + "type": "str", + "length": 32 + }, + { + "name": "remark", + "title": "备注", + "type": "text" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "datetime", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_sa_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_sa_sale_date", + "idxtype": "index", + "idxfields": ["sale_date"] + }, + { + "name": "idx_sa_product", + "idxtype": "index", + "idxfields": ["productid"] + }, + { + "name": "idx_sa_subdist", + "idxtype": "index", + "idxfields": ["sub_distributor_id"] + }, + { + "name": "idx_sa_supplier", + "idxtype": "index", + "idxfields": ["supplier_id"] + }, + { + "name": "idx_sa_source", + "idxtype": "index", + "idxfields": ["source_type", "source_id"] + } + ], + "codes": [ + { + "field": "supply_contract_id", + "table": "supply_contracts", + "valuefield": "id", + "textfield": "contract_name" + }, + { + "field": "distribution_agreement_id", + "table": "distribution_agreements", + "valuefield": "id", + "textfield": "agreement_name" + }, + { + "field": "sub_distributor_id", + "table": "sub_distributors", + "valuefield": "id", + "textfield": "sub_dist_name" + }, + { + "field": "supplier_id", + "table": "suppliers", + "valuefield": "id", + "textfield": "supplier_name" + }, + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3c7097e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "supplychain" +version = "1.0.0" +description = "供应商和分销商管理模块 — 供销合同、分销协议、产品折扣及供销记账" +requires-python = ">=3.8" +dependencies = [ + "sqlor", + "bricks_for_python", +] + +[tool.setuptools.packages.find] +where = ["."] +include = ["supplychain*"] diff --git a/skill/SKILL.md b/skill/SKILL.md new file mode 100644 index 0000000..3164e6c --- /dev/null +++ b/skill/SKILL.md @@ -0,0 +1,60 @@ +--- +name: supplychain-module +version: 1.0.0 +description: 供应商和分销商管理模块 — 供销合同、分销协议、产品折扣及供销记账 +trigger_conditions: + - 需要管理供应商信息 + - 需要创建供销合同或分销协议 + - 需要为产品设置进货或分销折扣 + - 需要计算产品销售时的供销记账金额 +--- + +# supplychain 模块技能文档 + +## 模块概述 + +supplychain 模块为分销商提供供应链管理功能,包括供应商管理、供销合同管理、二级分销商管理、分销协议管理,以及产品销售时的供销记账金额计算。 + +## 数据表结构 + +### 实体表 +- **suppliers** — 供应商(基本信息、联系方式、财务信息) +- **supply_contracts** — 供销合同(运营创建,关联供应商,设置有效期) +- **sub_distributors** — 二级分销商(销售创建,基本信息) +- **distribution_agreements** — 分销协议(销售创建,关联二级分销商) + +### 关系表 +- **supply_contract_items** — 供销合同产品折扣明细(合同下的产品级折扣) +- **distribution_agreement_items** — 分销协议产品折扣明细(协议下的产品级折扣) +- **supplychain_accounting** — 供销记账记录(销售时自动计算) + +## 折扣查找优先级 + +无论是进货折扣还是分销折扣,查找优先级均为: +1. 精确匹配 productid +2. 匹配 prodtypeid(产品分类级折扣) +3. 使用合同/协议的 default_discount + +## API 调用 + +### 记账计算 + +``` +POST /supplychain/api/calculate_accounting.dspy +Body: {productid, prodtypeid?, quantity, unit_price, sub_distributor_id?, sale_date?, ...} +``` + +### Python 函数调用 + +```python +# 在 .dspy 文件中 +record = await calculate_sale_accounting(sor, resellerid, productid, quantity, unit_price, + sub_distributor_id, prodtypeid, sale_date, ...) +``` + +## 注意事项 + +- productid 和 prodtypeid 引用 product 模块(即将开发) +- 所有数据通过 resellerid 隔离(机构级多租户) +- 合同/协议编号自动生成 +- 折扣值范围 0-1(1.0 表示无折扣) diff --git a/supplychain/__init__.py b/supplychain/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/supplychain/init.py b/supplychain/init.py new file mode 100644 index 0000000..7c83edc --- /dev/null +++ b/supplychain/init.py @@ -0,0 +1,253 @@ +from ahserver.serverenv import ServerEnv +from appPublic.jsonConfig import getConfig +from appPublic.dictObject import DictObject +from appPublic.uniqueID import getID +from sqlor.dbpools import DBPools +from datetime import datetime +import json + +MODULE_NAME = "supplychain" +MODULE_VERSION = "1.0.0" + + +def get_module_dbname(): + """Get the database name for the supplychain module.""" + env = ServerEnv() + return env.get_module_dbname('supplychain') + + +def get_db_context(): + """Get a database context manager for the supplychain module.""" + config = getConfig('.') + DBPools(config.databases) + dbname = get_module_dbname() + return db.sqlorContext(dbname) + + +async def get_active_supply_discount(sor, resellerid, productid, prodtypeid=None, sale_date=None): + """ + Get active supply contract discount for a product. + Priority: exact product > product type > contract default. + + Returns: dict with contract_id, discount, settlement_price, supplier_id + """ + if sale_date is None: + sale_date = datetime.now().strftime("%Y-%m-%d") + + # Try exact product match + sql = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, sc.supplier_id + FROM supply_contract_items sci + JOIN supply_contracts sc ON sci.contract_id = sc.id + WHERE sci.resellerid = ${resellerid}$ + AND sc.status = '1' + AND sc.start_date <= ${sale_date}$ + AND (sc.end_date IS NULL OR sc.end_date >= ${sale_date}$) + AND sci.productid = ${productid}$ + ORDER BY sc.start_date DESC LIMIT 1""" + recs = await sor.sqlExe(sql, {"resellerid": resellerid, "sale_date": sale_date, "productid": productid}) + + if not recs and prodtypeid: + sql = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, sc.supplier_id + FROM supply_contract_items sci + JOIN supply_contracts sc ON sci.contract_id = sc.id + WHERE sci.resellerid = ${resellerid}$ + AND sc.status = '1' + AND sc.start_date <= ${sale_date}$ + AND (sc.end_date IS NULL OR sc.end_date >= ${sale_date}$) + AND sci.prodtypeid = ${prodtypeid}$ + ORDER BY sc.start_date DESC LIMIT 1""" + recs = await sor.sqlExe(sql, {"resellerid": resellerid, "sale_date": sale_date, "prodtypeid": prodtypeid}) + + if recs: + return { + "contract_item_id": recs[0].id, + "contract_id": recs[0].contract_id, + "discount": float(recs[0].discount) if recs[0].discount else 1.0, + "settlement_price": float(recs[0].settlement_price) if recs[0].settlement_price else None, + "supplier_id": recs[0].supplier_id, + } + + # Fallback to contract default + sql = """SELECT id, supplier_id, default_discount FROM supply_contracts + WHERE resellerid = ${resellerid}$ + AND status = '1' + AND start_date <= ${sale_date}$ + AND (end_date IS NULL OR end_date >= ${sale_date}$) + ORDER BY start_date DESC LIMIT 1""" + recs = await sor.sqlExe(sql, {"resellerid": resellerid, "sale_date": sale_date}) + + if recs: + return { + "contract_item_id": None, + "contract_id": recs[0].id, + "discount": float(recs[0].default_discount) if recs[0].default_discount else 1.0, + "settlement_price": None, + "supplier_id": recs[0].supplier_id, + } + + return None + + +async def get_active_dist_discount(sor, resellerid, sub_distributor_id, productid, prodtypeid=None, sale_date=None): + """ + Get active distribution agreement discount for a product. + Priority: exact product > product type > agreement default. + + Returns: dict with agreement_id, discount, settlement_price + """ + if sale_date is None: + sale_date = datetime.now().strftime("%Y-%m-%d") + + # Try exact product match + sql = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price + FROM distribution_agreement_items dai + JOIN distribution_agreements da ON dai.agreement_id = da.id + WHERE dai.resellerid = ${resellerid}$ + AND da.sub_distributor_id = ${sub_distributor_id}$ + AND da.status = '1' + AND da.start_date <= ${sale_date}$ + AND (da.end_date IS NULL OR da.end_date >= ${sale_date}$) + AND dai.productid = ${productid}$ + ORDER BY da.start_date DESC LIMIT 1""" + ns = {"resellerid": resellerid, "sub_distributor_id": sub_distributor_id, + "sale_date": sale_date, "productid": productid} + recs = await sor.sqlExe(sql, ns) + + if not recs and prodtypeid: + sql = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price + FROM distribution_agreement_items dai + JOIN distribution_agreements da ON dai.agreement_id = da.id + WHERE dai.resellerid = ${resellerid}$ + AND da.sub_distributor_id = ${sub_distributor_id}$ + AND da.status = '1' + AND da.start_date <= ${sale_date}$ + AND (da.end_date IS NULL OR da.end_date >= ${sale_date}$) + AND dai.prodtypeid = ${prodtypeid}$ + ORDER BY da.start_date DESC LIMIT 1""" + ns["productid"] = None + recs = await sor.sqlExe(sql, ns) + + if recs: + return { + "agreement_item_id": recs[0].id, + "agreement_id": recs[0].agreement_id, + "discount": float(recs[0].discount) if recs[0].discount else 1.0, + "settlement_price": float(recs[0].settlement_price) if recs[0].settlement_price else None, + } + + # Fallback to agreement default + sql = """SELECT id, default_discount FROM distribution_agreements + WHERE resellerid = ${resellerid}$ + AND sub_distributor_id = ${sub_distributor_id}$ + AND status = '1' + AND start_date <= ${sale_date}$ + AND (end_date IS NULL OR end_date >= ${sale_date}$) + ORDER BY start_date DESC LIMIT 1""" + recs = await sor.sqlExe(sql, {"resellerid": resellerid, + "sub_distributor_id": sub_distributor_id, + "sale_date": sale_date}) + + if recs: + return { + "agreement_item_id": None, + "agreement_id": recs[0].id, + "discount": float(recs[0].default_discount) if recs[0].default_discount else 1.0, + "settlement_price": None, + } + + return None + + +async def calculate_sale_accounting(sor, resellerid, productid, quantity, unit_price, + sub_distributor_id=None, prodtypeid=None, + sale_date=None, source_type="2", source_id=None, + created_by=None, remark=""): + """ + Calculate and record accounting for a product sale. + + Returns: the created accounting record as dict + """ + if sale_date is None: + sale_date = datetime.now().strftime("%Y-%m-%d") + + total_amount = quantity * unit_price + + # Get supply discount + supply_info = await get_active_supply_discount(sor, resellerid, productid, prodtypeid, sale_date) + + supply_contract_id = None + supply_contract_item_id = None + supplier_id = None + supply_discount = 1.0 + supply_amount = total_amount + + if supply_info: + supply_contract_id = supply_info["contract_id"] + supply_contract_item_id = supply_info["contract_item_id"] + supplier_id = supply_info["supplier_id"] + supply_discount = supply_info["discount"] + if supply_info["settlement_price"]: + supply_amount = supply_info["settlement_price"] * quantity + else: + supply_amount = total_amount * supply_discount + + # Get distribution discount + distribution_agreement_id = None + distribution_agreement_item_id = None + dist_discount = 1.0 + dist_amount = total_amount + + if sub_distributor_id: + dist_info = await get_active_dist_discount(sor, resellerid, sub_distributor_id, + productid, prodtypeid, sale_date) + if dist_info: + distribution_agreement_id = dist_info["agreement_id"] + distribution_agreement_item_id = dist_info["agreement_item_id"] + dist_discount = dist_info["discount"] + if dist_info["settlement_price"]: + dist_amount = dist_info["settlement_price"] * quantity + else: + dist_amount = total_amount * dist_discount + + profit_amount = dist_amount - supply_amount + + # Create accounting record + accounting_id = getID() + record = { + "id": accounting_id, + "resellerid": resellerid, + "supply_contract_id": supply_contract_id, + "supply_contract_item_id": supply_contract_item_id, + "distribution_agreement_id": distribution_agreement_id, + "distribution_agreement_item_id": distribution_agreement_item_id, + "sub_distributor_id": sub_distributor_id, + "supplier_id": supplier_id, + "prodtypeid": prodtypeid, + "productid": productid, + "quantity": quantity, + "unit_price": unit_price, + "supply_discount": supply_discount, + "supply_amount": supply_amount, + "dist_discount": dist_discount, + "dist_amount": dist_amount, + "profit_amount": profit_amount, + "sale_date": sale_date, + "source_type": source_type, + "source_id": source_id, + "remark": remark, + "created_by": created_by, + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + await sor.C("supplychain_accounting", record) + return record + + +def load_supplychain(): + """Register all functions with ServerEnv.""" + env = ServerEnv() + 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/accounting.ui b/wwwroot/accounting.ui new file mode 100644 index 0000000..9393bcb --- /dev/null +++ b/wwwroot/accounting.ui @@ -0,0 +1,25 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "供销记账", + "fontSize": "20px", + "fontWeight": "bold", + "marginBottom": "16px" + } + }, + { + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('supplychain_accounting_list/index.ui')}}" + } + } + ] +} \ No newline at end of file diff --git a/wwwroot/api/calculate_accounting.dspy b/wwwroot/api/calculate_accounting.dspy new file mode 100644 index 0000000..133dc40 --- /dev/null +++ b/wwwroot/api/calculate_accounting.dspy @@ -0,0 +1,222 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """ + 产品销售时调用此API计算供销记账金额。 + + 输入参数: + productid: 产品ID + prodtypeid: 产品分类ID (可选) + quantity: 销售数量 + unit_price: 销售单价 + sub_distributor_id: 二级分销商ID (可选, 如果是直接销售则为空) + sale_date: 销售日期 (可选, 默认今天) + source_type: 来源类型 (1=手动, 2=API调用) + source_id: 来源记录ID (可选) + + 计算逻辑: + 1. 查找有效的供销合同及对应产品折扣 + 2. 查找有效的分销协议及对应产品折扣 (如果有二级分销商) + 3. 计算: 进货金额 = 单价 * 数量 * 进货折扣 + 4. 计算: 分销金额 = 单价 * 数量 * 分销折扣 + 5. 计算: 利润金额 = 分销金额 - 进货金额 + 6. 创建记账记录 + + 返回: 记账记录数据 + """ + user_id = await get_user() + user_orgid = await get_userorgid() + dbname = get_module_dbname('supplychain') + + # Parse input + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + productid = data.get("productid") + prodtypeid = data.get("prodtypeid") + quantity = float(data.get("quantity", 0)) + unit_price = float(data.get("unit_price", 0)) + sub_distributor_id = data.get("sub_distributor_id") + sale_date = data.get("sale_date", datetime.now().strftime("%Y-%m-%d")) + source_type = data.get("source_type", "2") + source_id = data.get("source_id") + remark = data.get("remark", "") + + if not productid or quantity <= 0 or unit_price <= 0: + return json.dumps({"status": "error", "message": "缺少必要参数: productid, quantity, unit_price"}) + + config = getConfig(".") + DBPools(config.databases) + total_amount = quantity * unit_price + + async with db.sqlorContext(dbname) as sor: + # Step 1: Find active supply contract with product discount + # Priority: exact product > product type > default contract discount + supply_contract_id = None + supply_contract_item_id = None + supplier_id = None + supply_discount = 1.0 + supply_amount = total_amount + + # Find supply contract items matching this product + if prodtypeid: + sql_sci = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, sc.supplier_id + FROM supply_contract_items sci + JOIN supply_contracts sc ON sci.contract_id = sc.id + WHERE sci.resellerid = ${resellerid}$ + AND sc.status = '1' + AND sc.start_date <= ${sale_date}$ + AND (sc.end_date IS NULL OR sc.end_date >= ${sale_date}$) + AND sci.productid = ${productid}$ + ORDER BY sci.created_at DESC LIMIT 1""" + ns_sci = {"resellerid": user_orgid, "sale_date": sale_date, "productid": productid} + sci_recs = await sor.sqlExe(sql_sci, ns_sci) + + if not prodtypeid or not sci_recs: + sql_sci = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, sc.supplier_id + FROM supply_contract_items sci + JOIN supply_contracts sc ON sci.contract_id = sc.id + WHERE sci.resellerid = ${resellerid}$ + AND sc.status = '1' + AND sc.start_date <= ${sale_date}$ + AND (sc.end_date IS NULL OR sc.end_date >= ${sale_date}$) + AND sci.prodtypeid = ${prodtypeid}$ + ORDER BY sci.created_at DESC LIMIT 1""" + ns_sci = {"resellerid": user_orgid, "sale_date": sale_date, "prodtypeid": prodtypeid} + sci_recs = await sor.sqlExe(sql_sci, ns_sci) + + if sci_recs: + supply_contract_item_id = sci_recs[0].id + supply_contract_id = sci_recs[0].contract_id + supplier_id = sci_recs[0].supplier_id + supply_discount = float(sci_recs[0].discount) if sci_recs[0].discount else 1.0 + if sci_recs[0].settlement_price: + supply_amount = float(sci_recs[0].settlement_price) * quantity + else: + supply_amount = total_amount * supply_discount + else: + # Fallback: find any active supply contract with default discount + sql_sc = """SELECT id, supplier_id, default_discount FROM supply_contracts + WHERE resellerid = ${resellerid}$ + AND status = '1' + AND start_date <= ${sale_date}$ + AND (end_date IS NULL OR end_date >= ${sale_date}$) + ORDER BY created_at DESC LIMIT 1""" + sc_recs = await sor.sqlExe(sql_sc, {"resellerid": user_orgid, "sale_date": sale_date}) + if sc_recs: + supply_contract_id = sc_recs[0].id + supplier_id = sc_recs[0].supplier_id + supply_discount = float(sc_recs[0].default_discount) if sc_recs[0].default_discount else 1.0 + supply_amount = total_amount * supply_discount + + # Step 2: Find active distribution agreement with product discount (if sub_distributor) + distribution_agreement_id = None + distribution_agreement_item_id = None + dist_discount = 1.0 + dist_amount = total_amount + + if sub_distributor_id: + # Find distribution agreement items matching this product + sql_dai = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price + FROM distribution_agreement_items dai + JOIN distribution_agreements da ON dai.agreement_id = da.id + WHERE dai.resellerid = ${resellerid}$ + AND da.sub_distributor_id = ${sub_distributor_id}$ + AND da.status = '1' + AND da.start_date <= ${sale_date}$ + AND (da.end_date IS NULL OR da.end_date >= ${sale_date}$) + AND dai.productid = ${productid}$ + ORDER BY dai.created_at DESC LIMIT 1""" + ns_dai = {"resellerid": user_orgid, "sub_distributor_id": sub_distributor_id, + "sale_date": sale_date, "productid": productid} + dai_recs = await sor.sqlExe(sql_dai, ns_dai) + + if not dai_recs and prodtypeid: + sql_dai = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price + FROM distribution_agreement_items dai + JOIN distribution_agreements da ON dai.agreement_id = da.id + WHERE dai.resellerid = ${resellerid}$ + AND da.sub_distributor_id = ${sub_distributor_id}$ + AND da.status = '1' + AND da.start_date <= ${sale_date}$ + AND (da.end_date IS NULL OR da.end_date >= ${sale_date}$) + AND dai.prodtypeid = ${prodtypeid}$ + ORDER BY dai.created_at DESC LIMIT 1""" + ns_dai["productid"] = None + dai_recs = await sor.sqlExe(sql_dai, ns_dai) + + if dai_recs: + distribution_agreement_item_id = dai_recs[0].id + distribution_agreement_id = dai_recs[0].agreement_id + dist_discount = float(dai_recs[0].discount) if dai_recs[0].discount else 1.0 + if dai_recs[0].settlement_price: + dist_amount = float(dai_recs[0].settlement_price) * quantity + else: + dist_amount = total_amount * dist_discount + else: + # Fallback: find active distribution agreement with default discount + sql_da = """SELECT id, default_discount FROM distribution_agreements + WHERE resellerid = ${resellerid}$ + AND sub_distributor_id = ${sub_distributor_id}$ + AND status = '1' + AND start_date <= ${sale_date}$ + AND (end_date IS NULL OR end_date >= ${sale_date}$) + ORDER BY created_at DESC LIMIT 1""" + da_recs = await sor.sqlExe(sql_da, {"resellerid": user_orgid, + "sub_distributor_id": sub_distributor_id, + "sale_date": sale_date}) + if da_recs: + distribution_agreement_id = da_recs[0].id + dist_discount = float(da_recs[0].default_discount) if da_recs[0].default_discount else 1.0 + dist_amount = total_amount * dist_discount + + # Step 3: Calculate profit + profit_amount = dist_amount - supply_amount + + # Step 4: Create accounting record + accounting_id = getID() + record = { + "id": accounting_id, + "resellerid": user_orgid, + "supply_contract_id": supply_contract_id, + "supply_contract_item_id": supply_contract_item_id, + "distribution_agreement_id": distribution_agreement_id, + "distribution_agreement_item_id": distribution_agreement_item_id, + "sub_distributor_id": sub_distributor_id, + "supplier_id": supplier_id, + "prodtypeid": prodtypeid, + "productid": productid, + "quantity": quantity, + "unit_price": unit_price, + "supply_discount": supply_discount, + "supply_amount": supply_amount, + "dist_discount": dist_discount, + "dist_amount": dist_amount, + "profit_amount": profit_amount, + "sale_date": sale_date, + "source_type": source_type, + "source_id": source_id, + "remark": remark, + "created_by": user_id, + "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + + await sor.C("supplychain_accounting", record) + + result = { + "status": "ok", + "data": record, + "summary": { + "total_amount": total_amount, + "supply_amount": supply_amount, + "dist_amount": dist_amount, + "profit_amount": profit_amount, + "supply_discount": supply_discount, + "dist_discount": dist_discount + } + } + + return json.dumps(result) diff --git a/wwwroot/api/distribution_agreement_items_create.dspy b/wwwroot/api/distribution_agreement_items_create.dspy new file mode 100644 index 0000000..784b7fd --- /dev/null +++ b/wwwroot/api/distribution_agreement_items_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new distribution_agreement_items record.""" + 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: + await sor.C("distribution_agreement_items", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/distribution_agreement_items_delete.dspy b/wwwroot/api/distribution_agreement_items_delete.dspy new file mode 100644 index 0000000..442e0b4 --- /dev/null +++ b/wwwroot/api/distribution_agreement_items_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a distribution_agreement_items record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("distribution_agreement_items", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/distribution_agreement_items_update.dspy b/wwwroot/api/distribution_agreement_items_update.dspy new file mode 100644 index 0000000..8bd204e --- /dev/null +++ b/wwwroot/api/distribution_agreement_items_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a distribution_agreement_items record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("distribution_agreement_items", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/distribution_agreements_create.dspy b/wwwroot/api/distribution_agreements_create.dspy new file mode 100644 index 0000000..b841b1a --- /dev/null +++ b/wwwroot/api/distribution_agreements_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new distribution_agreements record.""" + 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]}" + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.C("distribution_agreements", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/distribution_agreements_delete.dspy b/wwwroot/api/distribution_agreements_delete.dspy new file mode 100644 index 0000000..b6d8a32 --- /dev/null +++ b/wwwroot/api/distribution_agreements_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a distribution_agreements record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("distribution_agreements", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/distribution_agreements_update.dspy b/wwwroot/api/distribution_agreements_update.dspy new file mode 100644 index 0000000..91bff12 --- /dev/null +++ b/wwwroot/api/distribution_agreements_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a distribution_agreements record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("distribution_agreements", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/query_dist_discount.dspy b/wwwroot/api/query_dist_discount.dspy new file mode 100644 index 0000000..53f98b8 --- /dev/null +++ b/wwwroot/api/query_dist_discount.dspy @@ -0,0 +1,74 @@ +import json + +async def main(request, params_kw): + """ + 查询某二级分销商在某产品上的分销协议折扣。 + + 参数: sub_distributor_id, productid, prodtypeid(可选) + + 折扣查找优先级: + 1. 精确匹配 productid + 2. 匹配 prodtypeid + 3. 使用协议默认折扣 + """ + user_orgid = await get_userorgid() + dbname = get_module_dbname('supplychain') + + sub_distributor_id = params_kw.get("sub_distributor_id") + productid = params_kw.get("productid") + prodtypeid = params_kw.get("prodtypeid") + + if not sub_distributor_id or not productid: + return json.dumps({"status": "error", "message": "缺少sub_distributor_id或productid参数"}) + + config = getConfig(".") + DBPools(config.databases) + + async with db.sqlorContext(dbname) as sor: + # Try exact product match + sql = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price, + da.agreement_code, da.agreement_name + FROM distribution_agreement_items dai + JOIN distribution_agreements da ON dai.agreement_id = da.id + WHERE dai.resellerid = ${resellerid}$ + AND da.sub_distributor_id = ${sub_distributor_id}$ + AND da.status = '1' + AND da.start_date <= CURDATE() + AND (da.end_date IS NULL OR da.end_date >= CURDATE()) + AND dai.productid = ${productid}$ + ORDER BY da.start_date DESC""" + recs = await sor.sqlExe(sql, {"resellerid": user_orgid, + "sub_distributor_id": sub_distributor_id, + "productid": productid}) + + if not recs and prodtypeid: + sql = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price, + da.agreement_code, da.agreement_name + FROM distribution_agreement_items dai + JOIN distribution_agreements da ON dai.agreement_id = da.id + WHERE dai.resellerid = ${resellerid}$ + AND da.sub_distributor_id = ${sub_distributor_id}$ + AND da.status = '1' + AND da.start_date <= CURDATE() + AND (da.end_date IS NULL OR da.end_date >= CURDATE()) + AND dai.prodtypeid = ${prodtypeid}$ + ORDER BY da.start_date DESC""" + recs = await sor.sqlExe(sql, {"resellerid": user_orgid, + "sub_distributor_id": sub_distributor_id, + "prodtypeid": prodtypeid}) + + if not recs: + sql = """SELECT id as agreement_id, agreement_code, agreement_name, + default_discount as discount, NULL as settlement_price + FROM distribution_agreements + WHERE resellerid = ${resellerid}$ + AND sub_distributor_id = ${sub_distributor_id}$ + AND status = '1' + AND start_date <= CURDATE() + AND (end_date IS NULL OR end_date >= CURDATE()) + ORDER BY start_date DESC""" + recs = await sor.sqlExe(sql, {"resellerid": user_orgid, + "sub_distributor_id": sub_distributor_id}) + + result = [dict(r) for r in recs] if recs else [] + return json.dumps({"status": "ok", "data": result}) diff --git a/wwwroot/api/query_supply_discount.dspy b/wwwroot/api/query_supply_discount.dspy new file mode 100644 index 0000000..57a1b0a --- /dev/null +++ b/wwwroot/api/query_supply_discount.dspy @@ -0,0 +1,70 @@ +import json + +async def main(request, params_kw): + """ + 查询某产品在有效供销合同下的折扣信息。 + + 参数: productid, prodtypeid(可选) + + 折扣查找优先级: + 1. 精确匹配 productid + 2. 匹配 prodtypeid + 3. 使用合同默认折扣 + """ + user_orgid = await get_userorgid() + dbname = get_module_dbname('supplychain') + + productid = params_kw.get("productid") + prodtypeid = params_kw.get("prodtypeid") + + if not productid: + return json.dumps({"status": "error", "message": "缺少productid参数"}) + + config = getConfig(".") + DBPools(config.databases) + + async with db.sqlorContext(dbname) as sor: + # Try exact product match + sql = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, + sc.contract_code, sc.contract_name, sc.supplier_id, + s.supplier_name + FROM supply_contract_items sci + JOIN supply_contracts sc ON sci.contract_id = sc.id + LEFT JOIN suppliers s ON sc.supplier_id = s.id + WHERE sci.resellerid = ${resellerid}$ + AND sc.status = '1' + AND sc.start_date <= CURDATE() + AND (sc.end_date IS NULL OR sc.end_date >= CURDATE()) + AND sci.productid = ${productid}$ + ORDER BY sc.start_date DESC""" + recs = await sor.sqlExe(sql, {"resellerid": user_orgid, "productid": productid}) + + if not recs and prodtypeid: + sql = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, + sc.contract_code, sc.contract_name, sc.supplier_id, + s.supplier_name + FROM supply_contract_items sci + JOIN supply_contracts sc ON sci.contract_id = sc.id + LEFT JOIN suppliers s ON sc.supplier_id = s.id + WHERE sci.resellerid = ${resellerid}$ + AND sc.status = '1' + AND sc.start_date <= CURDATE() + AND (sc.end_date IS NULL OR sc.end_date >= CURDATE()) + AND sci.prodtypeid = ${prodtypeid}$ + ORDER BY sc.start_date DESC""" + recs = await sor.sqlExe(sql, {"resellerid": user_orgid, "prodtypeid": prodtypeid}) + + if not recs: + # Fallback to contract default + sql = """SELECT id as contract_id, contract_code, contract_name, + supplier_id, default_discount as discount, NULL as settlement_price + FROM supply_contracts + WHERE resellerid = ${resellerid}$ + AND status = '1' + AND start_date <= CURDATE() + AND (end_date IS NULL OR end_date >= CURDATE()) + ORDER BY start_date DESC""" + recs = await sor.sqlExe(sql, {"resellerid": user_orgid}) + + result = [dict(r) for r in recs] if recs else [] + return json.dumps({"status": "ok", "data": result}) diff --git a/wwwroot/api/sub_distributors_create.dspy b/wwwroot/api/sub_distributors_create.dspy new file mode 100644 index 0000000..4065c72 --- /dev/null +++ b/wwwroot/api/sub_distributors_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new sub_distributors record.""" + 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]}" + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.C("sub_distributors", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/sub_distributors_delete.dspy b/wwwroot/api/sub_distributors_delete.dspy new file mode 100644 index 0000000..8596548 --- /dev/null +++ b/wwwroot/api/sub_distributors_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a sub_distributors record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("sub_distributors", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/sub_distributors_update.dspy b/wwwroot/api/sub_distributors_update.dspy new file mode 100644 index 0000000..0ff8d04 --- /dev/null +++ b/wwwroot/api/sub_distributors_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a sub_distributors record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("sub_distributors", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/suppliers_create.dspy b/wwwroot/api/suppliers_create.dspy new file mode 100644 index 0000000..576d634 --- /dev/null +++ b/wwwroot/api/suppliers_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new suppliers record.""" + 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]}" + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.C("suppliers", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/suppliers_delete.dspy b/wwwroot/api/suppliers_delete.dspy new file mode 100644 index 0000000..a53dc92 --- /dev/null +++ b/wwwroot/api/suppliers_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a suppliers record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("suppliers", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/suppliers_update.dspy b/wwwroot/api/suppliers_update.dspy new file mode 100644 index 0000000..0dd227e --- /dev/null +++ b/wwwroot/api/suppliers_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a suppliers record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("suppliers", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/supply_contract_items_create.dspy b/wwwroot/api/supply_contract_items_create.dspy new file mode 100644 index 0000000..22bea28 --- /dev/null +++ b/wwwroot/api/supply_contract_items_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new supply_contract_items record.""" + 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: + await sor.C("supply_contract_items", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/supply_contract_items_delete.dspy b/wwwroot/api/supply_contract_items_delete.dspy new file mode 100644 index 0000000..09ca2ac --- /dev/null +++ b/wwwroot/api/supply_contract_items_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a supply_contract_items record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("supply_contract_items", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/supply_contract_items_update.dspy b/wwwroot/api/supply_contract_items_update.dspy new file mode 100644 index 0000000..1e7812d --- /dev/null +++ b/wwwroot/api/supply_contract_items_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a supply_contract_items record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("supply_contract_items", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/supply_contracts_create.dspy b/wwwroot/api/supply_contracts_create.dspy new file mode 100644 index 0000000..b5d4342 --- /dev/null +++ b/wwwroot/api/supply_contracts_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new supply_contracts record.""" + 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]}" + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.C("supply_contracts", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/supply_contracts_delete.dspy b/wwwroot/api/supply_contracts_delete.dspy new file mode 100644 index 0000000..c6d3de9 --- /dev/null +++ b/wwwroot/api/supply_contracts_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a supply_contracts record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("supply_contracts", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/supply_contracts_update.dspy b/wwwroot/api/supply_contracts_update.dspy new file mode 100644 index 0000000..36a2293 --- /dev/null +++ b/wwwroot/api/supply_contracts_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a supply_contracts record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("supply_contracts", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/supplychain_accounting_create.dspy b/wwwroot/api/supplychain_accounting_create.dspy new file mode 100644 index 0000000..ba301fb --- /dev/null +++ b/wwwroot/api/supplychain_accounting_create.dspy @@ -0,0 +1,34 @@ +import json +from appPublic.uniqueID import getID +from datetime import datetime + +async def main(request, params_kw): + """Create a new supplychain_accounting record.""" + 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: + await sor.C("supplychain_accounting", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/api/supplychain_accounting_delete.dspy b/wwwroot/api/supplychain_accounting_delete.dspy new file mode 100644 index 0000000..c9234f0 --- /dev/null +++ b/wwwroot/api/supplychain_accounting_delete.dspy @@ -0,0 +1,19 @@ +import json + +async def main(request, params_kw): + """Delete a supplychain_accounting record.""" + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.D("supplychain_accounting", {"id": record_id}) + return json.dumps({"status": "ok", "message": "Deleted successfully"}) diff --git a/wwwroot/api/supplychain_accounting_update.dspy b/wwwroot/api/supplychain_accounting_update.dspy new file mode 100644 index 0000000..32e6cd7 --- /dev/null +++ b/wwwroot/api/supplychain_accounting_update.dspy @@ -0,0 +1,27 @@ +import json +from datetime import datetime + +async def main(request, params_kw): + """Update a supplychain_accounting record.""" + user_id = await get_user() + dbname = get_module_dbname('supplychain') + + data = params_kw.get("data", "{}") + if isinstance(data, str): + data = json.loads(data) + + record_id = data.get("id") + if not record_id: + return json.dumps({"status": "error", "message": "Missing record id"}) + + data["updated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Remove fields that should not be updated + for key in ["id", "resellerid", "created_by", "created_at"]: + data.pop(key, None) + + config = getConfig(".") + DBPools(config.databases) + async with db.sqlorContext(dbname) as sor: + await sor.U("supplychain_accounting", data) + return json.dumps({"status": "ok", "data": data}) diff --git a/wwwroot/distribution_agreements.ui b/wwwroot/distribution_agreements.ui new file mode 100644 index 0000000..863a7f1 --- /dev/null +++ b/wwwroot/distribution_agreements.ui @@ -0,0 +1,25 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "分销协议管理", + "fontSize": "20px", + "fontWeight": "bold", + "marginBottom": "16px" + } + }, + { + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('distribution_agreements_list/index.ui')}}" + } + } + ] +} \ No newline at end of file diff --git a/wwwroot/index.ui b/wwwroot/index.ui new file mode 100644 index 0000000..17f11f6 --- /dev/null +++ b/wwwroot/index.ui @@ -0,0 +1,163 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "20px", + "bgcolor": "#F5F5F5" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "供销链管理", + "fontSize": "24px", + "fontWeight": "bold", + "color": "#333333", + "marginBottom": "20px" + } + }, + { + "widgettype": "Text", + "options": { + "text": "供应商、合同、二级分销商、分销协议及供销记账", + "fontSize": "14px", + "color": "#666666", + "marginBottom": "24px" + } + }, + { + "widgettype": "ResponsableBox", + "options": { + "gap": "16px", + "minWidth": "250px" + }, + "subwidgets": [ + { + "widgettype": "VBox", + "options": { + "bgcolor": "#FFFFFF", + "padding": "20px", + "borderRadius": "8px", + "cursor": "pointer", + "boxShadow": "0 2px 4px rgba(0,0,0,0.1)" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.supplychain_content", + "options": {"url": "{{entire_url('suppliers.ui')}}"}, + "mode": "replace" + }], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🏭", "fontSize": "32px", "textAlign": "center"}}, + {"widgettype": "Text", "options": {"text": "供应商管理", "fontSize": "16px", "fontWeight": "bold", "textAlign": "center", "marginTop": "8px"}}, + {"widgettype": "Text", "options": {"text": "添加和管理供应商信息", "fontSize": "12px", "color": "#999999", "textAlign": "center"}} + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#FFFFFF", + "padding": "20px", + "borderRadius": "8px", + "cursor": "pointer", + "boxShadow": "0 2px 4px rgba(0,0,0,0.1)" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.supplychain_content", + "options": {"url": "{{entire_url('supply_contracts.ui')}}"}, + "mode": "replace" + }], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "📋", "fontSize": "32px", "textAlign": "center"}}, + {"widgettype": "Text", "options": {"text": "供销合同", "fontSize": "16px", "fontWeight": "bold", "textAlign": "center", "marginTop": "8px"}}, + {"widgettype": "Text", "options": {"text": "管理与供应商的供销合同及产品折扣", "fontSize": "12px", "color": "#999999", "textAlign": "center"}} + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#FFFFFF", + "padding": "20px", + "borderRadius": "8px", + "cursor": "pointer", + "boxShadow": "0 2px 4px rgba(0,0,0,0.1)" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.supplychain_content", + "options": {"url": "{{entire_url('sub_distributors.ui')}}"}, + "mode": "replace" + }], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "🏪", "fontSize": "32px", "textAlign": "center"}}, + {"widgettype": "Text", "options": {"text": "二级分销商", "fontSize": "16px", "fontWeight": "bold", "textAlign": "center", "marginTop": "8px"}}, + {"widgettype": "Text", "options": {"text": "添加和管理二级分销商", "fontSize": "12px", "color": "#999999", "textAlign": "center"}} + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#FFFFFF", + "padding": "20px", + "borderRadius": "8px", + "cursor": "pointer", + "boxShadow": "0 2px 4px rgba(0,0,0,0.1)" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.supplychain_content", + "options": {"url": "{{entire_url('distribution_agreements.ui')}}"}, + "mode": "replace" + }], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "📝", "fontSize": "32px", "textAlign": "center"}}, + {"widgettype": "Text", "options": {"text": "分销协议", "fontSize": "16px", "fontWeight": "bold", "textAlign": "center", "marginTop": "8px"}}, + {"widgettype": "Text", "options": {"text": "管理与二级分销商的分销协议及产品折扣", "fontSize": "12px", "color": "#999999", "textAlign": "center"}} + ] + }, + { + "widgettype": "VBox", + "options": { + "bgcolor": "#FFFFFF", + "padding": "20px", + "borderRadius": "8px", + "cursor": "pointer", + "boxShadow": "0 2px 4px rgba(0,0,0,0.1)" + }, + "binds": [{ + "wid": "self", + "event": "click", + "actiontype": "urlwidget", + "target": "app.supplychain_content", + "options": {"url": "{{entire_url('accounting.ui')}}"}, + "mode": "replace" + }], + "subwidgets": [ + {"widgettype": "Text", "options": {"text": "💰", "fontSize": "32px", "textAlign": "center"}}, + {"widgettype": "Text", "options": {"text": "供销记账", "fontSize": "16px", "fontWeight": "bold", "textAlign": "center", "marginTop": "8px"}}, + {"widgettype": "Text", "options": {"text": "查看供销关系记账记录和利润统计", "fontSize": "12px", "color": "#999999", "textAlign": "center"}} + ] + } + ] + }, + { + "widgettype": "VBox", + "id": "supplychain_content", + "options": { + "width": "100%", + "flex": "1", + "marginTop": "20px" + } + } + ] +} diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui new file mode 100644 index 0000000..dd9deb5 --- /dev/null +++ b/wwwroot/menu.ui @@ -0,0 +1,27 @@ +{ + "widgettype": "Menu", + "options": { + "items": [ + { + "name": "供应商管理", + "url": "{{entire_url('suppliers.ui')}}" + }, + { + "name": "供销合同", + "url": "{{entire_url('supply_contracts.ui')}}" + }, + { + "name": "二级分销商", + "url": "{{entire_url('sub_distributors.ui')}}" + }, + { + "name": "分销协议", + "url": "{{entire_url('distribution_agreements.ui')}}" + }, + { + "name": "供销记账", + "url": "{{entire_url('accounting.ui')}}" + } + ] + } +} \ No newline at end of file diff --git a/wwwroot/sub_distributors.ui b/wwwroot/sub_distributors.ui new file mode 100644 index 0000000..b0678d0 --- /dev/null +++ b/wwwroot/sub_distributors.ui @@ -0,0 +1,25 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "二级分销商管理", + "fontSize": "20px", + "fontWeight": "bold", + "marginBottom": "16px" + } + }, + { + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('sub_distributors_list/index.ui')}}" + } + } + ] +} \ No newline at end of file diff --git a/wwwroot/suppliers.ui b/wwwroot/suppliers.ui new file mode 100644 index 0000000..af1d3ad --- /dev/null +++ b/wwwroot/suppliers.ui @@ -0,0 +1,25 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "供应商管理", + "fontSize": "20px", + "fontWeight": "bold", + "marginBottom": "16px" + } + }, + { + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('suppliers_list/index.ui')}}" + } + } + ] +} \ No newline at end of file diff --git a/wwwroot/supply_contracts.ui b/wwwroot/supply_contracts.ui new file mode 100644 index 0000000..43b5c12 --- /dev/null +++ b/wwwroot/supply_contracts.ui @@ -0,0 +1,25 @@ +{ + "widgettype": "VBox", + "options": { + "width": "100%", + "height": "100%", + "padding": "16px" + }, + "subwidgets": [ + { + "widgettype": "Text", + "options": { + "text": "供销合同管理", + "fontSize": "20px", + "fontWeight": "bold", + "marginBottom": "16px" + } + }, + { + "widgettype": "DataViewer", + "options": { + "url": "{{entire_url('supply_contracts_list/index.ui')}}" + } + } + ] +} \ No newline at end of file