Initial commit: supplychain module - supplier, sub-reseller, contract, agreement, and sales ledger management
This commit is contained in:
parent
58f427d530
commit
ed9c96d719
329
README.md
329
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 <db_host> -u <db_user> -p sage < mysql.ddl.sql
|
||||
```
|
||||
|
||||
### 3. 生成 DDL 并创建数据库表
|
||||
|
||||
### 3. Generate CRUD UI files
|
||||
```bash
|
||||
cd ~/repos/supplychain/models
|
||||
mysql -h <host> -u <user> -p <dbname> < 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}
|
||||
```
|
||||
|
||||
48
build.sh
48
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."
|
||||
|
||||
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
json/sales_ledger_list.json
Normal file
40
json/sales_ledger_list.json
Normal file
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
36
json/sub_resellers_list.json
Normal file
36
json/sub_resellers_list.json
Normal file
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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')}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
208
models/sales_ledger.json
Normal file
208
models/sales_ledger.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
132
models/sub_resellers.json
Normal file
132
models/sub_resellers.json
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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",
|
||||
|
||||
1
supplychain/__init__.py.bak
Normal file
1
supplychain/__init__.py.bak
Normal file
@ -0,0 +1 @@
|
||||
# supplychain Python package
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
9
wwwroot/api/sales_ledger_create.dspy
Normal file
9
wwwroot/api/sales_ledger_create.dspy
Normal file
@ -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)
|
||||
9
wwwroot/api/sales_ledger_delete.dspy
Normal file
9
wwwroot/api/sales_ledger_delete.dspy
Normal file
@ -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)
|
||||
9
wwwroot/api/sales_ledger_update.dspy
Normal file
9
wwwroot/api/sales_ledger_update.dspy
Normal file
@ -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)
|
||||
9
wwwroot/api/sub_resellers_create.dspy
Normal file
9
wwwroot/api/sub_resellers_create.dspy
Normal file
@ -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)
|
||||
9
wwwroot/api/sub_resellers_delete.dspy
Normal file
9
wwwroot/api/sub_resellers_delete.dspy
Normal file
@ -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)
|
||||
9
wwwroot/api/sub_resellers_update.dspy
Normal file
9
wwwroot/api/sub_resellers_update.dspy
Normal file
@ -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)
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
135
wwwroot/index.ui
135
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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -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')}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user