From ed9c96d71988c37c402365ae1c0cf57847514d76 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Tue, 26 May 2026 14:21:31 +0800 Subject: [PATCH] Initial commit: supplychain module - supplier, sub-reseller, contract, agreement, and sales ledger management --- README.md | 329 +++---- build.sh | 48 +- json/distribution_agreement_items_list.json | 16 +- json/distribution_agreements_list.json | 50 +- json/sales_ledger_list.json | 40 + json/sub_resellers_list.json | 36 + json/suppliers_list.json | 38 +- json/supply_contract_items_list.json | 16 +- json/supply_contracts_list.json | 50 +- models/distribution_agreement_items.json | 23 +- models/distribution_agreements.json | 14 +- models/sales_ledger.json | 208 +++++ models/sub_resellers.json | 132 +++ pyproject.toml | 2 +- supplychain/__init__.py | 0 supplychain/__init__.py.bak | 1 + supplychain/init.py | 862 +++++++++++++----- .../distribution_agreement_items_create.dspy | 30 +- .../distribution_agreement_items_delete.dspy | 26 +- .../distribution_agreement_items_update.dspy | 34 +- .../api/distribution_agreements_create.dspy | 35 +- .../api/distribution_agreements_delete.dspy | 26 +- .../api/distribution_agreements_update.dspy | 34 +- wwwroot/api/sales_ledger_create.dspy | 9 + wwwroot/api/sales_ledger_delete.dspy | 9 + wwwroot/api/sales_ledger_update.dspy | 9 + wwwroot/api/sub_resellers_create.dspy | 9 + wwwroot/api/sub_resellers_delete.dspy | 9 + wwwroot/api/sub_resellers_update.dspy | 9 + wwwroot/api/suppliers_create.dspy | 35 +- wwwroot/api/suppliers_delete.dspy | 26 +- wwwroot/api/suppliers_update.dspy | 34 +- wwwroot/api/supply_contract_items_create.dspy | 30 +- wwwroot/api/supply_contract_items_delete.dspy | 26 +- wwwroot/api/supply_contract_items_update.dspy | 34 +- wwwroot/api/supply_contracts_create.dspy | 35 +- wwwroot/api/supply_contracts_delete.dspy | 26 +- wwwroot/api/supply_contracts_update.dspy | 34 +- wwwroot/index.ui | 135 ++- wwwroot/menu.ui | 15 +- 40 files changed, 1533 insertions(+), 1001 deletions(-) create mode 100644 json/sales_ledger_list.json create mode 100644 json/sub_resellers_list.json create mode 100644 models/sales_ledger.json create mode 100644 models/sub_resellers.json delete mode 100644 supplychain/__init__.py create mode 100644 supplychain/__init__.py.bak create mode 100644 wwwroot/api/sales_ledger_create.dspy create mode 100644 wwwroot/api/sales_ledger_delete.dspy create mode 100644 wwwroot/api/sales_ledger_update.dspy create mode 100644 wwwroot/api/sub_resellers_create.dspy create mode 100644 wwwroot/api/sub_resellers_delete.dspy create mode 100644 wwwroot/api/sub_resellers_update.dspy diff --git a/README.md b/README.md index d285d5f..07bb3fa 100644 --- a/README.md +++ b/README.md @@ -1,236 +1,167 @@ -# supplychain — 供应商和分销商管理模块 +# Supplychain Module -## 概述 +Supplier and Reseller Management module for Sage platform. -supplychain 模块为 Sage 平台提供供应商和分销商的全链路管理功能,包括: +## Overview -- **供应商管理**:添加和管理供应商基本信息、联系方式、财务信息 -- **供销合同管理**:运营人员创建与供应商的供销合同,设置合同有效期和产品折扣 -- **二级分销商管理**:销售人员添加和管理二级分销商 -- **分销协议管理**:销售人员与二级分销商签署分销协议,设置产品分销折扣 -- **供销记账**:产品销售时自动计算供销关系的记账金额(进货金额、分销金额、利润) +Manages the complete supply chain workflow for reseller/distributor organizations: +- **Operators** manage suppliers and supply contracts with product-level discount terms +- **Sales** manage sub-resellers (tier-2 distributors) and distribution agreements with product-level discount terms +- **Sales ledger** records transactions and calculates supply/distribution amounts for accounting -## 目录结构 +## Architecture + +### Tables + +| Table | Description | +|-------|-------------| +| `suppliers` | Supplier master data (contact, bank, tax info) | +| `supply_contracts` | Supply contracts between reseller and supplier | +| `supply_contract_items` | Product-level discount details per supply contract | +| `sub_resellers` | Sub-reseller (tier-2 distributor) master data | +| `distribution_agreements` | Distribution agreements between reseller and sub-reseller | +| `distribution_agreement_items` | Product-level discount details per distribution agreement | +| `sales_ledger` | Sales transaction records with calculated amounts | + +### Discount Calculation Logic + +`calculate_sale_amounts()` API is called during product sales: + +1. Finds active supply contract for the supplier (filtered by date and status) +2. Looks up product-specific discount in contract items (priority: exact product > product type > default) +3. Finds active distribution agreement for the sub-reseller +4. Looks up product-specific discount in agreement items (same priority) +5. Calculates: supply_amount = total * supply_discount, distribution_amount = total * distribution_discount, profit = distribution - supply + +### Role-Based Access + +- `reseller.operator` — manage suppliers, supply contracts, and contract items +- `reseller.sale` — manage sub-resellers, distribution agreements, and agreement items +- `reseller.accountant` — view and manage sales ledger entries + +## Directory Structure ``` supplychain/ -├── supplychain/ # Python 包 -│ ├── __init__.py -│ └── init.py # 模块初始化 + ServerEnv 注册 -├── wwwroot/ # 前端文件 -│ ├── index.ui # 模块入口页 -│ ├── menu.ui # 导航菜单 -│ ├── suppliers.ui # 供应商管理页 -│ ├── supply_contracts.ui # 供销合同页 -│ ├── sub_distributors.ui # 二级分销商页 -│ ├── distribution_agreements.ui # 分销协议页 -│ ├── accounting.ui # 供销记账页 -│ └── api/ # API 端点 -│ ├── *_create.dspy -│ ├── *_update.dspy -│ ├── *_delete.dspy -│ ├── calculate_accounting.dspy # 记账计算 API -│ ├── query_supply_discount.dspy # 查询进货折扣 -│ └── query_dist_discount.dspy # 查询分销折扣 -├── models/ # 数据库表定义 -│ ├── suppliers.json -│ ├── supply_contracts.json -│ ├── supply_contract_items.json -│ ├── sub_distributors.json -│ ├── distribution_agreements.json -│ ├── distribution_agreement_items.json -│ └── supplychain_accounting.json -├── json/ # CRUD 配置 -│ ├── suppliers_list.json -│ ├── supply_contracts_list.json -│ ├── supply_contract_items_list.json -│ ├── sub_distributors_list.json -│ ├── distribution_agreements_list.json -│ ├── distribution_agreement_items_list.json -│ └── supplychain_accounting_list.json -├── init/ -│ └── data.json # 初始化数据(可选) -├── scripts/ -│ └── load_path.py # RBAC 权限管理脚本 +├── supplychain/ +│ ├── __init__.py # Package init +│ └── init.py # Module init + ServerEnv registration +├── wwwroot/ +│ ├── index.ui # Module entry point +│ ├── menu.ui # Navigation menu +│ └── api/ # CRUD API endpoints (.dspy files) +├── models/ # Table definitions (JSON) +├── json/ # CRUD definitions (JSON) +├── init/ # Initialization data ├── pyproject.toml ├── build.sh └── README.md ``` -## 数据库表 - -| 表名 | 说明 | 类别 | -|------|------|------| -| suppliers | 供应商表 | entity | -| supply_contracts | 供销合同表 | entity | -| supply_contract_items | 供销合同产品折扣明细 | relation | -| sub_distributors | 二级分销商表 | entity | -| distribution_agreements | 分销协议表 | entity | -| distribution_agreement_items | 分销协议产品折扣明细 | relation | -| supplychain_accounting | 供销记账表 | relation | - -## 角色权限 - -| 角色 | 权限范围 | -|------|----------| -| 运营 (operator) | 供应商管理、供销合同管理(含产品折扣) | -| 销售 (sale) | 二级分销商管理、分销协议管理(含产品折扣) | -| 所有登录用户 | 供销记账查看 | - -## 折扣计算规则 - -### 进货折扣(供应商 → 分销商) - -查找优先级: -1. 供销合同 → 产品明细(精确匹配 productid) -2. 供销合同 → 产品明细(匹配 prodtypeid) -3. 供销合同 → 默认折扣(default_discount) - -### 分销折扣(分销商 → 二级分销商) - -查找优先级: -1. 分销协议 → 产品明细(精确匹配 productid) -2. 分销协议 → 产品明细(匹配 prodtypeid) -3. 分销协议 → 默认折扣(default_discount) - -### 记账金额计算 - -``` -销售总额 = 单价 × 数量 -进货金额 = 销售总额 × 进货折扣 (或 结算单价 × 数量) -分销金额 = 销售总额 × 分销折扣 (或 结算单价 × 数量) -利润金额 = 分销金额 - 进货金额 -``` - -## API 接口 - -### 记账计算 API - -**端点**: `/supplychain/api/calculate_accounting.dspy` - -**请求方法**: POST - -**参数**: -```json -{ - "productid": "产品ID", - "prodtypeid": "产品分类ID (可选)", - "quantity": 10, - "unit_price": 100.00, - "sub_distributor_id": "二级分销商ID (可选)", - "sale_date": "2026-05-25 (可选, 默认今天)", - "source_type": "2 (来源类型: 1=手动, 2=API)", - "source_id": "来源记录ID (可选)", - "remark": "备注 (可选)" -} -``` - -**返回**: -```json -{ - "status": "ok", - "data": {...}, - "summary": { - "total_amount": 1000.00, - "supply_amount": 700.00, - "dist_amount": 900.00, - "profit_amount": 200.00, - "supply_discount": 0.7, - "dist_discount": 0.9 - } -} -``` - -### 折扣查询 API - -**进货折扣查询**: `/supplychain/api/query_supply_discount.dspy` -- 参数: `productid`, `prodtypeid` (可选) - -**分销折扣查询**: `/supplychain/api/query_dist_discount.dspy` -- 参数: `sub_distributor_id`, `productid`, `prodtypeid` (可选) - -## Python 模块函数 - -通过 `ServerEnv` 注册后可在 .dspy 文件中直接调用: - -```python -# 获取进货折扣 -supply_info = await get_active_supply_discount(sor, resellerid, productid, prodtypeid, sale_date) - -# 获取分销折扣 -dist_info = await get_active_dist_discount(sor, resellerid, sub_distributor_id, productid, prodtypeid, sale_date) - -# 计算并创建记账记录 -record = await calculate_sale_accounting(sor, resellerid, productid, quantity, unit_price, - sub_distributor_id, prodtypeid, sale_date, source_type, source_id, created_by, remark) -``` - -## 安装步骤 - -### 1. 克隆模块 +## Sage Integration +### 1. Install module ```bash -cd ~/repos -git clone git@git.opencomputing.cn:yumoqing/supplychain.git +cd ~/repos/sage/pkgs +git clone https://git.opencomputing.cn/yumoqing/supplychain +cd supplychain +../../py3/bin/pip install . ``` -### 2. 构建模块 - +### 2. Generate DDL and apply to database ```bash -cd ~/repos/supplychain -chmod +x build.sh -./build.sh +cd ~/repos/sage/pkgs/supplychain/models +../../py3/bin/json2ddl mysql . > mysql.ddl.sql +mysql -h -u -p sage < mysql.ddl.sql ``` -### 3. 生成 DDL 并创建数据库表 - +### 3. Generate CRUD UI files ```bash -cd ~/repos/supplychain/models -mysql -h -u -p < mysql.ddl.sql +cd ~/repos/sage/pkgs/supplychain/json +../../py3/bin/xls2ui -m ../models -o ../wwwroot supplychain *.json ``` -### 4. 集成到 Sage +### 4. Link wwwroot +```bash +cd ~/repos/sage/wwwroot +ln -sf ../pkgs/supplychain/wwwroot supplychain +``` -**a. 修改 `app/sage.py`**: +### 5. Register module in Sage +Add to `~/repos/sage/app/sage.py`: ```python from supplychain.init import load_supplychain -# 在 init() 函数中添加: +# In init(): load_supplychain() ``` -**b. 修改 `build.sh`** (Sage 根目录): -```bash -for m in ... supplychain +### 6. Register RBAC permissions +Add to `~/repos/sage/load_path.py`: +``` +/supplychain logined +/supplychain/index.ui logined +/supplychain/menu.ui any +/supplychain/suppliers_list logined +/supplychain/suppliers_list/index.ui logined +/supplychain/supply_contracts_list logined +/supplychain/supply_contracts_list/index.ui logined +/supplychain/supply_contract_items_list logined +/supplychain/supply_contract_items_list/index.ui logined +/supplychain/sub_resellers_list logined +/supplychain/sub_resellers_list/index.ui logined +/supplychain/distribution_agreements_list logined +/supplychain/distribution_agreements_list/index.ui logined +/supplychain/distribution_agreement_items_list logined +/supplychain/distribution_agreement_items_list/index.ui logined +/supplychain/sales_ledger_list logined +/supplychain/sales_ledger_list/index.ui logined +/supplychain/api/suppliers_create.dspy logined +/supplychain/api/suppliers_update.dspy logined +/supplychain/api/suppliers_delete.dspy logined +/supplychain/api/supply_contracts_create.dspy logined +/supplychain/api/supply_contracts_update.dspy logined +/supplychain/api/supply_contracts_delete.dspy logined +/supplychain/api/supply_contract_items_create.dspy logined +/supplychain/api/supply_contract_items_update.dspy logined +/supplychain/api/supply_contract_items_delete.dspy logined +/supplychain/api/sub_resellers_create.dspy logined +/supplychain/api/sub_resellers_update.dspy logined +/supplychain/api/sub_resellers_delete.dspy logined +/supplychain/api/distribution_agreements_create.dspy logined +/supplychain/api/distribution_agreements_update.dspy logined +/supplychain/api/distribution_agreements_delete.dspy logined +/supplychain/api/distribution_agreement_items_create.dspy logined +/supplychain/api/distribution_agreement_items_update.dspy logined +/supplychain/api/distribution_agreement_items_delete.dspy logined +/supplychain/api/sales_ledger_create.dspy logined +/supplychain/api/sales_ledger_update.dspy logined +/supplychain/api/sales_ledger_delete.dspy logined ``` -### 5. 注册 RBAC 权限 - -模块提供 `scripts/load_path.py` 权限管理脚本,自动注册所有路径: - +Then run: ```bash -cd ~/repos/sage -./py3/bin/python ~/repos/supplychain/scripts/load_path.py +cd ~/repos/sage && ./py3/bin/python load_path.py ``` -脚本按角色分类注册: -- `any`: 静态资源/CRUD 别名目录 -- `logined`: 所有页面和 API -- `reseller.operator`: 供应商、供销合同管理 -- `reseller.sale`: 二级分销商、分销协议管理 - -**维护规则**: 每次代码变更如有新 path 出现,需同步更新 `scripts/load_path.py` 中的路径列表。 - -### 6. 重启 Sage - +### 7. Restart Sage ```bash -cd ~/repos/sage -./stop.sh && ./start.sh +cd ~/repos/sage && ./stop.sh && ./start.sh ``` -## 产品模块引用 +## API for External Modules -本模块的 `productid` 和 `prodtypeid` 字段引用 product 模块的 `products` 和 `product_types` 表。 -已在以下模型的 `codes` 段添加引用配置: -- `models/supply_contract_items.json` -- `models/distribution_agreement_items.json` -- `models/supplychain_accounting.json` +Other modules can call `calculate_sale_amounts()` to compute supply/distribution amounts during sales: + +```python +env = ServerEnv() +result = await env.calculate_sale_amounts(request, { + "resellerid": org_id, + "sub_reseller_id": sub_reseller_id, # optional + "supplier_id": supplier_id, # optional + "prodtypeid": product_type_id, + "productid": product_id, + "quantity": qty, + "unit_price": price, +}) +# Returns: {contract_id, agreement_id, total_amount, supply_discount, supply_amount, distribution_discount, distribution_amount, profit_amount} +``` diff --git a/build.sh b/build.sh index 966fc4e..ddab3ce 100644 --- a/build.sh +++ b/build.sh @@ -1,10 +1,12 @@ -#!/usr/bin/env bash +#!/bin/bash +# Supplychain module build script +# Links wwwroot files to Sage and generates DDL/CRUD UI files + set -e -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -cd "$SCRIPT_DIR" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# Find Sage root directory +# Find Sage root SAGE_ROOT="" for candidate in "$SCRIPT_DIR/../.." "$HOME/repos/sage" "$HOME/sage"; do if [ -d "$candidate/wwwroot" ] && [ -d "$candidate/py3/bin" ]; then @@ -14,38 +16,38 @@ for candidate in "$SCRIPT_DIR/../.." "$HOME/repos/sage" "$HOME/sage"; do done if [ -z "$SAGE_ROOT" ]; then - echo "ERROR: Cannot find Sage root directory" + echo "ERROR: Sage root not found" exit 1 fi echo "Sage root: $SAGE_ROOT" -# Install module -cd "$SCRIPT_DIR" -$SAGE_ROOT/py3/bin/pip install -e . - -# Generate DDL from models +# Generate DDL from models if present if [ -d "$SCRIPT_DIR/models" ]; then - echo "Generating DDL..." + echo "Generating DDL from models..." cd "$SCRIPT_DIR/models" - $SAGE_ROOT/py3/bin/json2ddl mysql . > mysql.ddl.sql - echo "DDL generated: $SCRIPT_DIR/models/mysql.ddl.sql" + if ls *.xlsx 1>/dev/null 2>&1; then + "$SAGE_ROOT/py3/bin/xls2ddl" mysql . > mysql.ddl.sql + echo "DDL generated: models/mysql.ddl.sql" + elif ls *.json 1>/dev/null 2>&1; then + "$SAGE_ROOT/py3/bin/json2ddl" mysql . > mysql.ddl.sql + echo "DDL generated: models/mysql.ddl.sql" + fi fi -# Generate CRUD UI from json definitions +# Generate CRUD UI from json definitions if present 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." + if [ -d "$SCRIPT_DIR/models" ]; then + "$SAGE_ROOT/py3/bin/xls2ui" -m "$SCRIPT_DIR/models" -o "$SCRIPT_DIR/wwwroot" supplychain *.json + echo "CRUD UI files generated in wwwroot/" + fi fi -# Create symlink in Sage wwwroot -echo "Creating wwwroot symlink..." +# Link wwwroot to Sage +echo "Linking wwwroot to Sage..." rm -f "$SAGE_ROOT/wwwroot/supplychain" -ln -s "$SCRIPT_DIR/wwwroot" "$SAGE_ROOT/wwwroot/supplychain" +ln -sf "$SCRIPT_DIR/wwwroot" "$SAGE_ROOT/wwwroot/supplychain" -echo "Build complete." +echo "Supplychain module build complete." diff --git a/json/distribution_agreement_items_list.json b/json/distribution_agreement_items_list.json index 8a995f6..d5348f0 100644 --- a/json/distribution_agreement_items_list.json +++ b/json/distribution_agreement_items_list.json @@ -3,25 +3,15 @@ "alias": "distribution_agreement_items_list", "title": "分销协议产品折扣", "params": { - "sortby": [ - "created_at desc" - ], + "sortby": ["prodtypeid", "productid"], "logined_userorgid": "resellerid", "browserfields": { - "exclouded": [ - "created_at" - ] + "exclouded": ["id", "agreement_id", "resellerid"] }, - "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 index fdc0e7d..78f04c8 100644 --- a/json/distribution_agreements_list.json +++ b/json/distribution_agreements_list.json @@ -3,47 +3,37 @@ "alias": "distribution_agreements_list", "title": "分销协议管理", "params": { - "sortby": [ - "created_at desc" - ], + "sortby": ["created_at desc"], "logined_userorgid": "resellerid", + "data_filter": { + "AND": [ + {"field": "agreement_name", "op": "LIKE", "var": "agreement_name"}, + {"field": "agreement_code", "op": "LIKE", "var": "agreement_code"}, + {"field": "status", "op": "=", "var": "status"} + ] + }, + "filter_labels": { + "agreement_name": "协议名称", + "agreement_code": "协议编号", + "status": "状态" + }, "browserfields": { - "exclouded": [ - "created_by", - "created_at", - "updated_at" - ], + "exclouded": ["id"], "alters": { "status": { "uitype": "code", "data": [ - { - "value": "1", - "text": "有效" - }, - { - "value": "0", - "text": "无效" - }, - { - "value": "2", - "text": "已过期" - } + {"value": "1", "text": "生效中"}, + {"value": "2", "text": "已到期"}, + {"value": "0", "text": "已终止"} ] } } }, - "editexclouded": [ - "id", - "resellerid", - "created_by", - "created_at", - "updated_at" - ], "subtables": [ { - "field": "id", - "title": "产品折扣", + "field": "agreement_id", + "title": "产品分销折扣", "url": "{{entire_url('../distribution_agreement_items_list')}}", "subtable": "distribution_agreement_items" } @@ -54,4 +44,4 @@ "delete_data_url": "{{entire_url('../api/distribution_agreements_delete.dspy')}}" } } -} \ No newline at end of file +} diff --git a/json/sales_ledger_list.json b/json/sales_ledger_list.json new file mode 100644 index 0000000..a9dbd29 --- /dev/null +++ b/json/sales_ledger_list.json @@ -0,0 +1,40 @@ +{ + "tblname": "sales_ledger", + "alias": "sales_ledger_list", + "title": "销售记账", + "params": { + "sortby": ["sale_date desc", "created_at desc"], + "logined_userorgid": "resellerid", + "data_filter": { + "AND": [ + {"field": "sale_date", "op": ">=", "var": "sale_date_start"}, + {"field": "sale_date", "op": "<=", "var": "sale_date_end"}, + {"field": "productid", "op": "=", "var": "productid"}, + {"field": "settlement_status", "op": "=", "var": "settlement_status"} + ] + }, + "filter_labels": { + "sale_date_start": "销售开始日期", + "sale_date_end": "销售结束日期", + "productid": "产品ID", + "settlement_status": "结算状态" + }, + "browserfields": { + "exclouded": ["id"], + "alters": { + "settlement_status": { + "uitype": "code", + "data": [ + {"value": "0", "text": "未结算"}, + {"value": "1", "text": "已结算"} + ] + } + } + }, + "editable": { + "new_data_url": "{{entire_url('../api/sales_ledger_create.dspy')}}", + "update_data_url": "{{entire_url('../api/sales_ledger_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/sales_ledger_delete.dspy')}}" + } + } +} diff --git a/json/sub_resellers_list.json b/json/sub_resellers_list.json new file mode 100644 index 0000000..98d414d --- /dev/null +++ b/json/sub_resellers_list.json @@ -0,0 +1,36 @@ +{ + "tblname": "sub_resellers", + "alias": "sub_resellers_list", + "title": "二级分销商管理", + "params": { + "sortby": ["created_at desc"], + "logined_userorgid": "resellerid", + "data_filter": { + "AND": [ + {"field": "sub_reseller_name", "op": "LIKE", "var": "sub_reseller_name"}, + {"field": "status", "op": "=", "var": "status"} + ] + }, + "filter_labels": { + "sub_reseller_name": "二级分销商名称", + "status": "状态" + }, + "browserfields": { + "exclouded": ["id"], + "alters": { + "status": { + "uitype": "code", + "data": [ + {"value": "1", "text": "正常"}, + {"value": "0", "text": "停用"} + ] + } + } + }, + "editable": { + "new_data_url": "{{entire_url('../api/sub_resellers_create.dspy')}}", + "update_data_url": "{{entire_url('../api/sub_resellers_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/sub_resellers_delete.dspy')}}" + } + } +} diff --git a/json/suppliers_list.json b/json/suppliers_list.json index b115a59..0860f7f 100644 --- a/json/suppliers_list.json +++ b/json/suppliers_list.json @@ -3,28 +3,34 @@ "alias": "suppliers_list", "title": "供应商管理", "params": { - "sortby": [ - "created_at desc" - ], + "sortby": ["created_at desc"], "logined_userorgid": "resellerid", - "browserfields": { - "exclouded": [ - "created_by", - "created_at", - "updated_at" + "data_filter": { + "AND": [ + {"field": "supplier_name", "op": "LIKE", "var": "supplier_name"}, + {"field": "status", "op": "=", "var": "status"} ] }, - "editexclouded": [ - "id", - "resellerid", - "created_by", - "created_at", - "updated_at" - ], + "filter_labels": { + "supplier_name": "供应商名称", + "status": "状态" + }, + "browserfields": { + "exclouded": ["id"], + "alters": { + "status": { + "uitype": "code", + "data": [ + {"value": "1", "text": "正常"}, + {"value": "0", "text": "停用"} + ] + } + } + }, "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 index 2189556..7107280 100644 --- a/json/supply_contract_items_list.json +++ b/json/supply_contract_items_list.json @@ -3,25 +3,15 @@ "alias": "supply_contract_items_list", "title": "供销合同产品折扣", "params": { - "sortby": [ - "created_at desc" - ], + "sortby": ["prodtypeid", "productid"], "logined_userorgid": "resellerid", "browserfields": { - "exclouded": [ - "created_at" - ] + "exclouded": ["id", "contract_id", "resellerid"] }, - "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 index 48c4d7c..6f4d2c6 100644 --- a/json/supply_contracts_list.json +++ b/json/supply_contracts_list.json @@ -3,47 +3,37 @@ "alias": "supply_contracts_list", "title": "供销合同管理", "params": { - "sortby": [ - "created_at desc" - ], + "sortby": ["created_at desc"], "logined_userorgid": "resellerid", + "data_filter": { + "AND": [ + {"field": "contract_name", "op": "LIKE", "var": "contract_name"}, + {"field": "contract_code", "op": "LIKE", "var": "contract_code"}, + {"field": "status", "op": "=", "var": "status"} + ] + }, + "filter_labels": { + "contract_name": "合同名称", + "contract_code": "合同编号", + "status": "状态" + }, "browserfields": { - "exclouded": [ - "created_by", - "created_at", - "updated_at" - ], + "exclouded": ["id"], "alters": { "status": { "uitype": "code", "data": [ - { - "value": "1", - "text": "有效" - }, - { - "value": "0", - "text": "无效" - }, - { - "value": "2", - "text": "已过期" - } + {"value": "1", "text": "生效中"}, + {"value": "2", "text": "已到期"}, + {"value": "0", "text": "已终止"} ] } } }, - "editexclouded": [ - "id", - "resellerid", - "created_by", - "created_at", - "updated_at" - ], "subtables": [ { - "field": "id", - "title": "产品折扣", + "field": "contract_id", + "title": "产品折扣明细", "url": "{{entire_url('../supply_contract_items_list')}}", "subtable": "supply_contract_items" } @@ -54,4 +44,4 @@ "delete_data_url": "{{entire_url('../api/supply_contracts_delete.dspy')}}" } } -} \ No newline at end of file +} diff --git a/models/distribution_agreement_items.json b/models/distribution_agreement_items.json index 2bbe0fd..7c3ebc9 100644 --- a/models/distribution_agreement_items.json +++ b/models/distribution_agreement_items.json @@ -24,7 +24,7 @@ }, { "name": "resellerid", - "title": "所属主分销商机构ID", + "title": "所属分销商机构ID", "type": "str", "length": 32, "nullable": "no" @@ -51,8 +51,13 @@ "default": "1.0000" }, { - "name": "settlement_price", - "title": "结算单价", + "name": "min_order_qty", + "title": "最小订购量", + "type": "int" + }, + { + "name": "sale_price", + "title": "分销指导价", "type": "double", "length": 15, "dec": 4 @@ -87,18 +92,6 @@ "table": "distribution_agreements", "valuefield": "id", "textfield": "agreement_name" - }, - { - "field": "prodtypeid", - "table": "product_types", - "valuefield": "id", - "textfield": "type_name" - }, - { - "field": "productid", - "table": "products", - "valuefield": "id", - "textfield": "product_name" } ] } diff --git a/models/distribution_agreements.json b/models/distribution_agreements.json index 3d8bc5b..f32c422 100644 --- a/models/distribution_agreements.json +++ b/models/distribution_agreements.json @@ -17,13 +17,13 @@ }, { "name": "resellerid", - "title": "所属主分销商机构ID", + "title": "所属分销商机构ID", "type": "str", "length": 32, "nullable": "no" }, { - "name": "sub_distributor_id", + "name": "sub_reseller_id", "title": "二级分销商ID", "type": "str", "length": 32, @@ -105,9 +105,9 @@ "idxfields": ["resellerid"] }, { - "name": "idx_da_subdist", + "name": "idx_da_sub_reseller", "idxtype": "index", - "idxfields": ["sub_distributor_id"] + "idxfields": ["sub_reseller_id"] }, { "name": "idx_da_code", @@ -117,10 +117,10 @@ ], "codes": [ { - "field": "sub_distributor_id", - "table": "sub_distributors", + "field": "sub_reseller_id", + "table": "sub_resellers", "valuefield": "id", - "textfield": "sub_dist_name" + "textfield": "sub_reseller_name" }, { "field": "resellerid", diff --git a/models/sales_ledger.json b/models/sales_ledger.json new file mode 100644 index 0000000..c082c89 --- /dev/null +++ b/models/sales_ledger.json @@ -0,0 +1,208 @@ +{ + "summary": [ + { + "name": "sales_ledger", + "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": "sub_reseller_id", + "title": "二级分销商ID", + "type": "str", + "length": 32 + }, + { + "name": "supplier_id", + "title": "供应商ID", + "type": "str", + "length": 32 + }, + { + "name": "agreement_id", + "title": "分销协议ID", + "type": "str", + "length": 32 + }, + { + "name": "contract_id", + "title": "供销合同ID", + "type": "str", + "length": 32 + }, + { + "name": "prodtypeid", + "title": "产品分类ID", + "type": "str", + "length": 32 + }, + { + "name": "productid", + "title": "产品ID", + "type": "str", + "length": 32 + }, + { + "name": "sale_date", + "title": "销售日期", + "type": "date", + "nullable": "no" + }, + { + "name": "quantity", + "title": "销售数量", + "type": "double", + "length": 15, + "dec": 2, + "nullable": "no" + }, + { + "name": "unit_price", + "title": "销售单价", + "type": "double", + "length": 15, + "dec": 4, + "nullable": "no" + }, + { + "name": "supply_discount", + "title": "进货折扣", + "type": "double", + "length": 5, + "dec": 4 + }, + { + "name": "supply_amount", + "title": "进货金额", + "type": "double", + "length": 15, + "dec": 2 + }, + { + "name": "distribution_discount", + "title": "分销折扣", + "type": "double", + "length": 5, + "dec": 4 + }, + { + "name": "distribution_amount", + "title": "分销金额", + "type": "double", + "length": 15, + "dec": 2 + }, + { + "name": "profit_amount", + "title": "利润金额", + "type": "double", + "length": 15, + "dec": 2 + }, + { + "name": "settlement_status", + "title": "结算状态", + "type": "char", + "length": 1, + "nullable": "no", + "default": "0" + }, + { + "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_sl_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_sl_sale_date", + "idxtype": "index", + "idxfields": ["sale_date"] + }, + { + "name": "idx_sl_product", + "idxtype": "index", + "idxfields": ["prodtypeid", "productid"] + }, + { + "name": "idx_sl_sub_reseller", + "idxtype": "index", + "idxfields": ["sub_reseller_id"] + }, + { + "name": "idx_sl_supplier", + "idxtype": "index", + "idxfields": ["supplier_id"] + } + ], + "codes": [ + { + "field": "sub_reseller_id", + "table": "sub_resellers", + "valuefield": "id", + "textfield": "sub_reseller_name" + }, + { + "field": "supplier_id", + "table": "suppliers", + "valuefield": "id", + "textfield": "supplier_name" + }, + { + "field": "agreement_id", + "table": "distribution_agreements", + "valuefield": "id", + "textfield": "agreement_name" + }, + { + "field": "contract_id", + "table": "supply_contracts", + "valuefield": "id", + "textfield": "contract_name" + }, + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/models/sub_resellers.json b/models/sub_resellers.json new file mode 100644 index 0000000..ab4e42b --- /dev/null +++ b/models/sub_resellers.json @@ -0,0 +1,132 @@ +{ + "summary": [ + { + "name": "sub_resellers", + "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_reseller_code", + "title": "二级分销商编号", + "type": "str", + "length": 64, + "nullable": "no" + }, + { + "name": "sub_reseller_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_sr_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_sr_code", + "idxtype": "unique", + "idxfields": ["resellerid", "sub_reseller_code"] + } + ], + "codes": [ + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + } + ] +} diff --git a/pyproject.toml b/pyproject.toml index 3c7097e..d6db327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "supplychain" version = "1.0.0" -description = "供应商和分销商管理模块 — 供销合同、分销协议、产品折扣及供销记账" +description = "Supply chain management module - suppliers, contracts, sub-resellers, distribution agreements, and sales ledger" requires-python = ">=3.8" dependencies = [ "sqlor", diff --git a/supplychain/__init__.py b/supplychain/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/supplychain/__init__.py.bak b/supplychain/__init__.py.bak new file mode 100644 index 0000000..9221053 --- /dev/null +++ b/supplychain/__init__.py.bak @@ -0,0 +1 @@ +# supplychain Python package diff --git a/supplychain/init.py b/supplychain/init.py index 8ef0243..74c7c76 100644 --- a/supplychain/init.py +++ b/supplychain/init.py @@ -1,252 +1,668 @@ -from ahserver.serverenv import ServerEnv +""" +Supplychain module - Supplier and Reseller Management +Handles suppliers, supply contracts, sub-resellers, distribution agreements, +and sales ledger for accounting calculations. +""" +import json +from datetime import datetime, date +from appPublic.uniqueID import getID 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 +from ahserver.serverenv import ServerEnv MODULE_NAME = "supplychain" -MODULE_VERSION = "1.0.0" def _get_dbname(): - """Get the database name for the supplychain module.""" + """Get module database name dynamically.""" env = ServerEnv() - return env.get_module_dbname('supplychain') + return env.get_module_dbname(MODULE_NAME) -def get_db_context(): - """Get a database context manager for the supplychain module.""" - config = getConfig('.') - DBPools(config.databases) - dbname = _get_dbname() - return db.sqlorContext(dbname) +def _get_sor(): + """Get a sqlor context for this module's database.""" + config = getConfig() + db = DBPools() + db.databases = config.databases + return db, _get_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, +def _generate_supplier_code(resellerid): + """Generate unique supplier code: SUP-{YYYYMMDD}-{seq}.""" + env = ServerEnv() + today = env.strdate(env.today()) + prefix = f"SUP-{today.replace('-', '')}" + db, dbname = _get_sor() + with db.sqlorContext(dbname) as sor: + sql = """SELECT COUNT(*) as cnt FROM suppliers +WHERE resellerid = ${resellerid}$ AND supplier_code LIKE ${prefix}$""" + recs = sor.sqlExe(sql, {"resellerid": resellerid, "prefix": prefix + "%"}) + seq = (recs[0].cnt if recs else 0) + 1 + return f"{prefix}-{seq:04d}" + + +def _generate_contract_code(resellerid): + """Generate unique contract code: SC-{YYYYMMDD}-{seq}.""" + env = ServerEnv() + today = env.strdate(env.today()) + prefix = f"SC-{today.replace('-', '')}" + db, dbname = _get_sor() + with db.sqlorContext(dbname) as sor: + sql = """SELECT COUNT(*) as cnt FROM supply_contracts +WHERE resellerid = ${resellerid}$ AND contract_code LIKE ${prefix}$""" + recs = sor.sqlExe(sql, {"resellerid": resellerid, "prefix": prefix + "%"}) + seq = (recs[0].cnt if recs else 0) + 1 + return f"{prefix}-{seq:04d}" + + +def _generate_sub_reseller_code(resellerid): + """Generate unique sub-reseller code: SR-{YYYYMMDD}-{seq}.""" + env = ServerEnv() + today = env.strdate(env.today()) + prefix = f"SR-{today.replace('-', '')}" + db, dbname = _get_sor() + with db.sqlorContext(dbname) as sor: + sql = """SELECT COUNT(*) as cnt FROM sub_resellers +WHERE resellerid = ${resellerid}$ AND sub_reseller_code LIKE ${prefix}$""" + recs = sor.sqlExe(sql, {"resellerid": resellerid, "prefix": prefix + "%"}) + seq = (recs[0].cnt if recs else 0) + 1 + return f"{prefix}-{seq:04d}" + + +def _generate_agreement_code(resellerid): + """Generate unique agreement code: DA-{YYYYMMDD}-{seq}.""" + env = ServerEnv() + today = env.strdate(env.today()) + prefix = f"DA-{today.replace('-', '')}" + db, dbname = _get_sor() + with db.sqlorContext(dbname) as sor: + sql = """SELECT COUNT(*) as cnt FROM distribution_agreements +WHERE resellerid = ${resellerid}$ AND agreement_code LIKE ${prefix}$""" + recs = sor.sqlExe(sql, {"resellerid": resellerid, "prefix": prefix + "%"}) + seq = (recs[0].cnt if recs else 0) + 1 + return f"{prefix}-{seq:04d}" + + +# ============================================================ +# Supplier APIs +# ============================================================ + +async def create_supplier(request, params_kw): + """Create a new supplier record.""" + env = ServerEnv() + user_id = await env.get_user() + resellerid = await env.get_userorgid() + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + supplier_code = data.get("supplier_code") or _generate_supplier_code(resellerid) + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = { + "id": getID(), + "resellerid": resellerid, + "supplier_code": supplier_code, + "supplier_name": data.get("supplier_name"), + "contact_person": data.get("contact_person"), + "contact_phone": data.get("contact_phone"), + "contact_email": data.get("contact_email"), + "address": data.get("address"), + "tax_number": data.get("tax_number"), + "bank_name": data.get("bank_name"), + "bank_account": data.get("bank_account"), + "status": data.get("status", "1"), + "remark": data.get("remark"), + "created_by": user_id, + "created_at": now, + "updated_at": now, } - - # 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, + await sor.C("suppliers", rec) + return json.dumps({"status": "ok", "data": rec, "message": "创建成功"}) + + +async def update_supplier(request, params_kw): + """Update a supplier record.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = {"id": data["id"], "updated_at": now} + for key in ["supplier_name", "contact_person", "contact_phone", "contact_email", + "address", "tax_number", "bank_name", "bank_account", "status", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("suppliers", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_supplier(request, params_kw): + """Delete a supplier record.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("suppliers", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Supply Contract APIs +# ============================================================ + +async def create_supply_contract(request, params_kw): + """Create a new supply contract.""" + env = ServerEnv() + user_id = await env.get_user() + resellerid = await env.get_userorgid() + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + contract_code = data.get("contract_code") or _generate_contract_code(resellerid) + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = { + "id": getID(), + "resellerid": resellerid, + "supplier_id": data.get("supplier_id"), + "contract_code": contract_code, + "contract_name": data.get("contract_name"), + "sign_date": data.get("sign_date"), + "start_date": data.get("start_date"), + "end_date": data.get("end_date"), + "status": data.get("status", "1"), + "default_discount": data.get("default_discount", 1.0), + "remark": data.get("remark"), + "created_by": user_id, + "created_at": now, + "updated_at": now, } - - return None + await sor.C("supply_contracts", rec) + return json.dumps({"status": "ok", "data": rec, "message": "创建成功"}) -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, +async def update_supply_contract(request, params_kw): + """Update a supply contract.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = {"id": data["id"], "updated_at": now} + for key in ["supplier_id", "contract_name", "sign_date", "start_date", + "end_date", "status", "default_discount", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("supply_contracts", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_supply_contract(request, params_kw): + """Delete a supply contract and its items.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("supply_contract_items", {"contract_id": data["id"]}) + await sor.D("supply_contracts", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Supply Contract Items APIs +# ============================================================ + +async def create_supply_contract_item(request, params_kw): + """Create a supply contract product discount item.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = { + "id": getID(), + "contract_id": data["contract_id"], + "resellerid": data.get("resellerid", ""), + "prodtypeid": data.get("prodtypeid"), + "productid": data.get("productid"), + "discount": data.get("discount", 1.0), + "settlement_price": data.get("settlement_price"), + "remark": data.get("remark"), + "created_at": now, } - - # 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, + await sor.C("supply_contract_items", rec) + return json.dumps({"status": "ok", "data": rec, "message": "创建成功"}) + + +async def update_supply_contract_item(request, params_kw): + """Update a supply contract item.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + rec = {"id": data["id"]} + for key in ["prodtypeid", "productid", "discount", "settlement_price", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("supply_contract_items", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_supply_contract_item(request, params_kw): + """Delete a supply contract item.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("supply_contract_items", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Sub-Reseller APIs +# ============================================================ + +async def create_sub_reseller(request, params_kw): + """Create a new sub-reseller.""" + env = ServerEnv() + user_id = await env.get_user() + resellerid = await env.get_userorgid() + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + sub_reseller_code = data.get("sub_reseller_code") or _generate_sub_reseller_code(resellerid) + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = { + "id": getID(), + "resellerid": resellerid, + "sub_reseller_code": sub_reseller_code, + "sub_reseller_name": data.get("sub_reseller_name"), + "contact_person": data.get("contact_person"), + "contact_phone": data.get("contact_phone"), + "contact_email": data.get("contact_email"), + "address": data.get("address"), + "tax_number": data.get("tax_number"), + "bank_name": data.get("bank_name"), + "bank_account": data.get("bank_account"), + "status": data.get("status", "1"), + "remark": data.get("remark"), + "created_by": user_id, + "created_at": now, + "updated_at": now, } - - return None + await sor.C("sub_resellers", rec) + return json.dumps({"status": "ok", "data": rec, "message": "创建成功"}) -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=""): +async def update_sub_reseller(request, params_kw): + """Update a sub-reseller.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = {"id": data["id"], "updated_at": now} + for key in ["sub_reseller_name", "contact_person", "contact_phone", "contact_email", + "address", "tax_number", "bank_name", "bank_account", "status", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("sub_resellers", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_sub_reseller(request, params_kw): + """Delete a sub-reseller.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("sub_resellers", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Distribution Agreement APIs +# ============================================================ + +async def create_distribution_agreement(request, params_kw): + """Create a new distribution agreement.""" + env = ServerEnv() + user_id = await env.get_user() + resellerid = await env.get_userorgid() + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + agreement_code = data.get("agreement_code") or _generate_agreement_code(resellerid) + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = { + "id": getID(), + "resellerid": resellerid, + "sub_reseller_id": data.get("sub_reseller_id"), + "agreement_code": agreement_code, + "agreement_name": data.get("agreement_name"), + "sign_date": data.get("sign_date"), + "start_date": data.get("start_date"), + "end_date": data.get("end_date"), + "status": data.get("status", "1"), + "default_discount": data.get("default_discount", 1.0), + "remark": data.get("remark"), + "created_by": user_id, + "created_at": now, + "updated_at": now, + } + await sor.C("distribution_agreements", rec) + return json.dumps({"status": "ok", "data": rec, "message": "创建成功"}) + + +async def update_distribution_agreement(request, params_kw): + """Update a distribution agreement.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = {"id": data["id"], "updated_at": now} + for key in ["sub_reseller_id", "agreement_name", "sign_date", "start_date", + "end_date", "status", "default_discount", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("distribution_agreements", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_distribution_agreement(request, params_kw): + """Delete a distribution agreement and its items.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("distribution_agreement_items", {"agreement_id": data["id"]}) + await sor.D("distribution_agreements", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Distribution Agreement Items APIs +# ============================================================ + +async def create_distribution_agreement_item(request, params_kw): + """Create a distribution agreement product discount item.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = { + "id": getID(), + "agreement_id": data["agreement_id"], + "resellerid": data.get("resellerid", ""), + "prodtypeid": data.get("prodtypeid"), + "productid": data.get("productid"), + "discount": data.get("discount", 1.0), + "min_order_qty": data.get("min_order_qty"), + "sale_price": data.get("sale_price"), + "remark": data.get("remark"), + "created_at": now, + } + await sor.C("distribution_agreement_items", rec) + return json.dumps({"status": "ok", "data": rec, "message": "创建成功"}) + + +async def update_distribution_agreement_item(request, params_kw): + """Update a distribution agreement item.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + rec = {"id": data["id"]} + for key in ["prodtypeid", "productid", "discount", "min_order_qty", + "sale_price", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("distribution_agreement_items", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_distribution_agreement_item(request, params_kw): + """Delete a distribution agreement item.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("distribution_agreement_items", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Sales Ledger APIs +# ============================================================ + +async def create_sales_ledger(request, params_kw): + """Create a sales ledger entry.""" + env = ServerEnv() + user_id = await env.get_user() + resellerid = await env.get_userorgid() + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + quantity = float(data.get("quantity", 0)) + unit_price = float(data.get("unit_price", 0)) + total_amount = quantity * unit_price + + supply_discount = float(data.get("supply_discount", 1.0)) + supply_amount = total_amount * supply_discount + + distribution_discount = float(data.get("distribution_discount", 1.0)) + distribution_amount = total_amount * distribution_discount + + profit_amount = distribution_amount - supply_amount + + rec = { + "id": getID(), + "resellerid": resellerid, + "sub_reseller_id": data.get("sub_reseller_id"), + "supplier_id": data.get("supplier_id"), + "agreement_id": data.get("agreement_id"), + "contract_id": data.get("contract_id"), + "prodtypeid": data.get("prodtypeid"), + "productid": data.get("productid"), + "sale_date": data.get("sale_date"), + "quantity": quantity, + "unit_price": unit_price, + "supply_discount": supply_discount, + "supply_amount": round(supply_amount, 2), + "distribution_discount": distribution_discount, + "distribution_amount": round(distribution_amount, 2), + "profit_amount": round(profit_amount, 2), + "settlement_status": data.get("settlement_status", "0"), + "remark": data.get("remark"), + "created_by": user_id, + "created_at": now, + "updated_at": now, + } + await sor.C("sales_ledger", rec) + return json.dumps({"status": "ok", "data": rec, "message": "记账成功"}) + + +async def update_sales_ledger(request, params_kw): + """Update a sales ledger entry.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + rec = {"id": data["id"], "updated_at": now} + for key in ["sub_reseller_id", "supplier_id", "agreement_id", "contract_id", + "prodtypeid", "productid", "sale_date", "quantity", "unit_price", + "supply_discount", "supply_amount", "distribution_discount", + "distribution_amount", "profit_amount", "settlement_status", "remark"]: + if key in data: + rec[key] = data[key] + await sor.U("sales_ledger", rec) + return json.dumps({"status": "ok", "message": "更新成功"}) + + +async def delete_sales_ledger(request, params_kw): + """Delete a sales ledger entry.""" + data = params_kw + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + await sor.D("sales_ledger", {"id": data["id"]}) + return json.dumps({"status": "ok", "message": "删除成功"}) + + +# ============================================================ +# Discount Calculation API (called by other modules during sales) +# ============================================================ + +async def calculate_sale_amounts(request, params_kw): """ - Calculate and record accounting for a product sale. - - Returns: the created accounting record as dict + Calculate supply and distribution amounts for a sale. + Called by accounting/other modules during product sales. + + Parameters: + resellerid: 分销商机构ID + sub_reseller_id: 二级分销商ID(可选) + supplier_id: 供应商ID(可选) + prodtypeid: 产品分类ID + productid: 产品ID + quantity: 销售数量 + unit_price: 销售单价 + + Returns: supply_discount, supply_amount, distribution_discount, distribution_amount, profit_amount """ - if sale_date is None: - sale_date = datetime.now().strftime("%Y-%m-%d") - + env = ServerEnv() + data = params_kw + resellerid = data.get("resellerid") + sub_reseller_id = data.get("sub_reseller_id") + supplier_id = data.get("supplier_id") + prodtypeid = data.get("prodtypeid") + productid = data.get("productid") + quantity = float(data.get("quantity", 0)) + unit_price = float(data.get("unit_price", 0)) 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 + db, dbname = _get_sor() + async with db.sqlorContext(dbname) as sor: + biz_date = await env.get_business_date(sor) + + # Step 1: Find active supply contract for this supplier + supply_discount = 1.0 + contract_id = None + if supplier_id: + sql_sc = """SELECT id, default_discount FROM supply_contracts +WHERE resellerid = ${resellerid}$ + AND supplier_id = ${supplier_id}$ + AND status = '1' + AND start_date <= ${biz_date}$ + AND (end_date IS NULL OR end_date > ${biz_date}$)""" + recs_sc = await sor.sqlExe(sql_sc, { + "resellerid": resellerid, + "supplier_id": supplier_id, + "biz_date": biz_date + }) + + if recs_sc: + contract_id = recs_sc[0].id + supply_discount = float(recs_sc[0].default_discount) + + # Try to find product-specific discount in contract items + # Priority: exact product > product type > NULL (default) + sql_sci = """SELECT discount FROM supply_contract_items +WHERE contract_id = ${contract_id}$ + AND ((prodtypeid = ${prodtypeid}$ AND productid = ${productid}$) + OR (prodtypeid = ${prodtypeid}$ AND productid IS NULL) + OR (prodtypeid IS NULL AND productid IS NULL)) +ORDER BY + CASE WHEN prodtypeid IS NOT NULL AND productid IS NOT NULL THEN 1 + WHEN prodtypeid IS NOT NULL AND productid IS NULL THEN 2 + ELSE 3 END +LIMIT 1""" + recs_sci = await sor.sqlExe(sql_sci, { + "contract_id": contract_id, + "prodtypeid": prodtypeid, + "productid": productid + }) + if recs_sci: + supply_discount = float(recs_sci[0].discount) + + supply_amount = total_amount * supply_discount + + # Step 2: Find active distribution agreement for this sub-reseller + distribution_discount = 1.0 + agreement_id = None + if sub_reseller_id: + sql_da = """SELECT id, default_discount FROM distribution_agreements +WHERE resellerid = ${resellerid}$ + AND sub_reseller_id = ${sub_reseller_id}$ + AND status = '1' + AND start_date <= ${biz_date}$ + AND (end_date IS NULL OR end_date > ${biz_date}$)""" + recs_da = await sor.sqlExe(sql_da, { + "resellerid": resellerid, + "sub_reseller_id": sub_reseller_id, + "biz_date": biz_date + }) + + if recs_da: + agreement_id = recs_da[0].id + distribution_discount = float(recs_da[0].default_discount) + + # Try to find product-specific discount with priority ordering + sql_dai = """SELECT discount FROM distribution_agreement_items +WHERE agreement_id = ${agreement_id}$ + AND ((prodtypeid = ${prodtypeid}$ AND productid = ${productid}$) + OR (prodtypeid = ${prodtypeid}$ AND productid IS NULL) + OR (prodtypeid IS NULL AND productid IS NULL)) +ORDER BY + CASE WHEN prodtypeid IS NOT NULL AND productid IS NOT NULL THEN 1 + WHEN prodtypeid IS NOT NULL AND productid IS NULL THEN 2 + ELSE 3 END +LIMIT 1""" + recs_dai = await sor.sqlExe(sql_dai, { + "agreement_id": agreement_id, + "prodtypeid": prodtypeid, + "productid": productid + }) + if recs_dai: + distribution_discount = float(recs_dai[0].discount) + + distribution_amount = total_amount * distribution_discount + profit_amount = distribution_amount - supply_amount + + result = { + "contract_id": contract_id, + "agreement_id": agreement_id, + "total_amount": round(total_amount, 2), + "supply_discount": supply_discount, + "supply_amount": round(supply_amount, 2), + "distribution_discount": distribution_discount, + "distribution_amount": round(distribution_amount, 2), + "profit_amount": round(profit_amount, 2), + } + return json.dumps({"status": "ok", "data": result}) + + +# ============================================================ +# Register functions with ServerEnv +# ============================================================ def load_supplychain(): - """Register all functions with ServerEnv.""" + """Register all supplychain 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 + # Supplier + env.create_supplier = create_supplier + env.update_supplier = update_supplier + env.delete_supplier = delete_supplier + # Supply Contract + env.create_supply_contract = create_supply_contract + env.update_supply_contract = update_supply_contract + env.delete_supply_contract = delete_supply_contract + # Supply Contract Items + env.create_supply_contract_item = create_supply_contract_item + env.update_supply_contract_item = update_supply_contract_item + env.delete_supply_contract_item = delete_supply_contract_item + # Sub-Reseller + env.create_sub_reseller = create_sub_reseller + env.update_sub_reseller = update_sub_reseller + env.delete_sub_reseller = delete_sub_reseller + # Distribution Agreement + env.create_distribution_agreement = create_distribution_agreement + env.update_distribution_agreement = update_distribution_agreement + env.delete_distribution_agreement = delete_distribution_agreement + # Distribution Agreement Items + env.create_distribution_agreement_item = create_distribution_agreement_item + env.update_distribution_agreement_item = update_distribution_agreement_item + env.delete_distribution_agreement_item = delete_distribution_agreement_item + # Sales Ledger + env.create_sales_ledger = create_sales_ledger + env.update_sales_ledger = update_sales_ledger + env.delete_sales_ledger = delete_sales_ledger + # Calculation API + env.calculate_sale_amounts = calculate_sale_amounts return True diff --git a/wwwroot/api/distribution_agreement_items_create.dspy b/wwwroot/api/distribution_agreement_items_create.dspy index 6162ce9..1ead3de 100644 --- a/wwwroot/api/distribution_agreement_items_create.dspy +++ b/wwwroot/api/distribution_agreement_items_create.dspy @@ -1,23 +1,9 @@ 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_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_distribution_agreement_items', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_distribution_agreement_items function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/distribution_agreement_items_delete.dspy b/wwwroot/api/distribution_agreement_items_delete.dspy index 442e0b4..44d3eb8 100644 --- a/wwwroot/api/distribution_agreement_items_delete.dspy +++ b/wwwroot/api/distribution_agreement_items_delete.dspy @@ -1,19 +1,9 @@ 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"}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_distribution_agreement_items', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_distribution_agreement_items function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/distribution_agreement_items_update.dspy b/wwwroot/api/distribution_agreement_items_update.dspy index 8bd204e..5023c0d 100644 --- a/wwwroot/api/distribution_agreement_items_update.dspy +++ b/wwwroot/api/distribution_agreement_items_update.dspy @@ -1,27 +1,9 @@ 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_distribution_agreement_items', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_distribution_agreement_items function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/api/distribution_agreements_create.dspy b/wwwroot/api/distribution_agreements_create.dspy index 4d6924d..2bf09a7 100644 --- a/wwwroot/api/distribution_agreements_create.dspy +++ b/wwwroot/api/distribution_agreements_create.dspy @@ -1,28 +1,9 @@ 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 agreement code - if not data.get("agreement_code"): - data["agreement_code"] = f"DA-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" - - config = getConfig(".") - DBPools(config.databases) - async with db.sqlorContext(dbname) as sor: - await sor.C("distribution_agreements", data) - return json.dumps({"status": "ok", "data": data}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_distribution_agreements', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_distribution_agreements function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/distribution_agreements_delete.dspy b/wwwroot/api/distribution_agreements_delete.dspy index b6d8a32..0c9ebe4 100644 --- a/wwwroot/api/distribution_agreements_delete.dspy +++ b/wwwroot/api/distribution_agreements_delete.dspy @@ -1,19 +1,9 @@ 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"}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_distribution_agreements', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_distribution_agreements function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/distribution_agreements_update.dspy b/wwwroot/api/distribution_agreements_update.dspy index 91bff12..8b9bf6c 100644 --- a/wwwroot/api/distribution_agreements_update.dspy +++ b/wwwroot/api/distribution_agreements_update.dspy @@ -1,27 +1,9 @@ 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_distribution_agreements', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_distribution_agreements function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/api/sales_ledger_create.dspy b/wwwroot/api/sales_ledger_create.dspy new file mode 100644 index 0000000..894635d --- /dev/null +++ b/wwwroot/api/sales_ledger_create.dspy @@ -0,0 +1,9 @@ +import json +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_sales_ledger', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_sales_ledger function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/sales_ledger_delete.dspy b/wwwroot/api/sales_ledger_delete.dspy new file mode 100644 index 0000000..f20f037 --- /dev/null +++ b/wwwroot/api/sales_ledger_delete.dspy @@ -0,0 +1,9 @@ +import json +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_sales_ledger', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_sales_ledger function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/sales_ledger_update.dspy b/wwwroot/api/sales_ledger_update.dspy new file mode 100644 index 0000000..d19ae07 --- /dev/null +++ b/wwwroot/api/sales_ledger_update.dspy @@ -0,0 +1,9 @@ +import json +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_sales_ledger', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_sales_ledger function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/api/sub_resellers_create.dspy b/wwwroot/api/sub_resellers_create.dspy new file mode 100644 index 0000000..acdae72 --- /dev/null +++ b/wwwroot/api/sub_resellers_create.dspy @@ -0,0 +1,9 @@ +import json +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_sub_resellers', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_sub_resellers function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/sub_resellers_delete.dspy b/wwwroot/api/sub_resellers_delete.dspy new file mode 100644 index 0000000..3c4ae41 --- /dev/null +++ b/wwwroot/api/sub_resellers_delete.dspy @@ -0,0 +1,9 @@ +import json +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_sub_resellers', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_sub_resellers function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/sub_resellers_update.dspy b/wwwroot/api/sub_resellers_update.dspy new file mode 100644 index 0000000..9d20007 --- /dev/null +++ b/wwwroot/api/sub_resellers_update.dspy @@ -0,0 +1,9 @@ +import json +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_sub_resellers', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_sub_resellers function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/api/suppliers_create.dspy b/wwwroot/api/suppliers_create.dspy index 5dc69c6..28d402c 100644 --- a/wwwroot/api/suppliers_create.dspy +++ b/wwwroot/api/suppliers_create.dspy @@ -1,28 +1,9 @@ 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 supplier code - if not data.get("supplier_code"): - data["supplier_code"] = f"SUP-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" - - config = getConfig(".") - DBPools(config.databases) - async with db.sqlorContext(dbname) as sor: - await sor.C("suppliers", data) - return json.dumps({"status": "ok", "data": data}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_suppliers', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_suppliers function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/suppliers_delete.dspy b/wwwroot/api/suppliers_delete.dspy index a53dc92..f26346e 100644 --- a/wwwroot/api/suppliers_delete.dspy +++ b/wwwroot/api/suppliers_delete.dspy @@ -1,19 +1,9 @@ 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"}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_suppliers', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_suppliers function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/suppliers_update.dspy b/wwwroot/api/suppliers_update.dspy index 0dd227e..342ebc6 100644 --- a/wwwroot/api/suppliers_update.dspy +++ b/wwwroot/api/suppliers_update.dspy @@ -1,27 +1,9 @@ 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_suppliers', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_suppliers function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/api/supply_contract_items_create.dspy b/wwwroot/api/supply_contract_items_create.dspy index 058db72..3dd4d13 100644 --- a/wwwroot/api/supply_contract_items_create.dspy +++ b/wwwroot/api/supply_contract_items_create.dspy @@ -1,23 +1,9 @@ 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_at"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_supply_contract_items', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_supply_contract_items function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/supply_contract_items_delete.dspy b/wwwroot/api/supply_contract_items_delete.dspy index 09ca2ac..5571c53 100644 --- a/wwwroot/api/supply_contract_items_delete.dspy +++ b/wwwroot/api/supply_contract_items_delete.dspy @@ -1,19 +1,9 @@ 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"}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_supply_contract_items', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_supply_contract_items function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/supply_contract_items_update.dspy b/wwwroot/api/supply_contract_items_update.dspy index 1e7812d..2ca3b45 100644 --- a/wwwroot/api/supply_contract_items_update.dspy +++ b/wwwroot/api/supply_contract_items_update.dspy @@ -1,27 +1,9 @@ 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_supply_contract_items', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_supply_contract_items function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/api/supply_contracts_create.dspy b/wwwroot/api/supply_contracts_create.dspy index 66f3392..7668759 100644 --- a/wwwroot/api/supply_contracts_create.dspy +++ b/wwwroot/api/supply_contracts_create.dspy @@ -1,28 +1,9 @@ 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 contract code - if not data.get("contract_code"): - data["contract_code"] = f"SC-{datetime.now().strftime('%Y%m%d')}-{getID()[:4].upper()}" - - config = getConfig(".") - DBPools(config.databases) - async with db.sqlorContext(dbname) as sor: - await sor.C("supply_contracts", data) - return json.dumps({"status": "ok", "data": data}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +create_func = getattr(env, 'create_supply_contracts', None) +if create_func is None: + print(json.dumps({"status": "error", "message": "create_supply_contracts function not found"})) +else: + result = await create_func(request, params_kw) + print(result) diff --git a/wwwroot/api/supply_contracts_delete.dspy b/wwwroot/api/supply_contracts_delete.dspy index c6d3de9..0472fea 100644 --- a/wwwroot/api/supply_contracts_delete.dspy +++ b/wwwroot/api/supply_contracts_delete.dspy @@ -1,19 +1,9 @@ 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"}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +delete_func = getattr(env, 'delete_supply_contracts', None) +if delete_func is None: + print(json.dumps({"status": "error", "message": "delete_supply_contracts function not found"})) +else: + result = await delete_func(request, params_kw) + print(result) diff --git a/wwwroot/api/supply_contracts_update.dspy b/wwwroot/api/supply_contracts_update.dspy index 36a2293..c19b103 100644 --- a/wwwroot/api/supply_contracts_update.dspy +++ b/wwwroot/api/supply_contracts_update.dspy @@ -1,27 +1,9 @@ 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}) +from ahserver.serverenv import ServerEnv +env = ServerEnv() +update_func = getattr(env, 'update_supply_contracts', None) +if update_func is None: + print(json.dumps({"status": "error", "message": "update_supply_contracts function not found"})) +else: + result = await update_func(request, params_kw) + print(result) diff --git a/wwwroot/index.ui b/wwwroot/index.ui index eda0b87..bbb2b88 100644 --- a/wwwroot/index.ui +++ b/wwwroot/index.ui @@ -3,55 +3,31 @@ "options": { "width": "100%", "height": "100%", - "padding": "0", - "bgcolor": "#0B1120" + "padding": "20px" }, "subwidgets": [ { - "widgettype": "HBox", + "widgettype": "Text", "options": { - "width": "100%", - "alignItems": "center", - "padding": "16px 24px", - "marginBottom": "0" - }, - "subwidgets": [ - { - "widgettype": "Title2", - "options": { - "text": "供销链管理", - "color": "#F1F5F9", - "fontWeight": "700" - } - }, - { - "widgettype": "Filler" - }, - { - "widgettype": "Text", - "options": { - "text": "供应商、合同、二级分销商与记账管理", - "fontSize": "14px", - "color": "#64748B" - } - } - ] + "text": "供销链管理", + "fontSize": "24px", + "fontWeight": "bold", + "marginBottom": "20px" + } }, { "widgettype": "ResponsableBox", "options": { - "gap": "12px", - "minWidth": "180px", - "padding": "0 24px 16px 24px" + "gap": "16px", + "minWidth": "250px" }, "subwidgets": [ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", - "padding": "16px 24px", + "bgcolor": "#FFFFFF", + "padding": "20px", "borderRadius": "8px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -61,26 +37,27 @@ "actiontype": "urlwidget", "target": "app.supplychain_content", "options": { - "url": "{{entire_url('suppliers.ui')}}" + "url": "{{entire_url('suppliers_list')}}" }, "mode": "replace" } ], "subwidgets": [ { - "widgettype": "Title5", + "widgettype": "Text", "options": { "text": "供应商管理", - "color": "#F1F5F9", - "fontWeight": "600" + "fontSize": "16px", + "fontWeight": "bold" } }, { "widgettype": "Text", "options": { - "text": "添加和管理供应商信息", + "text": "管理供应商信息、联系方式、银行账户等", "fontSize": "12px", - "color": "#94A3B8" + "color": "#666666", + "marginTop": "8px" } } ] @@ -88,10 +65,9 @@ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", - "padding": "16px 24px", + "bgcolor": "#FFFFFF", + "padding": "20px", "borderRadius": "8px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -101,18 +77,18 @@ "actiontype": "urlwidget", "target": "app.supplychain_content", "options": { - "url": "{{entire_url('supply_contracts.ui')}}" + "url": "{{entire_url('supply_contracts_list')}}" }, "mode": "replace" } ], "subwidgets": [ { - "widgettype": "Title5", + "widgettype": "Text", "options": { "text": "供销合同", - "color": "#F1F5F9", - "fontWeight": "600" + "fontSize": "16px", + "fontWeight": "bold" } }, { @@ -120,7 +96,8 @@ "options": { "text": "管理与供应商的供销合同及产品折扣", "fontSize": "12px", - "color": "#94A3B8" + "color": "#666666", + "marginTop": "8px" } } ] @@ -128,10 +105,9 @@ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", - "padding": "16px 24px", + "bgcolor": "#FFFFFF", + "padding": "20px", "borderRadius": "8px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -141,26 +117,27 @@ "actiontype": "urlwidget", "target": "app.supplychain_content", "options": { - "url": "{{entire_url('sub_distributors.ui')}}" + "url": "{{entire_url('sub_resellers_list')}}" }, "mode": "replace" } ], "subwidgets": [ { - "widgettype": "Title5", + "widgettype": "Text", "options": { "text": "二级分销商", - "color": "#F1F5F9", - "fontWeight": "600" + "fontSize": "16px", + "fontWeight": "bold" } }, { "widgettype": "Text", "options": { - "text": "添加和管理二级分销商", + "text": "管理二级分销商信息", "fontSize": "12px", - "color": "#94A3B8" + "color": "#666666", + "marginTop": "8px" } } ] @@ -168,10 +145,9 @@ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", - "padding": "16px 24px", + "bgcolor": "#FFFFFF", + "padding": "20px", "borderRadius": "8px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -181,18 +157,18 @@ "actiontype": "urlwidget", "target": "app.supplychain_content", "options": { - "url": "{{entire_url('distribution_agreements.ui')}}" + "url": "{{entire_url('distribution_agreements_list')}}" }, "mode": "replace" } ], "subwidgets": [ { - "widgettype": "Title5", + "widgettype": "Text", "options": { "text": "分销协议", - "color": "#F1F5F9", - "fontWeight": "600" + "fontSize": "16px", + "fontWeight": "bold" } }, { @@ -200,7 +176,8 @@ "options": { "text": "管理与二级分销商的分销协议及产品折扣", "fontSize": "12px", - "color": "#94A3B8" + "color": "#666666", + "marginTop": "8px" } } ] @@ -208,10 +185,9 @@ { "widgettype": "VBox", "options": { - "bgcolor": "#1E293B", - "padding": "16px 24px", + "bgcolor": "#FFFFFF", + "padding": "20px", "borderRadius": "8px", - "border": "1px solid #334155", "cursor": "pointer" }, "binds": [ @@ -221,26 +197,27 @@ "actiontype": "urlwidget", "target": "app.supplychain_content", "options": { - "url": "{{entire_url('accounting.ui')}}" + "url": "{{entire_url('sales_ledger_list')}}" }, "mode": "replace" } ], "subwidgets": [ { - "widgettype": "Title5", + "widgettype": "Text", "options": { - "text": "供销记账", - "color": "#F1F5F9", - "fontWeight": "600" + "text": "销售记账", + "fontSize": "16px", + "fontWeight": "bold" } }, { "widgettype": "Text", "options": { - "text": "查看供销关系记账记录和利润统计", + "text": "查看和管理销售记账记录", "fontSize": "12px", - "color": "#94A3B8" + "color": "#666666", + "marginTop": "8px" } } ] @@ -249,11 +226,11 @@ }, { "widgettype": "VBox", - "id": "supplychain_content", - "css": "filler", + "id": "app.supplychain_content", "options": { "width": "100%", - "overflowY": "auto" + "flex": "1", + "marginTop": "20px" } } ] diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui index dd9deb5..3feae7e 100644 --- a/wwwroot/menu.ui +++ b/wwwroot/menu.ui @@ -1,27 +1,28 @@ { "widgettype": "Menu", "options": { + "label": "供销管理", "items": [ { "name": "供应商管理", - "url": "{{entire_url('suppliers.ui')}}" + "url": "{{entire_url('suppliers_list')}}" }, { "name": "供销合同", - "url": "{{entire_url('supply_contracts.ui')}}" + "url": "{{entire_url('supply_contracts_list')}}" }, { "name": "二级分销商", - "url": "{{entire_url('sub_distributors.ui')}}" + "url": "{{entire_url('sub_resellers_list')}}" }, { "name": "分销协议", - "url": "{{entire_url('distribution_agreements.ui')}}" + "url": "{{entire_url('distribution_agreements_list')}}" }, { - "name": "供销记账", - "url": "{{entire_url('accounting.ui')}}" + "name": "销售记账", + "url": "{{entire_url('sales_ledger_list')}}" } ] } -} \ No newline at end of file +}