修改数字

This commit is contained in:
Administrator 2026-06-15 13:47:39 +08:00
commit 17a21c4cb4
206 changed files with 30945 additions and 0 deletions

View File

@ -0,0 +1,5 @@
---
name: 修复BatchPushProducts并生成测试变量
overview: 修复 service/product.go 中 BatchPushProducts 方法的编译错误strconv.Itoa → strconv.Itoa并生成测试用的请求变量UserId=1965254774327533570包含 shop_ids 和 product_ids
---

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

9
.idea/cards.iml generated Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cards.iml" filepath="$PROJECT_DIR$/.idea/cards.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

610
PSI项目说明文档.md Normal file
View File

@ -0,0 +1,610 @@
# PSI 进销存系统 - 项目说明文档
## 一、项目概述
PSI 是一套基于 Go 语言开发的完整进销存管理系统涵盖采购、销售、库存、仓库、物流等核心业务环节支持波次Wave管理、OCR 识别、外部系统对接等企业级功能。
- **项目名称**PSI 进销存系统
- **技术栈**Go 1.25.7 + Gin + GORM + MySQL
- **服务端口**`9090`
- **项目路径**`D:\www\wwwroot\psi`
---
## 二、技术架构
| 组件 | 技术 |
|------|------|
| Web 框架 | [Gin v1.11.0](https://github.com/gin-gonic/gin) |
| ORM | [GORM v1.31.1](https://gorm.io/) + MySQL Driver |
| 认证 | JWTgolang-jwt/jwt/v5 |
| 日志 | Logrus + file-rotatelogs按时间轮转 |
| 搜索引擎 | Elasticsearch v8可选当前已注释 |
| OCR | 本地 OCR 服务(可启动独立进程) |
| Excel 导出 | Excelize v2 |
| 条形码 | boombuler/barcode二维码/条形码生成) |
| 图像处理 | golang.org/x/image |
| 加密 | golang.org/x/crypto |
---
## 三、目录结构
```
psi/
├── main.go # 程序入口
├── config.yaml # 配置文件
├── go.mod / go.sum # Go 依赖管理
├── config/ # 配置加载模块
├── constant/ # 常量定义(日志通道等)
├── controllers/ # HTTP 控制器层处理请求32个
├── middleware/ # 中间件JWT 认证、CORS、API 签名)
├── models/ # 数据模型层
│ ├── request/ # 请求参数结构体
│ ├── response/ # 响应结构体
│ ├── book_info.go # 图书信息模型
│ ├── employee.go # 员工模型
│ └── ... # 其他业务模型共41个表
├── service/ # 业务逻辑层
├── database/ # 数据库连接初始化
├── routes/ # 路由定义
├── utils/ # 工具函数JWT、加密等
├── es/ # Elasticsearch 初始化(可选)
├── ocr/ # OCR 服务
├── template/ # 模板文件
├── fonts/ # 字体文件
├── linux/ # Linux 部署文件
├── runtime/ # 运行时文件(日志等)
├── .idea/ # IDE 配置
└── sql.txt # 数据库补充脚本
```
---
## 四、数据库表关系
### 4.1 表分类统计
| 模块 | 表数量 | 说明 |
|-----|-------|------|
| 基础信息 | 10 | 员工、供应商、仓库、库位、商品、店铺、客户等 |
| 库存管理 | 5 | 库存、库存明细、库存流水、盘库等 |
| 采购管理 | 4 | 采购订单、采购明细、入库单、入库明细 |
| 销售出库 | 7 | 销售订单、销售明细、出库单、出库明细、发货单等 |
| 波次管理 | 3 | 波次主表、波次任务、波次任务明细 |
| 其他 | 12 | 小车、外部任务、打印、分账、统计等 |
| **总计** | **41** | |
### 4.2 核心表结构
#### 1基础信息模块
| 表名 | 说明 | 关键字段 |
|-----|------|---------|
| **employees** | 员工表 | id, username, role, status, expire_time |
| **supplier** | 供应商表 | id, code, name, status |
| **warehouse** | 仓库表 | id, code, name, logistics_id, type, status |
| **location** | 库位表 | id, warehouse_id, code, type, capacity, status |
| **product** | 商品表 | id, category_id, barcode, name, price, status |
| **shop** | 店铺表 | id, mall_id, shop_type, shop_alias_name, status |
| **customer** | 客户表 | - |
| **logistics** | 物流模板表 | - |
#### 2库存模块
| 表名 | 说明 | 关键字段 |
|-----|------|---------|
| **inventory** | 库存汇总表 | id, warehouse_id, product_id, batch_no, quantity, locked_quantity |
| **inventory_log** | 库存流水表 | id, warehouse_id, product_id, location_id, change_type, change_quantity, related_order_no |
#### 3采购模块
| 表名 | 说明 | 关键字段 |
|-----|------|---------|
| **purchase_order** | 采购订单主表 | id, po_no, supplier_id, warehouse_id, total_amount, status |
| **receiving_order** | 入库单主表 | id, receiving_no, purchase_order_id, wave_task_id, warehouse_id, status |
#### 4销售与出库模块
| 表名 | 说明 | 关键字段 |
|-----|------|---------|
| **sales_order** | 销售订单主表 | id, so_no, customer_id, warehouse_id, shop_type, total_amount, status, is_distribution |
| **outbound_order** | 出库单主表 | id, out_no, wave_task_id, warehouse_id, total_quantity, status |
| **shipping_order** | 发货单主表 | id, shipping_no, customer_id, status, shipping_time |
#### 5波次管理模块
| 表名 | 说明 | 关键字段 |
|-----|------|---------|
| **wave_header** | 波次主表 | id, wave_no, direction, type, warehouse_id, related_order_id, status |
| **wave_task** | 波次任务表 | - |
| **wave_task_detail** | 波次任务明细表 | - |
### 4.3 表关系图谱
```
┌─────────────┐
│ employees │
└──────┬──────┘
│ 创建/操作
┌───────────────────────────────────────────────────────────────────┐
│ 核心业务流程 │
├───────────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ supplier │─────▶│purchase_order│─────▶│ receiving │ │
│ │ (供应商) │ │ (采购单) │ │ (入库单) │ │
│ └──────────────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────────────┐ │ │ │
│ │ product │◀──────────┘ │ │
│ │ (商品) │ │ │
│ └──────┬───────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ │ │
│ └─────────────▶│ inventory │◀───────────┘ │
│ │ (库存) │ │
│ └──────┬───────┘ │
│ │ │
│ │ │
│ ┌──────────────┐ │ ┌──────────────┐ │
│ │ customer │ │ │ sales │ │
│ │ (客户) │◀───────────┴───────────▶│ (销售单) │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ ┌──────────────┐ ┌──────────────┐ │ │
│ │ shop │ │ outbound │◀───────┘ │
│ │ (店铺) │◀────▶│ (出库单) │ │
│ └──────────────┘ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ shipping │ │
│ │ (发货单) │ │
│ └──────────────┘ │
└───────────────────────────────────────────────────────────────────┘
┌──────────────┐
│ wave_header │
│ (波次) │
└──────┬───────┘
┌──────────────┐
│ wave_task │
│ (波次任务) │
└──────────────┘
┌──────────────┐
│ car │
│ (小车) │
└──────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 基础信息层 │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ warehouse│ │ location │ │logistics │ │
│ │ (仓库) │───▶│ (库位) │ │(物流模板)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 4.4 关键表关系详细说明
#### 采购入库流程关系
```
供应商(supplier) → 采购订单(purchase_order)
波次(wave_header)
波次任务(wave_task)
入库单(receiving_order)
库存增加(inventory + inventory_log)
```
- **purchase_order.supplier_id** → supplier.id
- **purchase_order.warehouse_id** → warehouse.id
- **receiving_order.purchase_order_id** → purchase_order.id
- **receiving_order.wave_task_id** → wave_task.id
- **inventory_log.related_order_no** → purchase_order.po_no 或 receiving_order.receiving_no
#### 销售出库流程关系
```
客户(customer) → 销售订单(sales_order)
波次(wave_header)
波次任务(wave_task)
出库单(outbound_order)
库存减少(inventory + inventory_log)
发货单(shipping_order)
```
- **sales_order.customer_id** → customer.id
- **sales_order.warehouse_id** → warehouse.id
- **sales_order.sales_person_id** → shop.id
- **outbound_order.wave_task_id** → wave_task.id
- **outbound_order.warehouse_id** → warehouse.id
- **inventory_log.related_order_no** → sales_order.so_no 或 outbound_order.out_no
#### 库存管理关系
```
warehouse(仓库) ──┐
├─→ inventory(库存) ←─ product(商品)
location(库位) ──┘ ↑
└─ inventory_log(库存流水)
```
- **inventory.warehouse_id** → warehouse.id
- **inventory.product_id** → product.id
- **inventory_log.warehouse_id** → warehouse.id
- **inventory_log.location_id** → location.id
- **inventory_log.product_id** → product.id
- **location.warehouse_id** → warehouse.id
---
## 五、核心业务模块
### 5.1 认证与权限
| 功能 | 说明 |
|------|------|
| JWT 认证 | 基于 Bearer Token有效期 24 小时 |
| 角色权限 | 普通员工role≠255/ 管理员role=255 |
| API 签名 | MD5 签名验证防篡改app_key + app_secret |
| 账号过期 | 支持设置员工账号过期时间 |
### 5.2 员工管理(管理员接口)
- 员工列表 / 新增 / 修改密码 / 设置级别
- 设置账号过期时间 / 查看过期状态
- PDA 配置(用于手持设备)
- 员工操作日志
### 5.3 供应商管理
- 供应商增删改查
- 供应商详情查看
### 5.4 仓库与库位管理
- 仓库增删改查
- 库位增删改查
- **库位批量生成**(按规则批量创建库位编号)
- 库位同步外部系统
### 5.5 商品管理
- 商品列表(支持分页、筛选)
- 商品新增 / 修改(支持图片上传)
- **商品售价批量修改**(公开接口,支持外部调用)
- 商品导出到 Excel
- 商品库存查询(按库位/仓库聚合)
- 商品日志(记录价格/信息变更,支持审核流程)
- 图书信息获取(对接外部图书 API
- 条形码 / 二维码生成
### 5.6 采购流程(波次模式)
```
创建采购单 → 生成采购波次 → 释放波次(生成波次任务)
PDA 扫码收货
提交入库单 → 库存增加
```
- `POST /api/purchase-order/create-with-wave` — 创建采购单并生成波次
- `POST /api/wave/release` — 释放波次,生成波次任务明细
- `POST /api/receiving/bind-wave` — 绑定波次,创建入库单
- `POST /api/receiving/submit` — 提交入库,更新库存
- 支持**小车Car**容量管理,按车容量分配波次任务
### 5.7 销售与出库流程
```
创建销售单 → 生成出库波次 → 释放出库波次
PDA 拣货/出库
提交出库单 → 库存减少
创建发货单 → 填写物流信息
```
- `POST /api/sales-order/create` — 创建销售订单(公开接口)
- `POST /api/outbound-order/create` — 创建出库单
- `POST /api/wave/outbound/create` — 生成出库波次
- `POST /api/wave/outbound/release` — 释放出库波次
- `POST /api/outbound/bind-wave` — 绑定出库波次
- `POST /api/outbound/submit` — 提交出库
- `POST /api/shipping-order/create` — 创建发货单
- `POST /api/shipping-order/update` — 更新物流信息
- `POST /api/sales-order/cancel` — 取消销售单
- `POST /api/wave/outbound/cancel` — 取消出库波次
### 5.8 库存管理
- 库存列表(按商品/库位/仓库维度)
- **库存按商品聚合列表**(同商品不同库位合并显示)
- 库存明细(跟踪每笔出入库记录)
- 库存变动日志
- **盘库调整**(库存盘点,支持盘盈/盘亏调整)
- **盘库退货**(将盘点差异商品退回)
- 库存统计
### 5.9 波次任务管理
- 波次任务列表(按采购/出库类型)
- 波次任务详情
- 波次任务进度跟踪
### 5.10 店铺与外部对接
- 店铺管理(增删改查)
- **外部任务OutTask**:与外部系统同步任务状态
- 按店铺查询外部任务
- 外部任务日志
- 同步商品到外部系统(`external_api.sync_product_url`
- 同步任务到外部系统(`external_api.sync_task_url`
### 5.11 物流管理
- 物流模板增删改查
- 物流模板详情
### 5.12 分拣设置
- 获取分拣配置
- 保存分拣配置(影响波次任务分配逻辑)
### 5.13 仪表盘统计
- 仪表盘概览统计(`/api/dashboard/statist`
- 各仓库库存统计(`/api/dashboard/warehouse`
- 订单统计(`/api/dashboard/order`
### 5.14 OCR 识别
- `POST /api/ocr` — 图片文字识别(公开接口)
- 可启动本地 OCR 服务进程(`OCRService.exe`
---
## 六、API 接口分类
### 公开接口(无需认证)
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/login/:type` | POST | 员工登录 |
| `/api/employee/reg` | POST | 员工注册 |
| `/api/ocr` | POST | OCR 文字识别 |
| `/api/sales-order/create` | POST | 创建销售单 |
| `/api/product/updatePrice` | POST | 批量修改商品售价 |
| `/api/product/getProductInventory` | GET | 查询商品库存 |
| `/api/employee/check_code` | GET | 校验员工码 |
| `/api/employee/clear_code` | POST | 清除员工码 |
| `/api/out-task-log/update` | POST | 更新外部任务日志 |
### 签名接口(需要 API 签名,无需 JWT
| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/location/sync-locations` | POST | 同步库位到外部系统 |
| `/api/goods/sync-batch` | POST | 批量同步商品到外部系统 |
### 需认证接口JWT + API 签名)
大部分业务接口需要 `Authorization: Bearer <token>` 请求头,以及 API 签名验证。
### 管理员接口(需要 role=255
员工管理、用户类型管理的完整 CRUD。
---
## 七、配置说明config.yaml
```yaml
server:
port: "9090" # 服务监听端口
host: "http://192.168.101.213:9090/" # 服务外部访问地址
database:
host: localhost
port: "3306"
user: root
password: root
name: psi # 数据库名
encrypt_key: "..." # 敏感字段加密密钥AES
jwt:
secret: "..." # JWT 签名密钥
expire_hours: 24 # Token 有效期
api_sign:
app_key: "psi"
app_secret: "psi_api_sign_secret" # API 签名密钥
timestamp_tolerance: 300 # 时间戳容差(秒)
log:
max_age: 600 # 日志最大保留时间(秒)
rotate_time: 600 # 日志轮转时间间隔(秒)
root_path: "./runtime/logs"
channel: # 各日志通道文件路径
sql: "/sql/err.log"
work: "/work/err.log"
request: "/request/err.log"
es: "/es/err.log"
redis: "/redis/err.log"
ocr:
service_url: "http://127.0.0.1:35569/ocr"
exe_url: "./ocr/OCRService.exe" # OCR 可执行文件路径
external_api:
sync_product_url: "http://192.168.101.127:8080/zhishu/filterSet/save"
sync_task_url: "http://192.168.101.156:8080/task/create"
sync_task_body_url: "http://192.168.101.156:8080/task/setTaskBody"
timeout: 30
```
> **注意**Elasticsearch 配置已注释ES 同步功能当前未启用。
---
## 八、数据库模型一览
| 模型文件 | 说明 |
|----------|------|
| `employee.go` | 员工/用户信息 |
| `employee_level.go` | 员工级别配置 |
| `employee_level_log.go` | 员工级别变更日志 |
| `employee_settings.go` | 员工 PDA 配置 |
| `supplier.go` | 供应商 |
| `warehouse.go` | 仓库 |
| `location.go` | 库位 |
| `product.go` | 商品 |
| `product_category.go` | 商品分类 |
| `product_unit_conversion.go` | 商品单位换算 |
| `product_serial.go` | 商品序列号 |
| `product_log.go` | 商品变更日志(审核流) |
| `purchase_order.go` | 采购订单 |
| `purchase_order_item.go` | 采购订单明细 |
| `receiving_order.go` | 入库单 |
| `receiving_order_item.go` | 入库单明细 |
| `sales_order.go` | 销售订单 |
| `sales_order_item.go` | 销售订单明细 |
| `outbound_order.go` | 出库单 |
| `outbound_order_item.go` | 出库单明细 |
| `shipping_order.go` | 发货单 |
| `inventory.go` | 库存 |
| `inventory_detail.go` | 库存明细 |
| `inventory_log.go` | 库存变动日志 |
| `stock_check.go` | 盘库单 |
| `stock_check_item.go` | 盘库明细 |
| `wave_header.go` | 波次头 |
| `wave_task.go` | 波次任务 |
| `wave_task_detail.go` | 波次任务明细 |
| `car.go` | 小车AGV/搬运车) |
| `car_shop.go` | 小车与店铺关联 |
| `shop.go` | 店铺 |
| `logistics.go` | 物流模板 |
| `out_task.go` | 外部任务 |
| `out_task_log.go` | 外部任务日志 |
| `print_log.go` | 打印日志 |
| `print_task.go` | 打印任务 |
| `sorting_settings.go` | 分拣设置 |
| `statist.go` | 统计数据 |
| `customer.go` | 客户 |
| `user_type.go` | 用户类型 |
| `book_info.go` | 图书信息(对接外部图书 API |
| `split_account_config.go` | 分账配置 |
| `split_account_deduction_log.go` | 分账扣钱日志 |
| `outbound_order_location_log.go` | 出库单库位变更记录 |
---
## 九、启动与运行
### 本地开发
```bash
# 进入项目目录
cd D:\www\wwwroot\psi
# 安装依赖
go mod download
# 运行项目
go run main.go
```
服务启动后监听 `http://localhost:9090`
### 配置说明
1. 确保 MySQL 服务运行,并创建 `psi` 数据库
2. 根据实际的 MySQL 账号密码修改 `config.yaml`
3. 如需启用 OCR确保 `ocr/OCRService.exe` 存在
4. 如需启用 Elasticsearch取消 `main.go` 中 ES 初始化代码的注释,并配置 `config.yaml` 中的 `es` 节点
5. 执行 `sql.txt` 中的补充 SQL 脚本(分账配置、出库单库位变更记录等)
---
## 十、中间件说明
| 中间件文件 | 功能 |
|-----------|------|
| `middleware/cors.go` | 跨域资源共享CORS处理 |
| `middleware/auth.go` | JWT 认证 + 员工状态校验 + 租户数据库连接 |
| `middleware/sign.go` | API 签名验证(防篡改、防重放攻击) |
**请求头要求(需认证接口)**
```
Authorization: Bearer <jwt_token>
X-App-Key: psi
X-Timestamp: <unix_timestamp>
X-Sign: <md5_sign>
```
签名计算方式:`md5(app_key + app_secret + timestamp)`
---
## 十一、外部系统集成
系统支持与以下外部系统对接:
| 对接功能 | 配置项 | 说明 |
|---------|--------|------|
| 商品同步 | `sync_product_url` | 将商品信息推送到外部系统 |
| 任务创建 | `sync_task_url` | 在外部系统创建任务 |
| 任务内容同步 | `sync_task_body_url` | 同步任务详细信息到外部系统 |
| 库位同步 | `/api/location/sync-locations` | 将库位信息推送到外部系统 |
| 商品批量同步 | `/api/goods/sync-batch` | 批量推送商品到外部系统 |
---
## 十二、技术架构亮点
| 技术特性 | 说明 |
|---------|------|
| **GORM ORM** | 强大的数据库操作,自动关联查询 |
| **JWT 认证** | 无状态认证,有效期 24 小时 |
| **API 签名验证** | MD5 签名防篡改、防重放攻击 |
| **Elasticsearch** | 可选的搜索引擎集成(代码已注释) |
| **OCR 识别** | 本地 OCR 服务进程,支持图片文字识别 |
| **Excel 处理** | Excelize 库,支持数据导入导出 |
| **条码生成** | boombuler/barcode支持二维码/条形码 |
| **日志轮转** | file-rotatelogs按时间轮转日志 |
| **软删除** | 所有表都支持 IsDel 逻辑删除标记 |
| **时间戳** | 使用 Unix 时间戳(秒)而非 datetime |
---
## 十三、待完善功能(根据代码注释)
- ✅ Elasticsearch 同步功能(代码已注释,可启用)
- ✅ OCR 服务(代码已注释,可启用)
- 外部系统对接 URL 已在配置中,需确认外部系统接口可用性
- `main.go` 中 ES 和 OCR 的初始化代码已注释,按需启用
---
*文档生成时间2026-06-05*
*项目路径D:\www\wwwroot\psi*

61
config.yaml Normal file
View File

@ -0,0 +1,61 @@
server:
port: "9090"
# host: "https://psi.api.buzhiyushu.cn/"
host: "http://192.168.101.213:9090/"
database:
host: 175.27.224.66
port: "3306"
user: root
password: 5e07c0eec1770c94
name: psi
encrypt_key: "0123456789abcdef0123456789abcdef"
task_database:
host: nj-cynosdbmysql-grp-1v6vxn5f.sql.tencentcdb.com
port: "26247"
user: root
password: Long6166@@
name: task
jwt:
secret: "0123456789abcdef0123456789abcdef"
expire_hours: 24
api_sign:
app_key: "psi"
app_secret: "psi_api_sign_secret"
client_id: "psi"
sign_method: "md5"
timestamp_tolerance: 300
log:
max_age: 600
rotate_time: 600
root_path: "./runtime/logs"
channel:
sql: "/sql/err.log"
work: "/work/err.log"
request: "/request/err.log"
es: "/es/err.log"
redis: "/redis/err.log"
es:
host: "http://36.212.12.92:9527"
index: "books-from-mysql-v2"
username: "elastic"
password: "+Tz5qR_KushZ-bPgZ_H-"
ocr:
service_url: "http://127.0.0.1:35569/ocr"
exe_url: "./ocr/OCRService.exe"
external_api:
# sync_product_url: "http://192.168.101.127:8080/zhishu/filterSet/save"
sync_product_url: "https://api.buzhiyushu.cn/zhishu/filterSet/save"
es_update_book_url: "https://book.center.yushutx.com/api/es/updateBookFieldsByISBN"
# sync_task_url: "http://192.168.101.156:8080/task/create"
sync_task_url: "http://36.212.7.246:8283/task/create"
# sync_task_body_url: "http://192.168.101.156:8080/task/setTaskBody"
sync_task_body_url: "http://36.212.7.246:8283/task/setTaskBody"
timeout: 30

106
config/config.go Normal file
View File

@ -0,0 +1,106 @@
package config
import (
"fmt"
"log"
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Server ServerConfig `yaml:"server"`
Database DatabaseConfig `yaml:"database"`
TaskDatabase DatabaseConfig `yaml:"task_database"`
JWT JWTConfig `yaml:"jwt"`
APISign APISignConfig `yaml:"api_sign"`
Log LogConfig `yaml:"log"`
ES ESConfig `yaml:"es"`
OCR OCRConfig `yaml:"ocr"`
ExternalAPI ExternalAPIConfig `yaml:"external_api"`
}
type ServerConfig struct {
Port string `yaml:"port"`
Host string `yaml:"host"`
}
type DatabaseConfig struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Name string `yaml:"name"`
EncryptKey string `yaml:"encrypt_key"`
}
type JWTConfig struct {
Secret string `yaml:"secret"`
ExpireHours int `yaml:"expire_hours"`
}
type APISignConfig struct {
AppKey string `yaml:"app_key"`
AppSecret string `yaml:"app_secret"`
SignMethod string `yaml:"sign_method"`
TimestampTolerance int `yaml:"timestamp_tolerance"`
ClientId string `yaml:"client_id"`
}
type LogConfig struct {
MaxAge int `yaml:"max_age"`
RotateTime int `yaml:"rotate_time"`
RootPath string `yaml:"root_path"`
Channel map[string]string `yaml:"channel"`
}
type ESConfig struct {
Host string `yaml:"host"`
Index string `yaml:"index"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
type OCRConfig struct {
ServiceUrl string `yaml:"service_url"`
ExeUrl string `yaml:"exe_url"`
}
type ExternalAPIConfig struct {
SyncProductURL string `yaml:"sync_product_url"`
SyncTaskURL string `yaml:"sync_task_url"`
SyncTaskBodyURL string `yaml:"sync_task_body_url"`
ESUpdateBookURL string `yaml:"es_update_book_url"`
Timeout int `yaml:"timeout"`
}
var AppConfig *Config
func Init() {
configPath := "config.yaml"
data, err := os.ReadFile(configPath)
if err != nil {
log.Fatalf("读取配置文件失败: %v", err)
}
AppConfig = &Config{}
err = yaml.Unmarshal(data, AppConfig)
if err != nil {
log.Fatalf("解析配置文件失败: %v", err)
}
log.Println("配置文件加载成功")
}
// GetDSN 返回MySQL连接字符串
func (c *Config) GetDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=30s&interpolateParams=true&multiStatements=true&allowNativePasswords=true&checkConnLiveness=true",
c.Database.User, c.Database.Password, c.Database.Host, c.Database.Port, c.Database.Name)
}
// GetTaskDSN 返回任务库MySQL连接字符串腾讯云MySQL
func (c *Config) GetTaskDSN() string {
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=30s&allowNativePasswords=true",
c.TaskDatabase.User, c.TaskDatabase.Password, c.TaskDatabase.Host, c.TaskDatabase.Port, c.TaskDatabase.Name)
}

120
constant/constant.go Normal file
View File

@ -0,0 +1,120 @@
package constant
const MaxTaskQuantity int = 200 // 单次任务最大数量
// 响应状态码
const (
OAUTH = 401 // 未授权
ERROR = 500 // 服务器错误
SUCCESS = 200 // 成功
VALIDATE = 204
)
// 波次类型
const (
WaveNormal = 1 // 普通
WaveNUrgent = 2 // 紧急
WaveNBatch = 3 // 批量
)
// 波次状态
const (
WaveStatusCreated = 1 // 已创建
WaveStatusReleased = 2 // 已下发
WaveStatusPicking = 3 // 拣货中
WaveStatusCompleted = 4 // 已完成
WaveStatusCancelled = 5 // 已取消
)
// 入出库方向
const (
DirectionInbound = 1 // 入库
DirectionOutbound = 2 // 出库
)
// 任务类型
const (
TaskTypePicking = 1 // 拣货任务
TaskTypePutaway = 4 // 上架任务
)
// 库存变更类型
const (
InventoryChangeInbound = 1 // 入库
InventoryChangeOutbound = 2 // 出库
InventoryChangeTransfer = 3 // 移库
InventoryChangeAdjustment = 4 // 盘点调整
InventoryChangeLock = 5 // 锁定库存
InventoryChangeUnlock = 6 // 解锁库存
)
// 订单类型
const (
OrderTypePurchase = "purchase" // 采购订单
OrderTypeReceiving = "receiving" // 入库订单
OrderTypeSales = "sales" // 销售订单
OrderTypeStockCheck = "stock_check" // 盘库单
)
// 采购订单状态 (PurchaseOrder.Status)
const (
PurchaseStatusDraft = 1 // 草稿
PurchaseStatusSubmitted = 2 // 已提交
PurchaseStatusApproved = 3 // 已审核
PurchaseStatusPartialReceived = 4 // 部分收货
PurchaseStatusReceived = 5 // 已收货
PurchaseStatusCancelled = 6 // 已取消
)
// 入库单状态 (ReceivingOrder.Status)
const (
ReceivingStatusPending = 1 // 待收货
ReceivingStatusChecking = 2 // 验收中
ReceivingStatusCompleted = 3 // 已完成
ReceivingStatusCancelled = 4 // 已取消
)
// 销售订单状态 (SalesOrder.Status)
const (
SalesStatusDraft = 1 // 草稿
SalesStatusConfirmed = 2 // 已确认
SalesStatusAllocated = 3 // 已分配库存
SalesStatusPicking = 4 // 拣货完成
SalesStatusShipped = 5 // 已发货
SalesStatusCancelled = 6 // 已取消
)
// 出库单状态 (OutboundOrder.Status)
const (
OutboundStatusCreated = 1 // 已创建
OutboundStatusPicking = 2 // 拣货中
OutboundStatusCompleted = 3 // 已完成
OutboundStatusCancelled = 4 // 已取消
OutboundStatusShipping = 5 // 发货中
OutboundStatusShipped = 6 // 已发货
)
// 发货单状态 (ShippingOrder.Status)
const (
ShippingStatusPending = 1 // 待发货
ShippingStatusShipped = 2 // 已发货
ShippingStatusSigned = 3 // 已签收
ShippingStatusCancelled = 4 // 已取消
)
// 库存盘点状态
const (
InventoryCheckStatusDraft = 1 // 待盘点
InventoryCheckStatusInProgress = 2 // 盘点中
InventoryCheckStatusCompleted = 3 // 已完成
InventoryCheckStatusCancelled = 4 // 已取消
)
// 日志渠道
const (
LoggerChannelSql = "sql" // SQL日志
LoggerChannelWork = "work" // 业务日志
LoggerChannelRequest = "request" // 请求日志
LoggerChannelEs = "es" // ES日志
LoggerChannelRedis = "redis" // Redis日志
)

33
controllers/barcode.go Normal file
View File

@ -0,0 +1,33 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
systemReq "psi/models/request"
"psi/service"
"psi/utils"
)
type BarcodeApi struct{}
var barcodeService = service.BarcodeService{}
func (r *BarcodeApi) GenerateBarcode(c *gin.Context) {
var req systemReq.BarcodeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "条形码生成请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := barcodeService.GenerateBarcode(req.Content)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "条形码生成异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}

128
controllers/book.go Normal file
View File

@ -0,0 +1,128 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type BookApi struct{}
var bookService = service.BookService{}
// GetBookInfo 获取图书信息
func (r *BookApi) GetBookInfo(c *gin.Context) {
var req systemReq.BookRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取图书信息请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := bookService.GetBookInfo(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取图书信息异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetSuitBook 获取套装书
func (r *BookApi) GetSuitBook(c *gin.Context) {
var req systemReq.BookRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取套装书请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := bookService.GetSuitBook(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取套装书异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetNoIsbnBook 获取无书号书
func (r *BookApi) GetNoIsbnBook(c *gin.Context) {
var req systemReq.GetNoIsbnBookRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取无书号书请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := bookService.GetNoIsbnBook(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取无书号书异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
"msg": "查询成功",
})
}
// GetProCode 获取商品编码
func (r *BookApi) GetProCode(c *gin.Context) {
var req systemReq.GetCodeRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取商品编码请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := bookService.GetProCode(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取商品编码异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// SyncBook 同步书籍信息
func (r *BookApi) SyncBook(c *gin.Context) {
var req systemReq.AddBookRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "同步书籍信息请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.LiveImage) == 0 {
image, err := parseImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.LiveImage = image
}
bookID, result, err := bookService.SyncBook(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "同步书籍信息异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": gin.H{
"id": bookID,
"isbn": result,
},
})
}

View File

@ -0,0 +1,38 @@
package controllers
import (
"net/http"
"github.com/gin-gonic/gin"
"psi/models/request"
"psi/service"
"psi/utils"
)
type CancelLogisticsApi struct{}
var cancelLogisticsService = service.CancelLogisticsService{}
// CancelLogistics 取消物流单号
func (r *CancelLogisticsApi) CancelLogistics(c *gin.Context) {
var req request.CancelLogisticsRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusOK, gin.H{
"code": "500",
"msg": "参数错误: " + err.Error(),
})
return
}
resp, err := cancelLogisticsService.CancelLogistics(req.UserID, req.LogisticsNo)
if err != nil {
utils.ErrorLog("cancel_logistics", nil) // simple log
c.JSON(http.StatusOK, gin.H{
"code": "500",
"msg": err.Error(),
})
return
}
c.JSON(http.StatusOK, resp)
}

161
controllers/car.go Normal file
View File

@ -0,0 +1,161 @@
package controllers
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type CarApi struct{}
var carService = service.CarService{}
func (r *CarApi) GetCarList(c *gin.Context) {
var req systemReq.QueryCarRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "查询小车列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
list, total, err := carService.GetCarList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询小车列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"list": list,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
"msg": "查询成功",
})
}
func (r *CarApi) GetCarDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取小车详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取小车详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
car, err := carService.GetCarByID(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询小车详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(car, "查询成功", c)
}
func (r *CarApi) CreateCar(c *gin.Context) {
var req systemReq.CreateCarRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建小车请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.ShopInfo) == 0 {
info, err := parseInfo(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.ShopInfo = info
}
userInfo := utils.GetUserInfo(c)
id, err := carService.CreateCar(req, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建小车异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
func (r *CarApi) UpdateCar(c *gin.Context) {
var req systemReq.UpdateCarRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新小车请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.ShopInfo) == 0 {
info, err := parseInfo(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.ShopInfo = info
}
userInfo := utils.GetUserInfo(c)
if err := carService.UpdateCar(req, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新小车异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
func (r *CarApi) DeleteCar(c *gin.Context) {
var req systemReq.DeleteCarRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除小车请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := carService.DeleteCar(req.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除小车异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
func parseInfo(c *gin.Context) ([]map[string]interface{}, error) {
var info []map[string]interface{}
for i := 0; i < 100; i++ {
infoStr := c.PostForm(fmt.Sprintf("shop_info[%d]", i))
if infoStr == "" {
break
}
var parsedData map[string]interface{}
if err := json.Unmarshal([]byte(infoStr), &parsedData); err != nil {
return nil, fmt.Errorf("第%d个shop_info参数JSON解析失败: %v", i, err)
}
info = append(info, parsedData)
}
return info, nil
}

120
controllers/config.go Normal file
View File

@ -0,0 +1,120 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type ConfigApi struct{}
var configService = service.ConfigService{}
// GetConfigList 获取配置列表
func (r *ConfigApi) GetConfigList(c *gin.Context) {
var req systemReq.GetConfigListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "配置列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := configService.GetConfigList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "配置列表异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetConfigDetail 获取配置详情
func (r *ConfigApi) GetConfigDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取配置详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取配置详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
result, err := configService.GetConfigDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "配置详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// CreateConfig 创建配置
func (r *ConfigApi) CreateConfig(c *gin.Context) {
var req systemReq.AddConfigRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建配置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
id, err := configService.CreateConfig(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建配置异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
// UpdateConfig 更新配置
func (r *ConfigApi) UpdateConfig(c *gin.Context) {
var req systemReq.UpdateConfigRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新配置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := configService.UpdateConfig(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新配置异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// DeleteConfig 删除配置
func (r *ConfigApi) DeleteConfig(c *gin.Context) {
var req systemReq.DeleteConfigRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除配置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := configService.DeleteConfig(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除配置异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}

308
controllers/employee.go Normal file
View File

@ -0,0 +1,308 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/models"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type EmployeeApi struct{}
var employeeService = service.EmployeeService{}
// Login 登录
// @param type: 255-admin, 128-employee
func (r *EmployeeApi) Login(c *gin.Context) {
userType := c.Param("type")
var req systemReq.LoginRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "登录请求异常", "参数错误: "+err.Error(), c, err)
return
}
response, err := employeeService.Login(req, userType, c)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "登录异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithDetailed(response, "登录成功", c)
}
// GetCurrentUser 获取当前用户信息
func (r *EmployeeApi) GetCurrentUser(c *gin.Context) {
employee, exists := c.Get("employee")
if !exists {
systemRes.NoAuth("未登录", c)
return
}
currentEmployee := employee.(models.Employee)
employeeInfo, err := employeeService.GetCurrentUser(currentEmployee.ID)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "获取当前用户信息异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithDetailed(gin.H{
"type": "admin",
"id": employeeInfo.ID,
"employee_id_str": employeeInfo.EmployeeIDStr,
"username": employeeInfo.Username,
"name": employeeInfo.Name,
"role": employeeInfo.Role,
"points": employeeInfo.Score,
"status": employeeInfo.Status,
}, "获取成功", c)
}
// Logout 退出登录
func (r *EmployeeApi) Logout(c *gin.Context) {
systemRes.OkWithMessage("退出成功", c)
}
// GetEmployeeList 员工列表
func (r *EmployeeApi) GetEmployeeList(c *gin.Context) {
var req systemReq.GetEmployeeListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "员工列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := employeeService.GetEmployeeList(req, userInfo.ID, userInfo.AboutID)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "员工列表异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// AddEmployee 添加员工/租户注册
func (r *EmployeeApi) AddEmployee(c *gin.Context) {
var req systemReq.AddEmployeeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "添加员工请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := employeeService.AddEmployee(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "添加员工异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "添加成功",
"data": result,
})
}
// UpdatePasswordEmployee 修改员工密码
func (r *EmployeeApi) UpdatePasswordEmployee(c *gin.Context) {
var req systemReq.UpdatePasswordEmployeeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "修改员工密码请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := employeeService.UpdatePasswordEmployee(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "修改员工密码异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithMessage("修改成功", c)
}
// UpdateExpireTimeEmployee 修改员工过期时间
func (r *EmployeeApi) UpdateExpireTimeEmployee(c *gin.Context) {
var req systemReq.UpdateExpireTimeEmployeeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "修改员工过期时间请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := employeeService.UpdateExpireTimeEmployee(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "修改员工过期时间异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithMessage("修改成功", c)
}
// CheckExpireTimeEmployee 校验员工过期时间
func (r *EmployeeApi) CheckExpireTimeEmployee(c *gin.Context) {
userInfo := utils.GetUserInfo(c)
err := employeeService.CheckExpireTimeEmployee(userInfo.ID)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "校验员工过期时间异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithMessage("操作成功", c)
}
// CheckCodeEmployee 校验员工机械码
func (r *EmployeeApi) CheckCodeEmployee(c *gin.Context) {
var req systemReq.CheckCodeEmployeeRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "校验员工机械码请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
code, err := employeeService.CheckCodeEmployee(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "校验员工机械码异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithDetailed(map[string]interface{}{
"code": code,
}, "查询成功", c)
}
// ClearCodeEmployee 清除员工机械码
func (r *EmployeeApi) ClearCodeEmployee(c *gin.Context) {
var req systemReq.ClearCodeEmployeeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "清除员工机械码请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := employeeService.ClearCodeEmployee(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "清除员工机械码异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithMessage("修改成功", c)
}
// GetLevelConfigList 获取员工等级配置列表
func (r *EmployeeApi) GetLevelConfigList(c *gin.Context) {
levelStr := c.Query("level")
var levelInt int8
if levelStr != "" {
fmt.Sscanf(levelStr, "%d", &levelInt)
}
result := service.GetLevelConfig(levelInt)
if result == nil {
systemRes.FailWithMessage("无效的等级配置", c)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// SetEmployeeLevel 设置员工等级
func (r *EmployeeApi) SetEmployeeLevel(c *gin.Context) {
var req systemReq.SetEmployeeLevelRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "设置员工等级请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := employeeService.SetEmployeeLevel(req, userInfo.ID, userInfo.Username)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "设置员工等级异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithMessage("操作成功", c)
}
// GetEmployeeSettings 获取员工设置
func (r *EmployeeApi) GetEmployeeSettings(c *gin.Context) {
var req systemReq.GetEmployeeSettingsRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取员工设置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result := employeeService.GetEmployeeSettings(req)
systemRes.OkWithDetailed(result, "查询成功", c)
}
// SaveEmployeeSettings 保存员工设置
func (r *EmployeeApi) SaveEmployeeSettings(c *gin.Context) {
var req systemReq.SaveEmployeeSettingsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "保存员工设置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := employeeService.SaveEmployeeSettings(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存员工设置异常", err, c, req)
return
}
systemRes.OkWithMessage("保存成功", c)
}

View File

@ -0,0 +1,99 @@
package controllers
import (
"fmt"
"io"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"psi/constant"
"psi/service"
"psi/utils"
)
var goodsImportService = &service.GoodsImportService{}
// GoodsImportApi 商品导入API独立模块
type GoodsImportApi struct{}
// ImportFromExcel 从Excel文件导入商品
// POST /api/goods/import-from-excel
// multipart/form-data: file (Excel), user_id, warehouse_id
func (r *GoodsImportApi) ImportFromExcel(c *gin.Context) {
// 获取 user_id
userIDStr := c.PostForm("user_id")
if userIDStr == "" {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品导入-缺少user_id", fmt.Errorf("user_id不能为空"), c, nil)
return
}
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil || userID <= 0 {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品导入-user_id格式错误", err, c, userIDStr)
return
}
// 获取 warehouse_id
warehouseIDStr := c.PostForm("warehouse_id")
if warehouseIDStr == "" {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品导入-缺少warehouse_id", fmt.Errorf("warehouse_id不能为空"), c, nil)
return
}
warehouseID, err := strconv.ParseInt(warehouseIDStr, 10, 64)
if err != nil || warehouseID <= 0 {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品导入-warehouse_id格式错误", err, c, warehouseIDStr)
return
}
// 获取上传的 Excel 文件
file, _, err := c.Request.FormFile("file")
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品导入-未上传文件", fmt.Errorf("未上传Excel文件: %v", err), c, nil)
return
}
defer file.Close()
fileBytes, err := io.ReadAll(file)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "商品导入-读取文件失败", fmt.Errorf("读取文件内容失败: %v", err), c, nil)
return
}
// 解析Excel
rows, err := goodsImportService.ParseGoodsImportExcel(fileBytes)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品导入-解析Excel失败", err, c, nil)
return
}
if len(rows) == 0 {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "没有有效数据",
"data": gin.H{
"success_count": 0,
"fail_count": 0,
"message": "Excel中没有有效数据行",
},
})
return
}
// 导入商品
result, err := goodsImportService.ImportGoodsFromExcel(userID, warehouseID, rows)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品导入-导入失败", err, c, nil)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "导入完成",
"data": gin.H{
"success_count": result.SuccessCount,
"fail_count": result.FailCount,
"message": result.Message,
},
})
}

168
controllers/inventory.go Normal file
View File

@ -0,0 +1,168 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type InventoryApi struct{}
var inventoryService = service.InventoryService{}
// GetInventoryList 获取库存汇总列表
func (r *InventoryApi) GetInventoryList(c *gin.Context) {
var req systemReq.GetInventoryListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "库存汇总列表请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.GetInventoryList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库存汇总列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetInventoryGroupedList 获取按仓库库位分组的库存列表
func (i *InventoryApi) GetInventoryGroupedList(c *gin.Context) {
var req systemReq.GetInventoryGroupedListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "库存分组列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.GetInventoryGroupedList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库存分组列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetInventorySummary 获取库存统计信息
func (r *InventoryApi) GetInventorySummary(c *gin.Context) {
result, err := inventoryService.GetInventorySummary(database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库存统计信息异常", err, c, nil)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetInventoryDetail 获取库存明细
func (r *InventoryApi) GetInventoryDetail(c *gin.Context) {
var req systemReq.GetInventoryDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "库存明细请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.GetInventoryDetail(req.ProductID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库存明细异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetInventoryLogList 获取库存流水列表
func (r *InventoryApi) GetInventoryLogList(c *gin.Context) {
var req systemReq.GetInventoryLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "库存流水列表请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.GetInventoryLogList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库存流水列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// InventoryStatist 获取ISBN/品相的库存总数
func (r *InventoryApi) InventoryStatist(c *gin.Context) {
var req systemReq.InventoryStatistRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "库存统计请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.InventoryStatist(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库存统计异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetStockCheckList 获取盘库列表
func (r *InventoryApi) GetStockCheckList(c *gin.Context) {
var req systemReq.GetStockCheckListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "盘库列表请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.GetStockCheckList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "盘库列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetStockCheckDetail 获取盘库明细列表
func (r *InventoryApi) GetStockCheckDetail(c *gin.Context) {
var req systemReq.GetStockCheckDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "盘库明细列表请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := inventoryService.GetStockCheckDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "盘库明细列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}

333
controllers/location.go Normal file
View File

@ -0,0 +1,333 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type LocationApi struct{}
var locationService = service.LocationService{}
func (r *LocationApi) GetLocationList(c *gin.Context) {
var req systemReq.QueryLocationRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "查询库位列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
list, total, err := locationService.GetLocationList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询库位列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"list": list,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
"msg": "查询成功",
})
}
// GetAllLocationList 查询所有库位列表仓库ID可选
func (r *LocationApi) GetAllLocationList(c *gin.Context) {
var req systemReq.QueryAllLocationRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "查询所有库位列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
list, total, err := locationService.GetAllLocationList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询所有库位列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"list": list,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
"msg": "查询成功",
})
}
func (r *LocationApi) GetLocationDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取库位详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取库位详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
location, err := locationService.GetLocationDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询库位详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(location, "查询成功", c)
}
// GetLocationInfo 获取库位ID
func (r *LocationApi) GetLocationInfo(c *gin.Context) {
var req systemReq.GetLocationIdRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取库位ID请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
location, err := locationService.GetLocationInfo(req.Code, req.WarehouseCode, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取库位ID异常", err, c, gin.H{"code": req.Code})
return
}
systemRes.OkWithDetailed(location, "查询成功", c)
}
func (r *LocationApi) CreateLocation(c *gin.Context) {
var req systemReq.CreateLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
id, err := locationService.CreateLocation(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建库位异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
func (r *LocationApi) BatchGenerateLocations(c *gin.Context) {
var req systemReq.BatchGenerateLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "批量生成库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Groups) == 0 {
groups, err := parseGroupsFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.Groups = groups
}
result, err := locationService.BatchGenerateLocations(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "批量生成库位异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, result.Message, c)
}
func (r *LocationApi) UpdateLocation(c *gin.Context) {
var req systemReq.UpdateLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.IDs) == 0 {
ids, err := parseIdsFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.IDs = ids
}
if err := locationService.UpdateLocation(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新库位异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
func (r *LocationApi) DeleteLocation(c *gin.Context) {
var req systemReq.DeleteLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.IDs) == 0 {
ids, err := parseIdsFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.IDs = ids
}
if err := locationService.DeleteLocation(req.IDs, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除库位异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
func (r *LocationApi) SyncLocations(c *gin.Context) {
var req systemReq.SyncLocationRequest
if err := c.ShouldBindJSON(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "同步库位数据请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := locationService.SyncLocations(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "同步库位数据异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
"msg": "同步完成",
})
}
// SyncGoods 同步商品数据
func (r *LocationApi) SyncGoods(c *gin.Context) {
var req systemReq.SyncGoodsRequest
if err := c.ShouldBindJSON(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "同步商品数据请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := locationService.SyncGoods(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "同步商品数据异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "同步完成",
"data": gin.H{
"success_count": result.SuccessCount,
"fail_count": result.FailCount,
"message": result.Message,
},
})
}
func parseIdsFromForm(c *gin.Context) ([]int64, error) {
var ids []int64
for i := 0; i < 100; i++ {
idStr := c.PostForm(fmt.Sprintf("ids[%d]", i))
if idStr == "" {
break
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
return nil, fmt.Errorf("第%d个ID格式错误", i)
}
if id <= 0 {
return nil, fmt.Errorf("第%d个ID必须大于0", i)
}
ids = append(ids, id)
}
if len(ids) == 0 {
return nil, fmt.Errorf("至少需要一个ID")
}
return ids, nil
}
func parseGroupsFromForm(c *gin.Context) ([]systemReq.GroupConfig, error) {
var groups []systemReq.GroupConfig
for i := 0; i < 5; i++ {
formatTypeStr := c.PostForm(fmt.Sprintf("groups[%d][format_type]", i))
if formatTypeStr == "" {
break
}
startValue := c.PostForm(fmt.Sprintf("groups[%d][start_value]", i))
endValue := c.PostForm(fmt.Sprintf("groups[%d][end_value]", i))
separator := c.PostForm(fmt.Sprintf("groups[%d][separator]", i))
paddingLenStr := c.PostForm(fmt.Sprintf("groups[%d][padding_len]", i))
if startValue == "" || endValue == "" {
return nil, fmt.Errorf("第%d组配置缺少必填字段", i)
}
var formatType int
if _, err := fmt.Sscanf(formatTypeStr, "%d", &formatType); err != nil {
return nil, fmt.Errorf("第%d组format_type格式错误", i)
}
if formatType < 1 || formatType > 4 {
return nil, fmt.Errorf("第%d组format_type必须在1-4之间", i)
}
paddingLen := 0
if paddingLenStr != "" {
if _, err := fmt.Sscanf(paddingLenStr, "%d", &paddingLen); err != nil {
return nil, fmt.Errorf("第%d组padding_len格式错误", i)
}
}
if separator == "" {
separator = ""
}
group := systemReq.GroupConfig{
FormatType: formatType,
StartValue: startValue,
EndValue: endValue,
PaddingLen: paddingLen,
Separator: separator,
}
groups = append(groups, group)
}
if len(groups) == 0 {
return nil, fmt.Errorf("至少需要一个分组配置")
}
if len(groups) > 5 {
return nil, fmt.Errorf("最多支持5组")
}
return groups, nil
}

View File

@ -0,0 +1,99 @@
package controllers
import (
"fmt"
"io"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"psi/constant"
"psi/service"
"psi/utils"
)
var locationImportService = &service.LocationImportService{}
// LocationImportApi 库位导入API
type LocationImportApi struct{}
// ImportFromExcel 从Excel文件批量创建库位
// POST /api/location/import-from-excel (sign鉴权)
// multipart/form-data: file (Excel), user_id, warehouse_id
func (r *LocationImportApi) ImportFromExcel(c *gin.Context) {
// 获取 user_id
userIDStr := c.PostForm("user_id")
if userIDStr == "" {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "库位导入-缺少user_id", fmt.Errorf("user_id不能为空"), c, nil)
return
}
userID, err := strconv.ParseInt(userIDStr, 10, 64)
if err != nil || userID <= 0 {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "库位导入-user_id格式错误", err, c, userIDStr)
return
}
// 获取 warehouse_id
warehouseIDStr := c.PostForm("warehouse_id")
if warehouseIDStr == "" {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "库位导入-缺少warehouse_id", fmt.Errorf("warehouse_id不能为空"), c, nil)
return
}
warehouseID, err := strconv.ParseInt(warehouseIDStr, 10, 64)
if err != nil || warehouseID <= 0 {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "库位导入-warehouse_id格式错误", err, c, warehouseIDStr)
return
}
// 获取上传的 Excel 文件
file, _, err := c.Request.FormFile("file")
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "库位导入-未上传文件", fmt.Errorf("未上传Excel文件: %v", err), c, nil)
return
}
defer file.Close()
fileBytes, err := io.ReadAll(file)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelRequest, "库位导入-读取文件失败", fmt.Errorf("读取文件内容失败: %v", err), c, nil)
return
}
// 解析Excel
rows, err := locationImportService.ParseLocationImportExcel(fileBytes)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库位导入-解析Excel失败", err, c, nil)
return
}
if len(rows) == 0 {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "没有有效数据",
"data": gin.H{
"success_count": 0,
"fail_count": 0,
"message": "Excel中没有有效的库位编码",
},
})
return
}
// 导入库位
result, err := locationImportService.ImportLocationsFromExcel(userID, warehouseID, rows)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "库位导入-导入失败", err, c, nil)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "导入完成",
"data": gin.H{
"success_count": result.SuccessCount,
"fail_count": result.FailCount,
"message": result.Message,
},
})
}

111
controllers/logistics.go Normal file
View File

@ -0,0 +1,111 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
"psi/models/request"
"psi/models/response"
"psi/service"
"psi/utils"
)
type LogisticsApi struct{}
var logisticsService = service.LogisticsService{}
func (r *LogisticsApi) GetLogisticsList(c *gin.Context) {
var req request.QueryLogisticsRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "查询物流模板列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
list, total, err := logisticsService.GetLogisticsList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询物流模板列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"list": list,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
"msg": "查询成功",
})
}
func (r *LogisticsApi) GetLogisticsDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
response.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id uint64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id == 0 {
response.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
logistics, err := logisticsService.GetLogisticsByID(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询物流模板详情异常", err, c, gin.H{"id": id})
return
}
response.OkWithDetailed(logistics, "查询成功", c)
}
func (r *LogisticsApi) CreateLogistics(c *gin.Context) {
var req request.CreateLogisticsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建物流模板请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
id, err := logisticsService.CreateLogistics(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建物流模板异常", err, c, req)
return
}
response.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
func (r *LogisticsApi) UpdateLogistics(c *gin.Context) {
var req request.UpdateLogisticsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新物流模板请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := logisticsService.UpdateLogistics(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新物流模板异常", err, c, req)
return
}
response.OkWithMessage("更新成功", c)
}
func (r *LogisticsApi) DeleteLogistics(c *gin.Context) {
var req request.DeleteLogisticsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除物流模板请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := logisticsService.DeleteLogistics(req.Id, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除物流模板异常", err, c, req)
return
}
response.OkWithMessage("删除成功", c)
}

64
controllers/ocr.go Normal file
View File

@ -0,0 +1,64 @@
package controllers
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"io"
"net/http"
"psi/constant"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type OcrApi struct{}
var ocrService = service.OcrService{}
func (r *OcrApi) RecognizeText(c *gin.Context) {
file, err := c.FormFile("image")
if err != nil {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "OCR识别请求异常",
"err_msg": err.Error(),
})
systemRes.FailWithValidateMessage("请上传图片文件", c)
return
}
imageFile, err := file.Open()
if err != nil {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "打开图片文件异常",
"err_msg": err.Error(),
})
systemRes.FailWithValidateMessage("读取图片文件失败", c)
return
}
defer imageFile.Close()
imageData, err := io.ReadAll(imageFile)
if err != nil {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "读取图片数据异常",
"err_msg": err.Error(),
})
systemRes.FailWithValidateMessage("读取图片数据失败", c)
return
}
result, err := ocrService.RecognizeText(imageData)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "OCR识别异常",
"err_msg": err.Error(),
})
systemRes.FailWithMessage(err.Error(), c)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}

100
controllers/out_task.go Normal file
View File

@ -0,0 +1,100 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type OutTaskApi struct{}
var outTaskService = service.OutTaskService{}
// GetOutTaskList 获取外部任务列表
func (r *OutTaskApi) GetOutTaskList(c *gin.Context) {
var req systemReq.GetOutTaskListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "外部任务列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := outTaskService.GetOutTaskList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "外部任务列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetOutTaskLogList 获取外部任务日志列表
func (r *OutTaskApi) GetOutTaskLogList(c *gin.Context) {
var req systemReq.GetOutTaskLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "外部任务日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := outTaskService.GetOutTaskLogList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "外部任务日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetOutTaskByShop 按店铺分组获取外部任务列表
func (r *OutTaskApi) GetOutTaskByShop(c *gin.Context) {
var req systemReq.GetOutTaskByShopRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "按店铺获取外部任务列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := outTaskService.GetOutTaskByShop(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "按店铺获取外部任务列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// UpdateOutTaskLog 修改外部任务日志
func (r *OutTaskApi) UpdateOutTaskLog(c *gin.Context) {
var req systemReq.UpdateOutTaskLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "修改外部任务日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := outTaskService.UpdateOutTaskLog(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "修改外部任务日志异常", err, c, req)
return
}
systemRes.OkWithMessage("操作成功", c)
}

57
controllers/outbound.go Normal file
View File

@ -0,0 +1,57 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type OutboundApi struct{}
var outboundService = service.OutboundService{}
// GetOutboundOrderList 获取出库单列表
func (r *OutboundApi) GetOutboundOrderList(c *gin.Context) {
var req systemReq.GetOutboundOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "出库单列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := outboundService.GetOutboundOrderList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "出库单列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetOutboundOrderDetail 获取出库单详情
func (r *OutboundApi) GetOutboundOrderDetail(c *gin.Context) {
var req systemReq.GetOutboundOrderDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "出库单详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := outboundService.GetOutboundOrderDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "出库单详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}

854
controllers/process.go Normal file
View File

@ -0,0 +1,854 @@
package controllers
import (
"bytes"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"psi/constant"
"psi/database"
"psi/models"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
"strconv"
)
type ProcessApi struct{}
var processService = &service.ProcessService{}
// CreatePurchaseOrderWithWave 创建采购单并生成入库波次
func (r *ProcessApi) CreatePurchaseOrderWithWave(c *gin.Context) {
var req systemReq.PurchaseOrderCreateRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建采购单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
carService := &service.CarService{}
capacity, err := carService.GetCarCapacityByID(req.CarID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询小车容量异常", err, c, gin.H{"car_id": req.CarID})
return
}
if len(req.Items) == 0 {
items, err := bindPurchaseOrderItems(c, int(capacity))
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定采购订单项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
userInfo := utils.GetUserInfo(c)
purchaseOrderID, waveID, err := processService.CreatePurchaseOrderWithWave(req, userInfo.Username, userInfo.ID, int(capacity), database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建采购单及波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"order_id": purchaseOrderID, "wave_id": waveID}, "采购单创建成功,波次已生成", c)
}
// ReleaseWave 提交波次,生成采购订单明细和波次任务明细
func (r *ProcessApi) ReleaseWave(c *gin.Context) {
var req systemReq.WaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
carService := &service.CarService{}
capacity, err := carService.GetCarCapacityByID(req.CarID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询小车容量异常", err, c, gin.H{"car_id": req.CarID})
return
}
if len(req.Items) == 0 {
items, err := bindWaveItems(c, int(capacity))
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定波次项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
waveID, waveNo, err := processService.ReleaseWave(req, capacity, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(systemRes.WaveReleaseResponse{
WaveID: waveID,
WaveNo: waveNo,
}, "波次提交成功,任务和明细已生成", c)
}
// BindWave 绑定波次,创建入库单
func (r *ProcessApi) BindWave(c *gin.Context) {
var req systemReq.BindWaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "绑定波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
receivingOrderID, waveTaskID, waveTaskBatchNo, err := processService.BindWave(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "绑定波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(map[string]interface{}{
"receiving_order_id": receivingOrderID,
"wave_task_id": waveTaskID,
"wave_task_batch_no": waveTaskBatchNo,
}, "绑定波次成功,入库单已创建", c)
}
// GetWaveTaskInfo 获取波次任务信息
func (r *ProcessApi) GetWaveTaskInfo(c *gin.Context) {
id, err := parsePathID(c, "id", "获取波次任务信息")
if err != nil {
return
}
info, err := processService.GetWaveTaskInfo(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取波次任务信息异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(info, "获取成功", c)
}
// SubmitReceiving 提交入库
func (r *ProcessApi) SubmitReceiving(c *gin.Context) {
var req systemReq.ReceivingSubmitRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交入库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Items) == 0 {
databaseConn := database.GetDB(c)
var waveTask models.WaveTask
if err := databaseConn.Where("id = ? AND is_del = 0", req.WaveTaskID).First(&waveTask).Error; err != nil {
logAndFail(constant.LoggerChannelWork, "查询波次任务失败", "错误: "+err.Error(), c)
return
}
carCapacity := int(waveTask.CarCapacity)
if carCapacity <= 0 {
logAndFail(constant.LoggerChannelWork, "波次任务未设置小车容量", fmt.Sprintf("wave_task_id: %d", req.WaveTaskID), c)
return
}
items, err := bindReceivingItems(c, carCapacity)
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定入库项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
userInfo := utils.GetUserInfo(c)
err := processService.SubmitReceiving(req, userInfo.Username, userInfo.ID, userInfo.AboutID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交入库异常", err, c, req)
return
}
go func(receivingOrderID, aboutID, waveTaskID int64, itemProductIDs []int64) {
databaseConn := database.DB
var receivingOrder models.ReceivingOrder
if err := databaseConn.Where("id = ? AND is_del = 0", receivingOrderID).First(&receivingOrder).Error; err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "查询入库单失败",
"receiving_order_id": receivingOrderID,
"err_msg": err.Error(),
})
return
}
var warehouse models.Warehouse
if err := databaseConn.Where("id = ? AND is_del = 0", receivingOrder.WarehouseID).First(&warehouse).Error; err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "查询仓库失败",
"warehouse_id": receivingOrder.WarehouseID,
"err_msg": err.Error(),
})
return
}
warehouseId := warehouse.ID
productIds := make([]string, len(itemProductIDs))
for i, pid := range itemProductIDs {
productIds[i] = fmt.Sprintf("%d", pid)
}
productIdsStr := strings.Join(productIds, ",")
url := "https://erp.buzhiyushu.cn/zhishu/product/releaseGoodsAuto"
method := "POST"
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
_ = writer.WriteField("userId", fmt.Sprintf("%d", aboutID))
_ = writer.WriteField("warehouseId", fmt.Sprintf("%d", warehouseId))
_ = writer.WriteField("productId", productIdsStr)
err := writer.Close()
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "关闭multipart writer失败",
"err_msg": err.Error(),
})
return
}
client := &http.Client{}
req, err := http.NewRequest(method, url, payload)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "创建HTTP请求失败",
"err_msg": err.Error(),
})
return
}
req.Header.Add("Authorization", "Basic ZWxhc3RpYzo1bVJESVVnNTJWQzBmcDE0bnctRg==")
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := client.Do(req)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "调用外部API失败",
"err_msg": err.Error(),
})
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "调用外部API失败-Body关闭失败",
"err_msg": err.Error(),
})
}
}(res.Body)
body, err := io.ReadAll(res.Body)
if err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "读取响应失败",
"err_msg": err.Error(),
})
return
}
utils.InfoLog(constant.LoggerChannelWork, logrus.Fields{
"source": "外部API调用完成",
"user_id": aboutID,
"warehouse_id": warehouseId,
"product_ids": productIdsStr,
"status_code": res.StatusCode,
"response": string(body),
})
}(req.ReceivingOrderID, userInfo.AboutID, req.WaveTaskID, func() []int64 {
productIDs := make([]int64, len(req.Items))
for i, item := range req.Items {
productIDs[i] = item.ProductID
}
return productIDs
}())
systemRes.OkWithMessage("入库提交成功", c)
}
// GetReceivingDetail 获取入库单详情
func (r *ProcessApi) GetReceivingDetail(c *gin.Context) {
id, err := parsePathID(c, "id", "获取入库单详情")
if err != nil {
return
}
detail, err := processService.GetReceivingDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取入库单详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(detail, "获取成功", c)
}
// CreateSalesOrderWithDetail 创建销售订单和明细
func (r *ProcessApi) CreateSalesOrderWithDetail(c *gin.Context) {
var req systemReq.SalesOrderCreateRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建销售订单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Items) == 0 {
items, err := bindSalesOrderItems(c)
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定销售订单项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
salesOrderID, err := processService.CreateSalesOrderWithDetail(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建销售订单及波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"order_id": salesOrderID}, "销售订单创建成功", c)
}
// CreateOutboundOrder 基于多个销售订单创建出库单
func (r *ProcessApi) CreateOutboundOrder(c *gin.Context) {
var req systemReq.CreateOutboundOrderRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建出库单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.SalesOrderIDs) == 0 {
ids, err := parseIdsFrom(c, req.Total)
if err != nil {
systemRes.FailWithMessage("参数错误: "+err.Error(), c)
return
}
req.SalesOrderIDs = ids
}
userInfo := utils.GetUserInfo(c)
outboundOrderID, outNo, err := processService.CreateOutboundOrder(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建出库单异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{
"outbound_order_id": outboundOrderID,
"out_no": outNo,
}, "出库单创建成功", c)
}
// CreateOutboundWave 基于出库单创建出库波次
func (r *ProcessApi) CreateOutboundWave(c *gin.Context) {
var req systemReq.CreateOutboundWaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if req.OutboundOrderID == 0 {
systemRes.FailWithMessage("参数错误: 出库单ID不能为空", c)
return
}
userInfo := utils.GetUserInfo(c)
waveID, err := processService.CreateOutboundWave(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建出库波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(systemRes.WaveReleaseResponse{
WaveID: waveID,
}, "出库波次创建成功", c)
}
// ReleaseOutboundWave 提交出库波次,生成波次任务明细
func (r *ProcessApi) ReleaseOutboundWave(c *gin.Context) {
var req systemReq.WaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
waveID, waveNo, err := processService.ReleaseOutboundWave(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交出库波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(systemRes.WaveReleaseResponse{
WaveID: waveID,
WaveNo: waveNo,
}, "出库波次提交成功,任务和明细已生成", c)
}
// BindOutboundWave 绑定出库波次
func (r *ProcessApi) BindOutboundWave(c *gin.Context) {
var req systemReq.BindWaveRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "绑定出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
outboundOrderID, waveTaskID, waveTaskBatchNo, err := processService.BindOutboundWave(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "绑定出库波次异常", err, c, req)
return
}
systemRes.OkWithDetailed(map[string]interface{}{
"out_order_id": outboundOrderID,
"wave_task_id": waveTaskID,
"wave_task_batch_no": waveTaskBatchNo,
}, "绑定出库波次成功", c)
}
// SubmitOutbound 提交出库
func (r *ProcessApi) SubmitOutbound(c *gin.Context) {
var req systemReq.OutboundSubmitRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "提交出库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.Items) == 0 {
databaseConn := database.GetDB(c)
var waveTask models.WaveTask
if err := databaseConn.Where("id = ? AND is_del = 0", req.WaveTaskID).First(&waveTask).Error; err != nil {
logAndFail(constant.LoggerChannelWork, "查询波次任务失败", "错误: "+err.Error(), c)
return
}
var count int64
if err := databaseConn.Model(&models.WaveTaskDetail{}).Where("wave_task_id = ? AND status = 1 AND is_del = 0", waveTask.ID).Count(&count).Error; err != nil {
logAndFail(constant.LoggerChannelWork, "查询波次任务明细数量失败", "错误: "+err.Error(), c)
return
}
items, err := bindOutboundItems(c, int(count))
if err != nil {
logAndFail(constant.LoggerChannelRequest, "绑定出库项异常", "参数错误: "+err.Error(), c)
return
}
req.Items = items
}
userInfo := utils.GetUserInfo(c)
err := processService.SubmitOutbound(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "提交出库异常", err, c, req)
return
}
systemRes.OkWithMessage("出库提交成功", c)
}
// GetOutboundDetail 获取发货单详情
func (r *ProcessApi) GetOutboundDetail(c *gin.Context) {
id, err := parsePathID(c, "id", "获取发货单详情")
if err != nil {
return
}
detail, err := processService.GetOutboundDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取发货单详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(detail, "获取成功", c)
}
// CreateShippingOrder 基于多个出库单创建发货单
func (r *ProcessApi) CreateShippingOrder(c *gin.Context) {
var req systemReq.CreateShippingOrderRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建发货单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.OutboundOrderIDs) == 0 {
ids, err := parseIdsFrom(c, req.Total)
if err != nil {
systemRes.FailWithMessage("参数错误: "+err.Error(), c)
return
}
req.OutboundOrderIDs = ids
}
userInfo := utils.GetUserInfo(c)
shippingOrderID, shippingNo, err := processService.CreateShippingOrder(req, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建发货单异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{
"shipping_order_id": shippingOrderID,
"shipping_no": shippingNo,
}, "发货单创建成功", c)
}
// UpdateShippingLogistics 更新发货单物流信息并回填销售订单明细
func (r *ProcessApi) UpdateShippingLogistics(c *gin.Context) {
var req systemReq.UpdateShippingLogisticsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新发货单物流信息请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := processService.UpdateShippingLogistics(req, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新发货单物流信息异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// CancelSalesOrder 取消销售订单
func (r *ProcessApi) CancelSalesOrder(c *gin.Context) {
var req systemReq.CancelSalesOrderRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "取消销售订单请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := processService.CancelSalesOrder(req.OrderID, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "取消销售订单异常", err, c, req)
return
}
systemRes.OkWithMessage("销售订单取消成功,库存已释放", c)
}
// CancelOutboundWave 取消出库波次
func (r *ProcessApi) CancelOutboundWave(c *gin.Context) {
var req systemReq.CancelOutboundWaveRequest
if err := c.ShouldBindJSON(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "取消出库波次请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := processService.CancelOutboundWave(req.WaveID, userInfo.Username, userInfo.ID, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "取消出库波次异常", err, c, req)
return
}
systemRes.OkWithMessage("出库波次取消成功,库存已释放", c)
}
// AdjustInventory 盘库调整(加库存/减库存)
func (r *ProcessApi) AdjustInventory(c *gin.Context) {
var req systemReq.StockCheckAdjustRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "盘库调整请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := processService.AdjustInventory(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "盘库调整异常", err, c, req)
return
}
systemRes.OkWithMessage("盘库调整成功", c)
}
// ReturnInventory 盘库退货
func (r *ProcessApi) ReturnInventory(c *gin.Context) {
var req systemReq.StockCheckReturnRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "盘库退货请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := processService.ReturnInventory(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "盘库退货异常", err, c, req)
return
}
systemRes.OkWithMessage("盘库退货成功", c)
}
// ChangeLocation 出库单切换库位
func (r *ProcessApi) ChangeLocation(c *gin.Context) {
var req systemReq.ChangeLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "出库单切换库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := processService.ChangeLocation(req, userInfo.Username, userInfo.ID, database.GetDB(c)); err != nil {
if errors.Is(err, utils.ErrNoAvailableLocation) {
c.Status(http.StatusNoContent)
return
}
utils.FailWithRequestLog(constant.LoggerChannelWork, "出库单切换库位异常", err, c, req)
return
}
systemRes.OkWithMessage("库位切换成功", c)
}
// parsePathID 从URL路径参数中获取并验证ID
func parsePathID(c *gin.Context, paramName, source string) (int64, error) {
idStr := c.Param(paramName)
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": source + "请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return 0, fmt.Errorf("ID参数不能为空")
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": source + "请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return 0, fmt.Errorf("ID格式错误")
}
return id, nil
}
// parseOptionalDate 解析可选日期字段
func parseOptionalDate(c *gin.Context, key string) int64 {
if str := c.PostForm(key); str != "" {
if val, err := strconv.ParseInt(str, 10, 64); err == nil {
return val
}
}
return 0
}
// bindSimpleItem 绑定简单项ProductID + Quantity + UnitPrice
func bindSimpleItem[T any](c *gin.Context, i int, creator func(int64, int64, int64) T) (T, bool, error) {
var zero T
productID, hasProduct, err := getPostFormInt64(c, fmt.Sprintf("items[%d][product_id]", i))
if err != nil || !hasProduct {
return zero, false, err
}
quantity, hasQuantity, err := getPostFormInt64(c, fmt.Sprintf("items[%d][quantity]", i))
if err != nil || !hasQuantity {
return zero, false, err
}
unitPrice, hasPrice, err := getPostFormInt64(c, fmt.Sprintf("items[%d][unit_price]", i))
if err != nil || !hasPrice {
return zero, false, err
}
return creator(productID, quantity, unitPrice), true, nil
}
// bindLocationItem 绑定带位置信息的项ProductID + LocationID + Quantity + 批次信息)
func bindLocationItem[T any](c *gin.Context, i int, creator func(int64, int64, string, int64, int64, int64) T) (T, bool, error) {
var zero T
productID, hasProduct, err := getPostFormInt64(c, fmt.Sprintf("items[%d][product_id]", i))
if err != nil || !hasProduct {
return zero, false, err
}
locationID, hasLocation, err := getPostFormInt64(c, fmt.Sprintf("items[%d][location_id]", i))
if err != nil || !hasLocation {
return zero, false, err
}
quantity, hasQuantity, err := getPostFormInt64(c, fmt.Sprintf("items[%d][quantity]", i))
if err != nil || !hasQuantity {
return zero, false, err
}
batchNo := c.PostForm(fmt.Sprintf("items[%d][batch_no]", i))
productionDate := parseOptionalDate(c, fmt.Sprintf("items[%d][production_date]", i))
expiryDate := parseOptionalDate(c, fmt.Sprintf("items[%d][expiry_date]", i))
return creator(productID, locationID, batchNo, productionDate, expiryDate, quantity), true, nil
}
// bindPurchaseOrderItems 绑定采购订单项
func bindPurchaseOrderItems(c *gin.Context, capacity int) ([]systemReq.PurchaseOrderItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.PurchaseOrderItemRequest, bool, error) {
return bindSimpleItem(c, i, func(productID, quantity, unitPrice int64) systemReq.PurchaseOrderItemRequest {
return systemReq.PurchaseOrderItemRequest{
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
}
})
}, capacity)
}
// bindWaveItems 绑定波次项
func bindWaveItems(c *gin.Context, capacity int) ([]systemReq.WaveItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.WaveItemRequest, bool, error) {
return bindSimpleItem(c, i, func(productID, quantity, unitPrice int64) systemReq.WaveItemRequest {
return systemReq.WaveItemRequest{
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
}
})
}, capacity)
}
// bindReceivingItems 绑定入库项
func bindReceivingItems(c *gin.Context, maxItems int) ([]systemReq.ReceivingItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.ReceivingItemRequest, bool, error) {
return bindLocationItem(c, i, func(productID, locationID int64, batchNo string, productionDate, expiryDate, quantity int64) systemReq.ReceivingItemRequest {
return systemReq.ReceivingItemRequest{
ProductID: productID,
LocationID: locationID,
BatchNo: batchNo,
ProductionDate: productionDate,
ExpiryDate: expiryDate,
Quantity: quantity,
}
})
}, maxItems)
}
// bindSalesOrderItems 绑定销售订单项
func bindSalesOrderItems(c *gin.Context) ([]systemReq.SalesOrderItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.SalesOrderItemRequest, bool, error) {
return bindSimpleItem(c, i, func(productID, quantity, unitPrice int64) systemReq.SalesOrderItemRequest {
return systemReq.SalesOrderItemRequest{
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
}
})
}, 200)
}
// bindOutboundItems 绑定出库项
func bindOutboundItems(c *gin.Context, maxItems int) ([]systemReq.OutboundItemRequest, error) {
return bindItemsWithForm(c, func(i int) (systemReq.OutboundItemRequest, bool, error) {
return bindLocationItem(c, i, func(productID, locationID int64, batchNo string, productionDate, expiryDate, quantity int64) systemReq.OutboundItemRequest {
return systemReq.OutboundItemRequest{
ProductID: productID,
LocationID: locationID,
BatchNo: batchNo,
ProductionDate: productionDate,
ExpiryDate: expiryDate,
Quantity: quantity,
}
})
}, maxItems)
}
// bindItemsWithForm 通用表单项绑定函数
func bindItemsWithForm[T any](c *gin.Context, builder func(int) (T, bool, error), maxItems int) ([]T, error) {
items := make([]T, 0)
for i := 0; i < maxItems; i++ {
item, hasData, err := builder(i)
if err != nil {
return nil, err
}
if !hasData {
continue
}
items = append(items, item)
}
return items, nil
}
func parseIdsFrom(c *gin.Context, total int) ([]int64, error) {
var ids []int64
for i := 0; i < total; i++ {
idStr := c.PostForm(fmt.Sprintf("order_ids[%d]", i))
if idStr == "" {
break
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
return nil, fmt.Errorf("第%d个ID格式错误", i)
}
if id <= 0 {
return nil, fmt.Errorf("第%d个ID必须大于0", i)
}
ids = append(ids, id)
}
if len(ids) == 0 {
return nil, fmt.Errorf("至少需要一个ID")
}
return ids, nil
}
// getPostFormInt64 获取表单中的int64值
func getPostFormInt64(c *gin.Context, key string) (int64, bool, error) {
str := c.PostForm(key)
if str == "" {
return 0, false, nil
}
val, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return 0, true, err
}
return val, true, nil
}
// logAndFail 业务统一的错误日志和响应函数
func logAndFail(channel string, source, errMsg string, c *gin.Context) {
utils.ErrorLog(channel, logrus.Fields{
"source": source,
"err_msg": errMsg,
})
systemRes.FailWithMessage(errMsg, c)
}
// ValidAndFail 参数统一的错误日志和响应函数
func ValidAndFail(channel string, source, errMsg string, c *gin.Context, err error) {
utils.ErrorLog(channel, logrus.Fields{
"source": source,
"err_msg": errMsg,
})
utils.HandleValidationError(c, err)
}

464
controllers/product.go Normal file
View File

@ -0,0 +1,464 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
"strings"
)
type ProductApi struct{}
var productService = service.ProductService{}
func (r *ProductApi) GetProductList(c *gin.Context) {
var req systemReq.GetProductListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "商品列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.IDs) == 0 {
ids, err := parseIds(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.IDs = ids
}
result, err := productService.GetProductList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
func (r *ProductApi) GetDistributionProductList(c *gin.Context) {
var req systemReq.GetDistributionProductListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "分销商品列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
// Name支持双重编码如果绑定后仍含%编码字符,再解码一次
if req.Name != "" && strings.Contains(req.Name, "%") {
if decoded, err := url.QueryUnescape(req.Name); err == nil {
req.Name = decoded
}
}
// StockOperator支持双重编码
if req.StockOperator != "" && strings.Contains(req.StockOperator, "%") {
if decoded, err := url.QueryUnescape(req.StockOperator); err == nil {
req.StockOperator = decoded
}
}
// SalePriceOperator支持双重编码
if req.SalePriceOperator != "" && strings.Contains(req.SalePriceOperator, "%") {
if decoded, err := url.QueryUnescape(req.SalePriceOperator); err == nil {
req.SalePriceOperator = decoded
}
}
result, err := productService.GetDistributionProductList(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "分销商品列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetProductDetail 获取商品详情
func (r *ProductApi) GetProductDetail(c *gin.Context) {
var req systemReq.GetProductDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "商品详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetProductDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品详情异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// SaveProduct 保存商品(添加或修改)
func (r *ProductApi) SaveProduct(c *gin.Context) {
var req systemReq.ProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "保存商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.LiveImage) == 0 {
image, err := parseImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.LiveImage = image
}
id, err := productService.SaveProduct(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存商品异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "操作成功", c)
}
// UpdatePrice 修改商品售价
func (r *ProductApi) UpdatePrice(c *gin.Context) {
var req systemReq.UpdatePriceRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "修改售价请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.UpdatePrice(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "修改售价异常", err, c, req)
return
}
systemRes.OkWithMessage("售价修改成功", c)
}
// DeleteProduct 删除商品
func (r *ProductApi) DeleteProduct(c *gin.Context) {
var req systemReq.DeleteProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := productService.DeleteProduct(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除商品异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
// GetProductInventory 获取商品库存数量
func (r *ProductApi) GetProductInventory(c *gin.Context) {
var req systemReq.GetProductInventoryRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取商品库存请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetProductInventory(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取商品库存异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// RetryOutTask 重试失败的外部任务
func (r *ProductApi) RetryOutTask(c *gin.Context) {
var req systemReq.RetryOutTaskRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "重试任务请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.RetryOutTask(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "重试任务异常", err, c, req)
return
}
systemRes.OkWithMessage("重试任务已提交", c)
}
// ExportProducts 导出商品到Excel
func (r *ProductApi) ExportProducts(c *gin.Context) {
var req systemReq.ExportProductRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "导出商品请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.ExportProducts(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "导出商品异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// PushProductToShop 上架到商铺:创建商品、库存记录,并推送到商铺
func (r *ProductApi) PushProductToShop(c *gin.Context) {
var req systemReq.PushProductToShopRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "上架到商铺请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
// 处理photos参数: 如果表单未绑定,尝试从原始表单解析
if len(req.Photos) == 0 {
var photos []string
for i := 0; i < 20; i++ {
photoStr := c.PostForm(fmt.Sprintf("photos[%d]", i))
if photoStr == "" {
break
}
photos = append(photos, photoStr)
}
req.Photos = photos
}
result, err := productService.PushProductToShop(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "上架到商铺异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// BatchPushProducts 批量推送商品到多个店铺
func (r *ProductApi) BatchPushProducts(c *gin.Context) {
var req systemReq.BatchPushProductRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "批量推送请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.BatchPushProducts(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "批量推送异常", err, c, req)
return
}
systemRes.OkWithMessage("批量推送完成", c)
}
func (r *ProductApi) GetProductLogList(c *gin.Context) {
var req systemReq.GetProductLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "商品日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := productService.GetProductLogList(req, userInfo.AboutID)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "商品日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// SaveProductLog 保存商品日志(添加或修改)
func (r *ProductApi) SaveProductLog(c *gin.Context) {
var req systemReq.ProductLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "保存商品日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.OldLiveImage) == 0 {
image, err := parseOldImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.OldLiveImage = image
}
if len(req.NewLiveImage) == 0 {
image, err := parseNewImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.NewLiveImage = image
}
userInfo := utils.GetUserInfo(c)
id, err := productService.SaveProductLog(req, userInfo.ID)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存商品日志异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "操作成功", c)
}
func (r *ProductApi) AuditProductLog(c *gin.Context) {
var req systemReq.AuditProductLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "审核商品日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.NewLiveImage) == 0 {
image, err := parseNewImageFromForm(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.NewLiveImage = image
}
userInfo := utils.GetUserInfo(c)
if err := productService.AuditProductLog(req, userInfo.ID); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "审核商品日志异常", err, c, req)
return
}
systemRes.OkWithMessage("审核成功", c)
}
func (r *ProductApi) DeleteProductLog(c *gin.Context) {
var req systemReq.DeleteProductLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除商品日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := productService.DeleteProductLog(req); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除商品日志异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
// GetShopProductDetail 获取店铺商品详情(包含商品数据和发送记录)
func (r *ProductApi) GetShopProductDetail(c *gin.Context) {
var req systemReq.GetShopProductDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "获取店铺商品详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := productService.GetShopProductDetail(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取店铺商品详情异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
func parseIds(c *gin.Context) ([]int64, error) {
var ids []int64
for i := 0; i < 100; i++ {
idStr := c.Query(fmt.Sprintf("ids[%d]", i))
if idStr == "" {
break
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil {
return nil, fmt.Errorf("第%d个ID格式错误", i)
}
if id <= 0 {
return nil, fmt.Errorf("第%d个ID必须大于0", i)
}
ids = append(ids, id)
}
return ids, nil
}
func parseImageFromForm(c *gin.Context) ([]string, error) {
var images []string
for i := 0; i < 10; i++ {
imageStr := c.PostForm(fmt.Sprintf("live_image[%d]", i))
if imageStr == "" {
break
}
images = append(images, imageStr)
}
return images, nil
}
func parseOldImageFromForm(c *gin.Context) ([]string, error) {
var images []string
for i := 0; i < 10; i++ {
imageStr := c.PostForm(fmt.Sprintf("old_live_image[%d]", i))
if imageStr == "" {
break
}
images = append(images, imageStr)
}
return images, nil
}
func parseNewImageFromForm(c *gin.Context) ([]string, error) {
var images []string
for i := 0; i < 10; i++ {
imageStr := c.PostForm(fmt.Sprintf("new_live_image[%d]", i))
if imageStr == "" {
break
}
images = append(images, imageStr)
}
return images, nil
}

57
controllers/purchase.go Normal file
View File

@ -0,0 +1,57 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type PurchaseApi struct{}
var purchaseService = service.PurchaseService{}
// GetPurchaseOrderList 获取采购订单列表
func (r *PurchaseApi) GetPurchaseOrderList(c *gin.Context) {
var req systemReq.GetPurchaseOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "采购订单列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := purchaseService.GetPurchaseOrderList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "采购订单列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetPurchaseOrderDetail 获取采购订单详情
func (r *PurchaseApi) GetPurchaseOrderDetail(c *gin.Context) {
var req systemReq.GetPurchaseOrderDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "采购订单详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := purchaseService.GetPurchaseOrderDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "采购订单详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}

57
controllers/receiving.go Normal file
View File

@ -0,0 +1,57 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type ReceivingApi struct{}
var receivingService = service.ReceivingService{}
// GetReceivingOrderList 获取入库单列表
func (r *ReceivingApi) GetReceivingOrderList(c *gin.Context) {
var req systemReq.GetReceivingOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "入库单列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := receivingService.GetReceivingOrderList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "入库单列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetReceivingOrderDetail 获取入库单详情
func (r *ReceivingApi) GetReceivingOrderDetail(c *gin.Context) {
var req systemReq.GetReceivingOrderDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "入库单详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := receivingService.GetReceivingOrderDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "入库单详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}

79
controllers/sales.go Normal file
View File

@ -0,0 +1,79 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type SalesApi struct{}
var salesService = service.SalesService{}
// GetSalesOrderList 获取销售订单列表
func (r *SalesApi) GetSalesOrderList(c *gin.Context) {
var req systemReq.GetSalesOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销售订单列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := salesService.GetSalesOrderList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销售订单列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetSalesOrderDetail 获取销售订单详情
func (r *SalesApi) GetSalesOrderDetail(c *gin.Context) {
var req systemReq.GetSalesOrderDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销售订单详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := salesService.GetSalesOrderDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销售订单详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetSalesOrderDetailList 获取分页的销售订单详情列表
func (r *SalesApi) GetSalesOrderDetailList(c *gin.Context) {
var req systemReq.GetSalesOrderDetailListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "销售订单详情列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := salesService.GetSalesOrderDetailList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "销售订单详情列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}

79
controllers/shipping.go Normal file
View File

@ -0,0 +1,79 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type ShippingApi struct{}
var shippingService = service.ShippingService{}
// GetShippingOrderList 获取发货单列表
func (r *ShippingApi) GetShippingOrderList(c *gin.Context) {
var req systemReq.GetShippingOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "发货单列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := shippingService.GetShippingOrderList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "发货单列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetShippingOrderDetail 获取发货单详情
func (r *ShippingApi) GetShippingOrderDetail(c *gin.Context) {
var req systemReq.GetShippingOrderDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "发货单详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := shippingService.GetShippingOrderDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "发货单详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetShippingOrderDetailList 获取发货单详情列表按状态筛选返回list+detail所有字段
func (r *ShippingApi) GetShippingOrderDetailList(c *gin.Context) {
var req systemReq.GetShippingOrderDetailListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "发货单详情列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := shippingService.GetShippingOrderDetailList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "发货单详情列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}

View File

@ -0,0 +1,57 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type ShippingApi struct{}
var shippingService = service.ShippingService{}
// GetShippingOrderList 获取发货单列表
func (r *ShippingApi) GetShippingOrderList(c *gin.Context) {
var req systemReq.GetShippingOrderListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "发货单列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := shippingService.GetShippingOrderList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "发货单列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetShippingOrderDetail 获取发货单详情
func (r *ShippingApi) GetShippingOrderDetail(c *gin.Context) {
var req systemReq.GetShippingOrderDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "发货单详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := shippingService.GetShippingOrderDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "发货单详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}

120
controllers/shop.go Normal file
View File

@ -0,0 +1,120 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type ShopApi struct{}
var shopService = service.ShopService{}
func (r *ShopApi) GetShopList(c *gin.Context) {
var req systemReq.QueryShopRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "查询店铺列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
list, total, err := shopService.GetShopList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询店铺列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"list": list,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
"msg": "查询成功",
})
}
func (r *ShopApi) GetShopDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取店铺详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取店铺详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
shop, err := shopService.GetShopByID(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询店铺详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(shop, "查询成功", c)
}
func (r *ShopApi) CreateShop(c *gin.Context) {
var req systemReq.CreateShopRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建店铺请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
id, err := shopService.CreateShop(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建店铺异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
func (r *ShopApi) UpdateShop(c *gin.Context) {
var req systemReq.UpdateShopRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新店铺请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := shopService.UpdateShop(req, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新店铺异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
func (r *ShopApi) DeleteShop(c *gin.Context) {
var req systemReq.DeleteShopRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除店铺请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if err := shopService.DeleteShop(req.ID, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除店铺异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}

View File

@ -0,0 +1,39 @@
package controllers
import (
"github.com/gin-gonic/gin"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type SortingSettingsApi struct{}
var sortingSettingsService = service.SortingSettingsService{}
// GetSortingSettings 获取分拣设置
func (r *SortingSettingsApi) GetSortingSettings(c *gin.Context) {
result := sortingSettingsService.GetSortingSettings(database.GetDB(c))
systemRes.OkWithDetailed(result, "查询成功", c)
}
// SaveSortingSettings 保存分拣设置
func (r *SortingSettingsApi) SaveSortingSettings(c *gin.Context) {
var req systemReq.SaveSortingSettingsRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "保存分拣设置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := sortingSettingsService.SaveSortingSettings(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存分拣设置异常", err, c, req)
return
}
systemRes.OkWithMessage("保存成功", c)
}

View File

@ -0,0 +1,127 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type SplitAccountConfigApi struct{}
var splitAccountConfigService = service.SplitAccountConfigService{}
// GetSplitAccountConfigList 获取分账配置列表
func (r *SplitAccountConfigApi) GetSplitAccountConfigList(c *gin.Context) {
var req systemReq.GetSplitAccountConfigListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "分账配置列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := splitAccountConfigService.GetSplitAccountConfigList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "分账配置列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetSplitAccountConfigDetail 获取分账配置详情
func (r *SplitAccountConfigApi) GetSplitAccountConfigDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取分账配置详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取分账配置详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
result, err := splitAccountConfigService.GetSplitAccountConfigDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "分账配置详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// CreateSplitAccountConfig 创建分账配置
func (r *SplitAccountConfigApi) CreateSplitAccountConfig(c *gin.Context) {
var req systemReq.AddSplitAccountConfigRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建分账配置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
id, err := splitAccountConfigService.CreateSplitAccountConfig(req, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建分账配置异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
// UpdateSplitAccountConfig 更新分账配置
func (r *SplitAccountConfigApi) UpdateSplitAccountConfig(c *gin.Context) {
var req systemReq.UpdateSplitAccountConfigRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新分账配置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := splitAccountConfigService.UpdateSplitAccountConfig(req, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新分账配置异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// DeleteSplitAccountConfig 删除分账配置
func (r *SplitAccountConfigApi) DeleteSplitAccountConfig(c *gin.Context) {
var req systemReq.DeleteSplitAccountConfigRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除分账配置请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := splitAccountConfigService.DeleteSplitAccountConfig(req, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除分账配置异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}

View File

@ -0,0 +1,127 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type SplitAccountDeductionLogApi struct{}
var splitAccountDeductionLogService = service.SplitAccountDeductionLogService{}
// GetSplitAccountDeductionLogList 获取分账扣钱日志列表
func (r *SplitAccountDeductionLogApi) GetSplitAccountDeductionLogList(c *gin.Context) {
var req systemReq.GetSplitAccountDeductionLogListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "分账扣钱日志列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := splitAccountDeductionLogService.GetSplitAccountDeductionLogList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "分账扣钱日志列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetSplitAccountDeductionLogDetail 获取分账扣钱日志详情
func (r *SplitAccountDeductionLogApi) GetSplitAccountDeductionLogDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取分账扣钱日志详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取分账扣钱日志详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
result, err := splitAccountDeductionLogService.GetSplitAccountDeductionLogDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "分账扣钱日志详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// CreateSplitAccountDeductionLog 创建分账扣钱日志
func (r *SplitAccountDeductionLogApi) CreateSplitAccountDeductionLog(c *gin.Context) {
var req systemReq.AddSplitAccountDeductionLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建分账扣钱日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
id, err := splitAccountDeductionLogService.CreateSplitAccountDeductionLog(req, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建分账扣钱日志异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
// UpdateSplitAccountDeductionLog 更新分账扣钱日志
func (r *SplitAccountDeductionLogApi) UpdateSplitAccountDeductionLog(c *gin.Context) {
var req systemReq.UpdateSplitAccountDeductionLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新分账扣钱日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := splitAccountDeductionLogService.UpdateSplitAccountDeductionLog(req, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新分账扣钱日志异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// DeleteSplitAccountDeductionLog 删除分账扣钱日志
func (r *SplitAccountDeductionLogApi) DeleteSplitAccountDeductionLog(c *gin.Context) {
var req systemReq.DeleteSplitAccountDeductionLogRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除分账扣钱日志请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
err := splitAccountDeductionLogService.DeleteSplitAccountDeductionLog(req, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除分账扣钱日志异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}

69
controllers/statist.go Normal file
View File

@ -0,0 +1,69 @@
package controllers
import (
"github.com/gin-gonic/gin"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type StatistApi struct{}
var statistService = service.StatistService{}
// DashboardStatist 仪表盘统计
func (i *StatistApi) DashboardStatist(c *gin.Context) {
var req systemReq.DashboardStatistRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "仪表盘统计请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := statistService.DashboardStatist(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "仪表盘统计异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetWarehouseStatist 仓库统计接口
func (i *StatistApi) GetWarehouseStatist(c *gin.Context) {
var req systemReq.WarehouseStatistRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "仓库统计请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := statistService.GetWarehouseStatist(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "仓库统计异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// GetOrderStatist 订单统计接口
func (i *StatistApi) GetOrderStatist(c *gin.Context) {
var req systemReq.OrderStatistRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "订单统计请求异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := statistService.GetOrderStatist(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "订单统计异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}

124
controllers/supplier.go Normal file
View File

@ -0,0 +1,124 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type SupplierApi struct{}
var supplierService = service.SupplierService{}
// GetSupplierList 获取供应商列表
func (r *SupplierApi) GetSupplierList(c *gin.Context) {
var req systemReq.GetSupplierListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "供应商列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := supplierService.GetSupplierList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "供应商列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetSupplierDetail 获取供应商详情
func (r *SupplierApi) GetSupplierDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取供应商详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取供应商详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
result, err := supplierService.GetSupplierDetail(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "供应商详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// CreateSupplier 创建供应商
func (r *SupplierApi) CreateSupplier(c *gin.Context) {
var req systemReq.AddSupplierRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建供应商请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
id, err := supplierService.CreateSupplier(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建供应商异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
// UpdateSupplier 更新供应商
func (r *SupplierApi) UpdateSupplier(c *gin.Context) {
var req systemReq.UpdateSupplierRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新供应商请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := supplierService.UpdateSupplier(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新供应商异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// DeleteSupplier 删除供应商
func (r *SupplierApi) DeleteSupplier(c *gin.Context) {
var req systemReq.DeleteSupplierRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除供应商请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := supplierService.DeleteSupplier(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除供应商异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}

123
controllers/user_type.go Normal file
View File

@ -0,0 +1,123 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"psi/constant"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
)
type UserTypeApi struct{}
var userTypeService = service.UserTypeService{}
// GetUserTypeList 获取用户类型列表
func (r *UserTypeApi) GetUserTypeList(c *gin.Context) {
var req systemReq.GetUserTypeListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "用户类型列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := userTypeService.GetUserTypeList(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "用户类型列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetUserTypeDetail 获取用户类型详情
func (r *UserTypeApi) GetUserTypeDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取用户类型详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取用户类型详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
result, err := userTypeService.GetUserTypeDetail(id)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "用户类型详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
// CreateUserType 创建用户类型
func (r *UserTypeApi) CreateUserType(c *gin.Context) {
var req systemReq.AddUserTypeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建用户类型请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
id, err := userTypeService.CreateUserType(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建用户类型异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
// UpdateUserType 更新用户类型
func (r *UserTypeApi) UpdateUserType(c *gin.Context) {
var req systemReq.UpdateUserTypeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新用户类型请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := userTypeService.UpdateUserType(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新用户类型异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
// DeleteUserType 删除用户类型
func (r *UserTypeApi) DeleteUserType(c *gin.Context) {
var req systemReq.DeleteUserTypeRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除用户类型请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
err := userTypeService.DeleteUserType(req)
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除用户类型异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}

207
controllers/warehouse.go Normal file
View File

@ -0,0 +1,207 @@
package controllers
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"os"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
"strings"
"time"
)
type WarehouseApi struct{}
var warehouseService = service.WarehouseService{}
func (r *WarehouseApi) GetWarehouseList(c *gin.Context) {
var req systemReq.QueryWarehouseRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "查询仓库列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
if len(req.IDs) == 0 {
ids, err := parseIds(c)
if err != nil {
systemRes.FailWithValidateMessage("参数错误: "+err.Error(), c)
return
}
req.IDs = ids
}
list, total, err := warehouseService.GetWarehouseList(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询仓库列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": gin.H{
"list": list,
"total": total,
"page": req.Page,
"page_size": req.PageSize,
},
"msg": "查询成功",
})
}
func (r *WarehouseApi) GetWarehouseDetail(c *gin.Context) {
idStr := c.Param("id")
if idStr == "" {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取仓库详情请求参数异常",
"err_msg": "ID参数不能为空",
})
systemRes.FailWithValidateMessage("参数错误: ID不能为空", c)
return
}
var id int64
if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil || id <= 0 {
utils.ErrorLog(constant.LoggerChannelRequest, logrus.Fields{
"source": "获取仓库详情请求参数异常",
"err_msg": "ID格式错误: " + idStr,
})
systemRes.FailWithValidateMessage("参数错误: ID格式不正确", c)
return
}
warehouse, err := warehouseService.GetWarehouseByID(id, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "查询仓库详情异常", err, c, gin.H{"id": id})
return
}
systemRes.OkWithDetailed(warehouse, "查询成功", c)
}
func (r *WarehouseApi) CreateWarehouse(c *gin.Context) {
var req systemReq.CreateWarehouseRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "创建仓库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
id, err := warehouseService.CreateWarehouse(req, userInfo.AboutID, userInfo.Username, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "创建仓库异常", err, c, req)
return
}
systemRes.OkWithDetailed(gin.H{"id": id}, "创建成功", c)
}
func (r *WarehouseApi) UpdateWarehouse(c *gin.Context) {
var req systemReq.UpdateWarehouseRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "更新仓库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := warehouseService.UpdateWarehouse(req, userInfo.AboutID, userInfo.Username, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "更新仓库异常", err, c, req)
return
}
systemRes.OkWithMessage("更新成功", c)
}
func (r *WarehouseApi) DeleteWarehouse(c *gin.Context) {
var req systemReq.DeleteWarehouseRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "删除仓库请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
if err := warehouseService.DeleteWarehouse(req.ID, userInfo.AboutID, userInfo.Username, database.GetDB(c)); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "删除仓库异常", err, c, req)
return
}
systemRes.OkWithMessage("删除成功", c)
}
func (r *LocationApi) LocationToCsv(c *gin.Context) {
var req systemReq.ExportLocationRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "导出库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
result, err := locationService.ExportLocations(req, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "导出库位异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "导出成功", c)
}
func (r *LocationApi) CsvToLocation(c *gin.Context) {
var req systemReq.ImportLocationRequest
if err := c.ShouldBind(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "导入库位请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
file, err := c.FormFile("file")
if err != nil {
systemRes.FailWithValidateMessage("请上传文件", c)
return
}
if !strings.HasSuffix(strings.ToLower(file.Filename), ".xlsx") && !strings.HasSuffix(strings.ToLower(file.Filename), ".xls") {
systemRes.FailWithValidateMessage("只支持Excel文件格式(.xlsx, .xls)", c)
return
}
filePath := fmt.Sprintf("excel/import_%s_%d.xlsx", time.Now().Format("20060102150405"), time.Now().UnixNano())
if err := c.SaveUploadedFile(file, filePath); err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "保存上传文件异常", err, c, gin.H{"filename": file.Filename})
return
}
defer func() {
if err := os.Remove(filePath); err != nil {
utils.ErrorLog(constant.LoggerChannelWork, logrus.Fields{
"source": "删除临时文件失败",
"err_msg": err.Error(),
})
}
}()
result, err := locationService.ImportLocationsFromCSV(req, filePath, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "导入库位异常", err, c, gin.H{"filename": file.Filename})
return
}
systemRes.OkWithDetailed(result, result.Message, c)
}
func (r *WarehouseApi) GetUserWarehouseMappings(c *gin.Context) {
data, err := warehouseService.GetUserWarehouseMappings()
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "获取用户的仓库映射列表异常", err, c, nil)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 0,
"data": data,
"msg": "查询成功",
})
}

83
controllers/wave.go Normal file
View File

@ -0,0 +1,83 @@
package controllers
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/constant"
"psi/database"
systemReq "psi/models/request"
systemRes "psi/models/response"
"psi/service"
"psi/utils"
"strconv"
)
type WaveApi struct{}
var waveService = service.WaveService{}
// GetWaveTaskList 获取波次任务列表
func (r *WaveApi) GetWaveTaskList(c *gin.Context) {
var req systemReq.GetWaveTaskListRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "波次任务列表请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := waveService.GetWaveTaskList(req, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "波次任务列表异常", err, c, req)
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": result,
})
}
// GetWaveTaskDetail 获取波次任务详情
func (r *WaveApi) GetWaveTaskDetail(c *gin.Context) {
var req systemReq.GetWaveTaskDetailRequest
if err := c.ShouldBindQuery(&req); err != nil {
ValidAndFail(constant.LoggerChannelRequest, "波次任务详情请求参数异常", "参数错误: "+err.Error(), c, err)
return
}
userInfo := utils.GetUserInfo(c)
result, err := waveService.GetWaveTaskDetail(req.ID, userInfo.ID, userInfo.Role, database.GetDB(c))
if err != nil {
utils.FailWithRequestLog(constant.LoggerChannelWork, "波次任务详情异常", err, c, req)
return
}
systemRes.OkWithDetailed(result, "查询成功", c)
}
func (r *WaveApi) GetWaveStatusById(c *gin.Context) {
waveIDStr := c.Query("id")
if waveIDStr == "" {
systemRes.FailWithValidateMessage("参数错误: id不能为空", c)
return
}
waveID, err := strconv.ParseInt(waveIDStr, 10, 64)
if err != nil || waveID <= 0 {
systemRes.FailWithValidateMessage("参数错误: id格式不正确", c)
return
}
waveTask, err := waveService.GetWaveStatusById(waveID, database.GetDB(c))
if err != nil {
systemRes.FailWithMessage(err.Error(), c)
return
}
systemRes.OkWithDetailed(gin.H{
"id": waveTask.ID,
"status": waveTask.Status,
}, "查询成功", c)
}

45
database/db_helper.go Normal file
View File

@ -0,0 +1,45 @@
package database
import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// GetTenantDBFromContext 从 gin.Context 获取租户 DB
// 已弃用,请使用 GetDB
func GetTenantDBFromContext(aboutID int64) (*gorm.DB, error) {
return GetTenantDB(aboutID)
}
// GetDB 从 gin.Context 获取当前应使用的数据库连接
// 如果上下文中有租户DB则返回租户DB否则返回主库
// 使用方式: db := database.GetDB(c)
func GetDB(c *gin.Context) *gorm.DB {
if c == nil {
return DB
}
if tenantDB, exists := c.Get("tenant_db"); exists {
if db, ok := tenantDB.(*gorm.DB); ok {
return db
}
}
return DB
}
// GetDBWithFallback 获取数据库连接,支持可选的 DB 参数覆盖
// 使用方式: db := database.GetDBWithFallback(c, dbOverride)
func GetDBWithFallback(c *gin.Context, dbOverride *gorm.DB) *gorm.DB {
if dbOverride != nil {
return dbOverride
}
return GetDB(c)
}
// OptionalDB 可选 DB 参数处理
// 使用方式: db := database.OptionalDB(db...)
func OptionalDB(db ...*gorm.DB) *gorm.DB {
if len(db) > 0 && db[0] != nil {
return db[0]
}
return DB
}

301
database/mysql.go Normal file
View File

@ -0,0 +1,301 @@
package database
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"psi/config"
"psi/models"
"psi/utils"
"time"
)
var DB *gorm.DB
func Init() {
cfg := config.AppConfig
var err error
// 使用MySQL驱动连接数据库开启慢查询日志>=200ms
slowLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond, // 慢SQL阈值
LogLevel: logger.Warn, // 只输出警告和错误
IgnoreRecordNotFoundError: true, // 忽略RecordNotFound
Colorful: false,
},
)
DB, err = gorm.Open(mysql.Open(cfg.GetDSN()), &gorm.Config{
Logger: slowLogger,
})
if err != nil {
log.Fatal("数据库连接失败:", err)
}
// 获取底层的 sql.DB 对象以配置连接池
sqlDB, err := DB.DB()
if err != nil {
log.Fatal("获取数据库连接失败:", err)
}
// 设置连接池参数
sqlDB.SetMaxIdleConns(50) // 最大空闲连接数(提升以减少连接建立开销)
sqlDB.SetMaxOpenConns(200) // 最大打开连接数(提升以应对高并发)
sqlDB.SetConnMaxLifetime(30 * time.Minute) // 连接最大生命周期(30分钟)
sqlDB.SetConnMaxIdleTime(5 * time.Minute) // 连接最大空闲时间(5分钟减少频繁创建连接)
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表'").AutoMigrate(&models.Employee{})
if err != nil {
log.Fatal("Employee表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='书籍信息表'").AutoMigrate(&models.BookInfo{})
if err != nil {
log.Fatal("BookInfo表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表'").AutoMigrate(&models.Customer{})
if err != nil {
log.Fatal("Customer表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存表'").AutoMigrate(&models.Inventory{})
if err != nil {
log.Fatal("Inventory表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存明细表'").AutoMigrate(&models.InventoryDetail{})
if err != nil {
log.Fatal("InventoryDetail表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表'").AutoMigrate(&models.InventoryLog{})
if err != nil {
log.Fatal("InventoryLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库位表'").AutoMigrate(&models.Location{})
if err != nil {
log.Fatal("Location表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品分类表'").AutoMigrate(&models.ProductCategory{})
if err != nil {
log.Fatal("ProductCategory表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品表'").AutoMigrate(&models.Product{})
if err != nil {
log.Fatal("Product表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品单位转换表'").AutoMigrate(&models.ProductUnitConversion{})
if err != nil {
log.Fatal("ProductUnitConversion表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品序列号表'").AutoMigrate(&models.ProductSerial{})
if err != nil {
log.Fatal("ProductSerial表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购订单主表'").AutoMigrate(&models.PurchaseOrder{})
if err != nil {
log.Fatal("PurchaseOrder表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购订单明细表'").AutoMigrate(&models.PurchaseOrderItem{})
if err != nil {
log.Fatal("PurchaseOrderItem表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印任务表'").AutoMigrate(&models.PrintTask{})
if err != nil {
log.Fatal("PrintTask表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印日志表'").AutoMigrate(&models.PrintLog{})
if err != nil {
log.Fatal("PrintLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库单主表'").AutoMigrate(&models.ReceivingOrder{})
if err != nil {
log.Fatal("ReceivingOrder表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库单明细表'").AutoMigrate(&models.ReceivingOrderItem{})
if err != nil {
log.Fatal("ReceivingOrderItem表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商表'").AutoMigrate(&models.Supplier{})
if err != nil {
log.Fatal("Supplier表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单主表'").AutoMigrate(&models.SalesOrder{})
if err != nil {
log.Fatal("SalesOrder表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单明细表'").AutoMigrate(&models.SalesOrderItem{})
if err != nil {
log.Fatal("SalesOrderItem表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仓库表'").AutoMigrate(&models.Warehouse{})
if err != nil {
log.Fatal("Warehouse表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='波次单主表'").AutoMigrate(&models.WaveHeader{})
if err != nil {
log.Fatal("WaveHeader表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='波次任务表'").AutoMigrate(&models.WaveTask{})
if err != nil {
log.Fatal("WaveTask表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='波次任务明细表'").AutoMigrate(&models.WaveTaskDetail{})
if err != nil {
log.Fatal("WaveTaskDetail表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小车表'").AutoMigrate(&models.Car{})
if err != nil {
log.Fatal("Car表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小车店铺关联表'").AutoMigrate(&models.CarShop{})
if err != nil {
log.Fatal("CarShop表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店铺表'").AutoMigrate(&models.Shop{})
if err != nil {
log.Fatal("Shop表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工等级表'").AutoMigrate(&models.EmployeeLevel{})
if err != nil {
log.Fatal("EmployeeLevel表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工等级日志表'").AutoMigrate(&models.EmployeeLevelLog{})
if err != nil {
log.Fatal("EmployeeLevelLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工配置表'").AutoMigrate(&models.EmployeeSettings{})
if err != nil {
log.Fatal("EmployeeSettings表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='盘库单主表'").AutoMigrate(&models.StockCheck{})
if err != nil {
log.Fatal("StockCheck表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='盘库单明细表'").AutoMigrate(&models.StockCheckItem{})
if err != nil {
log.Fatal("StockCheckItem表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部任务表'").AutoMigrate(&models.OutTask{})
if err != nil {
log.Fatal("OutTask表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部任务异常日志表'").AutoMigrate(&models.OutTaskLog{})
if err != nil {
log.Fatal("OutTaskLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单主表'").AutoMigrate(&models.OutboundOrder{})
if err != nil {
log.Fatal("OutboundOrder表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单明细表'").AutoMigrate(&models.OutboundOrderItem{})
if err != nil {
log.Fatal("OutboundOrderItem表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发货单主表'").AutoMigrate(&models.ShippingOrder{})
if err != nil {
log.Fatal("ShippingOrder表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发货单明细表'").AutoMigrate(&models.ShippingOrderItem{})
if err != nil {
log.Fatal("ShippingOrderItem表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运费模板表'").AutoMigrate(&models.Logistics{})
if err != nil {
log.Fatal("Logistics表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品日志表'").AutoMigrate(&models.ProductLog{})
if err != nil {
log.Fatal("ProductLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户类型表'").AutoMigrate(&models.UserType{})
if err != nil {
log.Fatal("UserType表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户映射表'").AutoMigrate(&models.UserMapping{})
if err != nil {
log.Fatal("UserMapping表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分账配置表'").AutoMigrate(&models.SplitAccountConfig{})
if err != nil {
log.Fatal("SplitAccountConfig表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分账扣钱日志表'").AutoMigrate(&models.SplitAccountDeductionLog{})
if err != nil {
log.Fatal("SplitAccountDeductionLog表迁移失败:", err)
}
err = DB.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置表'").AutoMigrate(&models.Config{})
if err != nil {
log.Fatal("Config表迁移失败:", err)
}
createDefaultAdmin()
InitTaskDB()
log.Println("MySQL数据库连接成功:", cfg.Database.Host, cfg.Database.Name)
}
func createDefaultAdmin() {
var count int64
DB.Model(&models.Employee{}).Where("role = ?", 255).Count(&count)
if count == 0 {
// 创建默认管理员账号账号init_00000密码admin123
hashedPassword, _ := utils.HashPassword("admin123")
admin := models.Employee{
EmployeeIDStr: "00000",
Username: "init_00000",
Password: hashedPassword,
Name: "系统管理员",
Role: 255,
Score: 0,
Status: 1,
Fid: 0,
AboutId: 0,
From: "system",
CreatedAt: time.Now().Unix(),
UpdatedAt: time.Now().Unix(),
}
DB.Create(&admin)
log.Println("已创建默认管理员账号: init_00000 / admin123")
}
}

48
database/task.go Normal file
View File

@ -0,0 +1,48 @@
package database
import (
"log"
"psi/config"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// TaskDB 任务数据库长连接(腾讯云 MySQL用于导入后写 t_shop_goods_published
var TaskDB *gorm.DB
func InitTaskDB() {
cfg := config.AppConfig
slowLogger := logger.New(
log.New(log.Writer(), "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Warn,
IgnoreRecordNotFoundError: true,
Colorful: false,
},
)
var err error
TaskDB, err = gorm.Open(mysql.Open(cfg.GetTaskDSN()), &gorm.Config{
Logger: slowLogger,
})
if err != nil {
log.Fatal("任务数据库连接失败:", err)
}
// 配置连接池
sqlDB, err := TaskDB.DB()
if err != nil {
log.Fatal("获取任务数据库连接池失败:", err)
}
sqlDB.SetMaxIdleConns(10) // 最大空闲连接
sqlDB.SetMaxOpenConns(50) // 最大打开连接
sqlDB.SetConnMaxLifetime(10 * time.Minute) // 连接最大存活10分钟
sqlDB.SetConnMaxIdleTime(2 * time.Minute) // 空闲连接2分钟后关闭
log.Println("任务数据库连接成功:", cfg.TaskDatabase.Host, cfg.TaskDatabase.Name)
}

415
database/tenant.go Normal file
View File

@ -0,0 +1,415 @@
package database
import (
"fmt"
"log"
"psi/config"
"psi/models"
"strings"
"sync"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// TenantManager 租户数据库管理器
type TenantManager struct {
connections map[int64]*gorm.DB // about_id -> DB连接
mu sync.RWMutex
}
var tenantManager *TenantManager
func init() {
tenantManager = &TenantManager{
connections: make(map[int64]*gorm.DB),
}
}
// GetTenantDB 获取租户数据库连接(如果不存在则创建)
func GetTenantDB(aboutID int64) (*gorm.DB, error) {
if aboutID == 0 {
return DB, nil // 主库
}
tenantManager.mu.RLock()
conn, exists := tenantManager.connections[aboutID]
tenantManager.mu.RUnlock()
if exists {
// 增量迁移:确保现有连接也有新表
migrateIncrementalTenantTables(conn)
return conn, nil
}
// 需要创建新连接
tenantManager.mu.Lock()
defer tenantManager.mu.Unlock()
// 双重检查
if conn, exists = tenantManager.connections[aboutID]; exists {
migrateIncrementalTenantTables(conn)
return conn, nil
}
// 创建租户数据库连接
dbName := fmt.Sprintf("psi_%d", aboutID)
conn, err := createTenantDB(dbName)
if err != nil {
return nil, err
}
tenantManager.connections[aboutID] = conn
log.Printf("租户数据库连接创建成功: %s", dbName)
return conn, nil
}
// createTenantDB 创建租户数据库(如果不存在则新建库和表)
func createTenantDB(dbName string) (*gorm.DB, error) {
cfg := config.AppConfig
// 先用主库连接检查/创建租户数据库
mainDSN := cfg.GetDSN()
mainDB, err := gorm.Open(mysql.Open(mainDSN), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("连接主库失败: %w", err)
}
// 检查数据库是否存在
var exists string
err = mainDB.Raw(fmt.Sprintf("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '%s'", dbName)).Scan(&exists).Error
needCreate := err != nil || exists == ""
// 创建数据库(如果不存在)
if needCreate {
createSQL := fmt.Sprintf("CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName)
if err = mainDB.Exec(createSQL).Error; err != nil {
// 如果数据库已存在Error 1007忽略该错误
if strings.Contains(err.Error(), "1007") {
log.Printf("租户数据库已存在: %s", dbName)
} else {
sqlMainDB, _ := mainDB.DB()
sqlMainDB.Close()
return nil, fmt.Errorf("创建租户数据库失败: %w", err)
}
} else {
log.Printf("租户数据库创建成功: %s", dbName)
}
}
sqlMainDB, _ := mainDB.DB()
sqlMainDB.Close()
// 连接租户数据库
tenantDSN := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s&readTimeout=30s&writeTimeout=30s&interpolateParams=true&multiStatements=true&allowNativePasswords=true&checkConnLiveness=true",
cfg.Database.User, cfg.Database.Password, cfg.Database.Host, cfg.Database.Port, dbName)
tenantDB, err := gorm.Open(mysql.Open(tenantDSN), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("连接租户数据库失败: %w", err)
}
// 配置连接池
sqlDB, err := tenantDB.DB()
if err != nil {
return nil, fmt.Errorf("获取租户数据库连接池失败: %w", err)
}
sqlDB.SetMaxIdleConns(20) // 最大空闲连接数
sqlDB.SetMaxOpenConns(100) // 最大打开连接数
sqlDB.SetConnMaxLifetime(10 * time.Minute) // 单个连接最大存活10分钟
sqlDB.SetConnMaxIdleTime(2 * time.Minute) // 空闲连接2分钟后关闭
migrateTenantTables(tenantDB)
return tenantDB, nil
}
// migrateTenantTables 迁移租户业务表
func migrateTenantTables(db *gorm.DB) {
// 迁移所有业务表(不包括 Employee 表Employee 在主库)
err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表'").AutoMigrate(&models.Customer{})
if err != nil {
log.Printf("Customer表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存汇总表'").AutoMigrate(&models.Inventory{})
if err != nil {
log.Printf("Inventory表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存明细表(库位级)'").AutoMigrate(&models.InventoryDetail{})
if err != nil {
log.Printf("InventoryDetail表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表'").AutoMigrate(&models.InventoryLog{})
if err != nil {
log.Printf("InventoryLog表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库位表'").AutoMigrate(&models.Location{})
if err != nil {
log.Printf("Location表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'").AutoMigrate(&models.ProductCategory{})
if err != nil {
log.Printf("ProductCategory表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'").AutoMigrate(&models.Product{})
if err != nil {
log.Printf("Product表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品单位换算表'").AutoMigrate(&models.ProductUnitConversion{})
if err != nil {
log.Printf("ProductUnitConversion表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='产品序列号表'").AutoMigrate(&models.ProductSerial{})
if err != nil {
log.Printf("ProductSerial表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购订单主表'").AutoMigrate(&models.PurchaseOrder{})
if err != nil {
log.Printf("PurchaseOrder表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购订单明细表'").AutoMigrate(&models.PurchaseOrderItem{})
if err != nil {
log.Printf("PurchaseOrderItem表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库单主表'").AutoMigrate(&models.ReceivingOrder{})
if err != nil {
log.Printf("ReceivingOrder表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库单明细表'").AutoMigrate(&models.ReceivingOrderItem{})
if err != nil {
log.Printf("ReceivingOrderItem表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商表'").AutoMigrate(&models.Supplier{})
if err != nil {
log.Printf("Supplier表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单主表'").AutoMigrate(&models.SalesOrder{})
if err != nil {
log.Printf("SalesOrder表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售订单明细表'").AutoMigrate(&models.SalesOrderItem{})
if err != nil {
log.Printf("SalesOrderItem表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='仓库表'").AutoMigrate(&models.Warehouse{})
if err != nil {
log.Printf("Warehouse表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='波次单主表'").AutoMigrate(&models.WaveHeader{})
if err != nil {
log.Printf("WaveHeader表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='波次任务表'").AutoMigrate(&models.WaveTask{})
if err != nil {
log.Printf("WaveTask表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='波次任务明细表'").AutoMigrate(&models.WaveTaskDetail{})
if err != nil {
log.Printf("WaveTaskDetail表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印任务表'").AutoMigrate(&models.PrintTask{})
if err != nil {
log.Printf("PrintTask表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印日志表'").AutoMigrate(&models.PrintLog{})
if err != nil {
log.Printf("PrintLog表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小车表'").AutoMigrate(&models.Car{})
if err != nil {
log.Printf("Car表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小车店铺关联表'").AutoMigrate(&models.CarShop{})
if err != nil {
log.Printf("CarShop表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='店铺表'").AutoMigrate(&models.Shop{})
if err != nil {
log.Printf("Shop表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='盘库单主表'").AutoMigrate(&models.StockCheck{})
if err != nil {
log.Printf("StockCheck表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='盘库单明细表'").AutoMigrate(&models.StockCheckItem{})
if err != nil {
log.Printf("StockCheckItem表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部任务表'").AutoMigrate(&models.OutTask{})
if err != nil {
log.Printf("OutTask表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部任务异常日志表'").AutoMigrate(&models.OutTaskLog{})
if err != nil {
log.Printf("OutTaskLog表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单主表'").AutoMigrate(&models.OutboundOrder{})
if err != nil {
log.Printf("OutboundOrder表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单明细表'").AutoMigrate(&models.OutboundOrderItem{})
if err != nil {
log.Printf("OutboundOrderItem表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发货单主表'").AutoMigrate(&models.ShippingOrder{})
if err != nil {
log.Printf("ShippingOrder表迁移失败: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发货单明细表'").AutoMigrate(&models.ShippingOrderItem{})
if err != nil {
log.Printf("ShippingOrderItem表迁移失败: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运费模板表'").AutoMigrate(&models.Logistics{})
if err != nil {
log.Printf("Logistics表迁移失败: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='统计表'").AutoMigrate(&models.Statist{})
if err != nil {
log.Printf("Statist表迁移失败: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分账配置表'").AutoMigrate(&models.SplitAccountConfig{})
if err != nil {
log.Printf("SplitAccountConfig表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分账扣钱日志表'").AutoMigrate(&models.SplitAccountDeductionLog{})
if err != nil {
log.Printf("SplitAccountDeductionLog表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单库位变更记录表'").AutoMigrate(&models.OutboundOrderLocationLog{})
if err != nil {
log.Printf("OutboundOrderLocationLog表迁移警告: %v", err)
}
err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置表'").AutoMigrate(&models.Config{})
if err != nil {
log.Printf("Config表迁移警告: %v", err)
}
log.Println("租户业务表迁移完成")
//tableOptions := "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
//
//modelsToMigrate := []interface{}{
// //&models.BookInfo{},
// &models.Customer{},
// &models.Inventory{},
// &models.InventoryDetail{},
// &models.InventoryLog{},
// &models.Location{},
// &models.ProductCategory{},
// &models.Product{},
// &models.ProductUnitConversion{},
// &models.ProductSerial{},
// &models.PurchaseOrder{},
// &models.PurchaseOrderItem{},
// &models.ReceivingOrder{},
// &models.ReceivingOrderItem{},
// &models.Supplier{},
// &models.SalesOrder{},
// &models.SalesOrderItem{},
// &models.Warehouse{},
// &models.WaveHeader{},
// &models.WaveTask{},
// &models.WaveTaskDetail{},
// &models.PrintTask{},
// &models.PrintLog{},
// &models.Car{},
// &models.CarShop{},
// &models.Shop{},
// &models.StockCheck{},
// &models.StockCheckItem{},
// &models.OutTask{},
// &models.OutTaskLog{},
// &models.OutboundOrder{},
// &models.OutboundOrderItem{},
//}
//
//for _, model := range modelsToMigrate {
// err := db.Set("gorm:table_options", tableOptions).AutoMigrate(model)
// if err != nil {
// log.Printf("租户表迁移警告: %v", err)
// }
//}
}
// CloseTenantConnections 关闭所有租户连接(用于优雅关闭)
func CloseTenantConnections() {
tenantManager.mu.Lock()
defer tenantManager.mu.Unlock()
for aboutID, conn := range tenantManager.connections {
sqlDB, err := conn.DB()
if err == nil {
sqlDB.Close()
}
delete(tenantManager.connections, aboutID)
}
log.Println("所有租户数据库连接已关闭")
}
// migrateIncrementalTenantTables 增量迁移:补充已有租户数据库可能存在的新表
func migrateIncrementalTenantTables(db *gorm.DB) {
// 检查 split_account_config 表是否存在,不存在则创建
if !db.Migrator().HasTable(&models.SplitAccountConfig{}) {
err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分账配置表'").AutoMigrate(&models.SplitAccountConfig{})
if err != nil {
log.Printf("SplitAccountConfig表增量迁移警告: %v", err)
} else {
log.Println("SplitAccountConfig表增量迁移成功")
}
}
if !db.Migrator().HasTable(&models.SplitAccountDeductionLog{}) {
err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分账扣钱日志表'").AutoMigrate(&models.SplitAccountDeductionLog{})
if err != nil {
log.Printf("SplitAccountDeductionLog表增量迁移警告: %v", err)
} else {
log.Println("SplitAccountDeductionLog表增量迁移成功")
}
}
if !db.Migrator().HasTable(&models.Config{}) {
err := db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置表'").AutoMigrate(&models.Config{})
if err != nil {
log.Printf("Config表增量迁移警告: %v", err)
} else {
log.Println("Config表增量迁移成功")
}
}
}

162
es/es.go Normal file
View File

@ -0,0 +1,162 @@
package es
import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/elastic/go-elasticsearch/v8"
"net/http"
"strconv"
"strings"
"time"
)
// BookPicObj 用于 book_pic
type BookPicObj struct {
LocalPath string `json:"localPath"`
PddPath string `json:"pddPath"`
}
// BookPicSObj 用于 book_pic_s
type BookPicSObj struct {
LocalPath string `json:"localPath"`
PddResponse string `json:"pddResponse"`
}
// BookDefPicObj 用于 book_def_pic
type BookDefPicObj struct {
LocalPath string `json:"localPath"`
PddPath string `json:"pddPath"`
}
type CatIdObject struct {
PinDuoDuoCatId string `json:"pin_duo_duo_cat_id"` // 拼多多分类 ID
KongFuZiCatId string `json:"kong_fu_zi_cat_id"` // 孔夫子分类 ID
XianYuCatId string `json:"xian_yu_cat_id"` // 闲鱼分类 ID
}
// FlexibleString 处理可能是字符串或数组的字段
type FlexibleString struct {
Value string
}
// MarshalJSON 实现自定义 JSON 序列化
func (f FlexibleString) MarshalJSON() ([]byte, error) {
return json.Marshal(f.Value)
}
// UnmarshalJSON 实现自定义 JSON 反序列化
func (f *FlexibleString) UnmarshalJSON(data []byte) error {
var value string
if err := json.Unmarshal(data, &value); err != nil {
return err
}
f.Value = value
return nil
}
type NumberOrString string
func (n *NumberOrString) UnmarshalJSON(data []byte) error {
s := strings.TrimSpace(string(data))
// 如果是数字(不以引号开头)
if len(s) > 0 && s[0] != '"' {
*n = NumberOrString(s)
return nil
}
// 普通字符串
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
*n = NumberOrString(str)
return nil
}
type Float64OrString float64
func (f *Float64OrString) UnmarshalJSON(b []byte) error {
// 去掉空值
if string(b) == "null" || len(b) == 0 {
*f = 0
return nil
}
// 尝试解析为 float
var num float64
if err := json.Unmarshal(b, &num); err == nil {
*f = Float64OrString(num)
return nil
}
// 尝试解析为 string
var str string
if err := json.Unmarshal(b, &str); err == nil {
if str == "" {
*f = 0
return nil
}
parsed, err := strconv.ParseFloat(str, 64)
if err != nil {
return err
}
*f = Float64OrString(parsed)
return nil
}
return fmt.Errorf("无法解析 fix_price: %s", string(b))
}
// ESClient 封装 Elasticsearch 客户端
type ESClient struct {
Client *elasticsearch.Client
}
var DefaultClient *ESClient
// Init 初始化默认 ES 客户端
func Init(addresses []string, username, password string) error {
client, err := NewESClient(addresses, username, password)
if err != nil {
return err
}
DefaultClient = client
return nil
}
// NewESClient 初始化客户端
func NewESClient(addresses []string, username, password string) (*ESClient, error) {
cfg := elasticsearch.Config{
Addresses: addresses,
Username: username,
Password: password,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
MaxIdleConnsPerHost: 100,
ResponseHeaderTimeout: 60 * time.Second,
},
CompressRequestBody: true,
}
client, err := elasticsearch.NewClient(cfg)
if err != nil {
return nil, fmt.Errorf("创建 ES 客户端失败: %v", err)
}
res, err := client.Info()
if err != nil {
return nil, fmt.Errorf("ES 连接失败: %v", err)
}
defer res.Body.Close()
if res.IsError() {
return nil, fmt.Errorf("ES 返回错误: %s", res.String())
}
fmt.Println("✅ Elasticsearch 连接成功")
return &ESClient{Client: client}, nil
}

Binary file not shown.

70
go.mod Normal file
View File

@ -0,0 +1,70 @@
module psi
go 1.25.7
require (
github.com/boombuler/barcode v1.1.0
github.com/elastic/go-elasticsearch/v8 v8.19.3
github.com/gin-gonic/gin v1.11.0
github.com/go-playground/validator/v10 v10.30.1
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.4
github.com/xuri/excelize/v2 v2.10.1
golang.org/x/crypto v0.48.0
golang.org/x/image v0.39.0
gopkg.in/yaml.v3 v3.0.1
gorm.io/datatypes v1.2.7
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.31.1
)
require (
filippo.io/edwards25519 v1.2.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/elastic/elastic-transport-go/v8 v8.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/strftime v1.1.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/richardlehane/mscfb v1.0.6 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
github.com/tiendc/go-deepcopy v1.7.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/xuri/efp v0.0.1 // indirect
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.uber.org/mock v0.6.0 // indirect
golang.org/x/arch v0.24.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.36.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

183
go.sum Normal file
View File

@ -0,0 +1,183 @@
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elastic/elastic-transport-go/v8 v8.8.0 h1:7k1Ua+qluFr6p1jfJjGDl97ssJS/P7cHNInzfxgBQAo=
github.com/elastic/elastic-transport-go/v8 v8.8.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk=
github.com/elastic/go-elasticsearch/v8 v8.19.3 h1:5LDg0hfGJXBa9Y+2QlUgRTsNJ/7rm7oNidydtFAq0LI=
github.com/elastic/go-elasticsearch/v8 v8.19.3/go.mod h1:tHJQdInFa6abmDbDCEH2LJja07l/SIpaGpJcm13nt7s=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.1.1 h1:zgf8QCsgj27GlKBy3SU9/8MMgegZ8UCzlCyHYrUF0QU=
github.com/lestrrat-go/strftime v1.1.1/go.mod h1:YDrzHJAODYQ+xxvrn5SG01uFIQAeDTzpxNVppCz7Nmw=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44=
github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0=
github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y=
golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo=
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc=
gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

Binary file not shown.

43
main.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"log"
"psi/config"
"psi/database"
router "psi/routes"
"psi/utils"
)
func main() {
log.Println("=== PSI 服务启动中 ===")
// 初始化配置
log.Println("[1/4] 初始化配置...")
config.Init()
log.Println("[1/4] 配置初始化完成")
// 初始化日志
log.Println("[2/4] 初始化日志...")
if err := utils.InitLogger(); err != nil {
log.Printf("[2/4] 日志初始化失败: %v", err)
} else {
log.Println("[2/4] 日志初始化完成")
}
// 初始化数据库
log.Println("[3/4] 初始化数据库...")
database.Init()
log.Println("[3/4] 数据库初始化完成")
// 初始化 Elasticsearch
//esConfig := config.AppConfig.ES
//_ = es.Init([]string{esConfig.Host}, esConfig.Username, esConfig.Password)
// 启动 OCR 服务
//go func() {
// ocr.StartService()
//}()
// 设置路由并启动服务器
log.Printf("[4/4] 启动服务器,端口: %s...", config.AppConfig.Server.Port)
router.Run()
}

116
middleware/auth.go Normal file
View File

@ -0,0 +1,116 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
"psi/database"
"psi/models"
"psi/utils"
"strings"
)
// JWTAuth JWT认证中间件
func JWTAuth() gin.HandlerFunc {
// 处理JWT认证
return func(c *gin.Context) {
// 检查认证头
authHeader := c.GetHeader("Authorization")
// 检查认证头是否为空
if authHeader == "" {
// 返回错误
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"})
// 中断处理
c.Abort()
// 返回
return
}
// 提取令牌
parts := strings.SplitN(authHeader, " ", 2)
// 处理错误
if !(len(parts) == 2 && parts[0] == "Bearer") {
// 返回错误
c.JSON(http.StatusUnauthorized, gin.H{"error": "认证格式错误"})
// 中断处理
c.Abort()
// 返回
return
}
// 解析JWT
claims, err := utils.ParseJWT(parts[1])
// 处理错误
if err != nil {
// 返回错误
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的认证令牌"})
// 中断处理
c.Abort()
// 返回
return
}
// 判断是员工还是用户
if claims.ID > 0 {
// 检查员工状态
var employee models.Employee
// 查询员工
if err := database.DB.Where("id = ? AND status = ? AND deleted_at = ?", claims.ID, 1, 0).First(&employee).Error;
// 处理错误
err != nil {
// 返回错误
c.JSON(http.StatusUnauthorized, gin.H{"error": "账号不存在或已被禁用"})
// 中断处理
c.Abort()
// 返回
return
}
// 将员工信息存入上下文
c.Set("id", employee.ID) // 设置员工ID
c.Set("role", employee.Role) // 设置角色
c.Set("username", employee.Username) // 设置用户名
c.Set("employee", employee) // 添加员工信息
c.Set("about_id", claims.AboutID) // 租户ID
c.Set("fid", employee.Fid) // 父级ID
// 如果有租户ID获取租户数据库连接并存入上下文
if claims.AboutID > 0 {
tenantDB, err := database.GetTenantDB(claims.AboutID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "租户数据库连接失败"})
c.Abort()
return
}
c.Set("tenant_db", tenantDB)
}
} else {
// 无效的身份
c.JSON(http.StatusUnauthorized, gin.H{"error": "无效的身份标识"})
// 中断处理
c.Abort()
// 返回
return
}
// 继续处理
c.Next()
}
}
// AdminRequired 管理员权限
func AdminRequired() gin.HandlerFunc {
// 处理管理员权限
return func(c *gin.Context) {
// 检查角色
role, exists := c.Get("role")
// 处理错误
if !exists || role.(int64) != 255 {
// 返回错误
c.JSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
// 中断处理
c.Abort()
// 返回
return
}
// 继续处理
c.Next()
}
}

24
middleware/cors.go Normal file
View File

@ -0,0 +1,24 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
// Cors 跨域中间件
func Cors() gin.HandlerFunc {
// 处理CORS请求
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*") // 允许所有源
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") // 允许携带cookie
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") // 允许的请求头
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH") // 允许的请求方法
// 处理OPTIONS请求
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204) // 返回204
return
}
// 处理正常请求
c.Next()
}
}

260
middleware/sign.go Normal file
View File

@ -0,0 +1,260 @@
package middleware
import (
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"psi/config"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
// APISign API签名验证中间件
func APISign() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取签名相关参数
appKey := c.Query("app_key")
clientId := c.Query("client_id")
sign := c.Query("sign")
signMethod := c.Query("sign_method")
timestamp := c.Query("timestamp")
if appKey == "" {
appKey = c.PostForm("app_key")
}
if clientId == "" {
clientId = c.PostForm("client_id")
}
if sign == "" {
sign = c.PostForm("sign")
}
if signMethod == "" {
signMethod = c.PostForm("sign_method")
}
if timestamp == "" {
timestamp = c.PostForm("timestamp")
}
// 验证必填参数
if appKey == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "缺少app_key参数",
})
c.Abort()
return
}
if clientId == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "缺少client_id参数",
})
c.Abort()
return
}
if sign == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "缺少sign参数",
})
c.Abort()
return
}
if timestamp == "" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "缺少timestamp参数",
})
c.Abort()
return
}
// 验证app_key
if appKey != config.AppConfig.APISign.AppKey {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "无效的app_key",
})
c.Abort()
return
}
// 验证client_id
if clientId != config.AppConfig.APISign.ClientId {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "无效的client_id",
})
c.Abort()
return
}
// 验证时间戳(防止重放攻击)
ts, err := strconv.ParseInt(timestamp, 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "时间戳格式错误",
})
c.Abort()
return
}
currentTime := time.Now().Unix()
timeDiff := currentTime - ts
// 检查时间戳是否在允许范围内默认300秒
tolerance := int64(config.AppConfig.APISign.TimestampTolerance)
if tolerance <= 0 {
tolerance = 300 // 默认5分钟
}
if timeDiff > tolerance || timeDiff < -tolerance {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "请求已过期,请检查系统时间",
})
c.Abort()
return
}
// 验证签名方法
if signMethod == "" {
signMethod = config.AppConfig.APISign.SignMethod
}
if signMethod != "md5" && signMethod != "sha256" {
c.JSON(http.StatusBadRequest, gin.H{
"error": "不支持的签名方法",
})
c.Abort()
return
}
// 计算签名
calculatedSign, err := calculateSign(c, signMethod)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "签名计算失败",
})
c.Abort()
return
}
// 验证签名
if calculatedSign != sign {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "签名验证失败",
})
c.Abort()
return
}
c.Next()
}
}
// calculateSign 计算签名
func calculateSign(c *gin.Context, signMethod string) (string, error) {
// 收集所有参数
params := make(map[string]string)
// 获取URL查询参数
for key, values := range c.Request.URL.Query() {
// 跳过sign参数本身
if key == "sign" {
continue
}
if len(values) > 0 {
params[key] = values[0]
}
}
// 如果是POST、PUT请求获取表单参数
if c.Request.Method == "POST" || c.Request.Method == "PUT" || c.Request.Method == "DELETE" {
c.Request.ParseForm()
for key, values := range c.Request.PostForm {
// 跳过sign参数本身
if key == "sign" {
continue
}
if len(values) > 0 {
// 如果是数组参数,用逗号拼接所有值
if len(values) > 1 {
params[key] = strings.Join(values, ",")
} else {
params[key] = values[0]
}
}
}
}
// 提取所有键并排序
keys := make([]string, 0, len(params))
for key := range params {
keys = append(keys, key)
}
sortKeysWithIndex(keys)
// 构建签名字符串key1=value1&key2=value2...
var signStrings []string
for _, key := range keys {
signStrings = append(signStrings, fmt.Sprintf("%s=%s", key, params[key]))
}
signStr := strings.Join(signStrings, "&")
//fmt.Println("signStr: %s", signStr)
// 在字符串前后添加secret
appSecret := config.AppConfig.APISign.AppSecret
signStr = appSecret + signStr + appSecret
// 根据签名方法计算哈希
var hash []byte
switch signMethod {
case "md5":
md5Hash := md5.Sum([]byte(signStr))
hash = md5Hash[:]
case "sha256":
sha256Hash := sha256.Sum256([]byte(signStr))
hash = sha256Hash[:]
default:
return "", fmt.Errorf("不支持的签名方法: %s", signMethod)
}
// 转换为大写十六进制字符串
return strings.ToUpper(hex.EncodeToString(hash)), nil
}
// sortKeysWithIndex 对参数键进行智能排序,处理带索引的数组参数
func sortKeysWithIndex(keys []string) {
// 正则表达式匹配带索引的键,如 items[0][product_id]
indexPattern := regexp.MustCompile(`^(.+)\[(\d+)\](.*)$`)
sort.Slice(keys, func(i, j int) bool {
keyI := keys[i]
keyJ := keys[j]
matchI := indexPattern.FindStringSubmatch(keyI)
matchJ := indexPattern.FindStringSubmatch(keyJ)
// 如果两个都是带索引的键
if matchI != nil && matchJ != nil {
prefixI, idxIStr, suffixI := matchI[1], matchI[2], matchI[3]
prefixJ, idxJStr, suffixJ := matchJ[1], matchJ[2], matchJ[3]
// 先比较前缀(如 items
if prefixI != prefixJ {
return prefixI < prefixJ
}
// 前缀相同,比较索引(数值比较)
idxI, errI := strconv.Atoi(idxIStr)
idxJ, errJ := strconv.Atoi(idxJStr)
if errI == nil && errJ == nil {
if idxI != idxJ {
return idxI < idxJ
}
// 索引相同,比较后缀(如 [product_id]
return suffixI < suffixJ
}
}
// 至少有一个不是带索引的键,使用默认字典序
return keyI < keyJ
})
}

32
models/book_info.go Normal file
View File

@ -0,0 +1,32 @@
package models
import "gorm.io/datatypes"
// BookInfo 书籍信息表
type BookInfo struct {
ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
Fid int64 `json:"fid" gorm:"not null;default:0;comment:父级ID"`
Type int8 `json:"type" gorm:"not null;comment:类型 1正常 2套装书 3 一号多书 4无书号"`
ISBN string `json:"isbn" gorm:"size:20;not null;default:'';comment:ISBN;index:idx_isbn"`
FISBN string `json:"f_isbn" gorm:"size:20;not null;default:'';comment:FISBN;"`
BookName string `json:"book_name" gorm:"size:100;not null;default:'';comment:书名"`
FBookName string `json:"f_book_name" gorm:"size:100;not null;default:'';comment:副书名"`
Author string `json:"author" gorm:"size:100;not null;default:'';comment:作者"`
Publishing string `json:"publishing" gorm:"size:50;not null;default:'';comment:出版社"`
PublicationTime int64 `json:"publication_time" gorm:"type:bigint;not null;default:0;comment:出版日期时间戳"`
Binding string `json:"binding" gorm:"size:10;not null;default:'';comment:装帧"`
PagesCount int64 `json:"pages_count" gorm:"not null;default:0;comment:页数"`
WordsCount int64 `json:"words_count" gorm:"not null;default:0;comment:字数"`
Format int64 `json:"format" gorm:"not null;default:0;comment:开本"`
Price int64 `json:"price" gorm:"not null;default:0;comment:价格"`
CatID datatypes.JSON `json:"cat_id" gorm:"type:json;not null;comment:类目json"`
LiveImage datatypes.JSON `json:"live_image" gorm:"column:live_image;type:json;not null;comment:实拍图json"`
}
func (BookInfo) TableName() string {
return "book_info"
}
func (BookInfo) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='书籍信息表'"
}

24
models/car.go Normal file
View File

@ -0,0 +1,24 @@
package models
// Car 小车表
type Car struct {
ID int64 `json:"id" gorm:"primarykey;comment:小车ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;comment:所属仓库ID"`
PushType int8 `json:"push_type" gorm:"type:tinyint(1);not null;default:1;comment:推送类型(1:创建波次后随即上架,2:入库后在上架)"`
ReleaseType int8 `json:"release_type" gorm:"type:tinyint(1);not null;default:1;comment:发布类型(1:独立库存,2:合并库存)"`
Code int64 `json:"code" gorm:"not null;default:0;comment:小车编号"`
Name string `json:"name" gorm:"size:50;not null;default:'';comment:小车名称"`
Capacity int64 `json:"capacity" gorm:"not null;default:0;comment:容量"`
Appearance int64 `json:"appearance" gorm:"type:bigint(20) unsigned;not null;default:0;comment:品相"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (Car) TableName() string {
return "car"
}
func (Car) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小车表'"
}

24
models/car_shop.go Normal file
View File

@ -0,0 +1,24 @@
package models
// CarShop 小车店铺关联表
type CarShop struct {
ID int64 `json:"id" gorm:"primarykey;comment:关联ID"`
CarID int64 `json:"car_id" gorm:"not null;default:0;comment:小车id"`
ShopID int64 `json:"shop_id" gorm:"not null;default:0;comment:店铺id"`
ShopName string `json:"shop_name" gorm:"size:50;not null;default:'';comment:店铺名称"`
ShopType int8 `json:"shop_type" gorm:"type:tinyint(1);not null;default:0;comment:店铺类型"`
ClientID string `json:"client_id" gorm:"size:50;not null;comment:客户端id"`
AppKey string `json:"app_key" gorm:"size:255;not null;default:'';comment:app_key"`
AppSecret string `json:"app_secret" gorm:"size:255;not null;default:'';comment:app_secret"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (CarShop) TableName() string {
return "car_shop"
}
func (CarShop) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小车店铺关联表'"
}

19
models/config.go Normal file
View File

@ -0,0 +1,19 @@
package models
// Config 配置表
type Config struct {
ID int64 `json:"id" gorm:"primarykey;comment:配置ID"`
Key string `json:"key" gorm:"size:200;not null;default:'';uniqueIndex:uk_key;comment:配置键"`
Value string `json:"value" gorm:"type:text;comment:配置值"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (Config) TableName() string {
return "config"
}
func (Config) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置表'"
}

23
models/customer.go Normal file
View File

@ -0,0 +1,23 @@
package models
// Customer 客户表
type Customer struct {
ID int64 `json:"id" gorm:"primarykey;comment:客户ID"`
Code string `json:"code" gorm:"size:50;not null;default:'';uniqueIndex:uk_code;comment:客户编码"`
Name string `json:"name" gorm:"size:200;not null;default:'';comment:客户名称"`
ContactPerson string `json:"contact_person" gorm:"size:50;default:'';comment:联系人"`
ContactPhone string `json:"contact_phone" gorm:"size:20;default:'';comment:联系电话"`
Address string `json:"address" gorm:"size:255;default:'';comment:地址"`
Status int8 `json:"status" gorm:"type:tinyint(1);default:1;comment:状态(0:禁用,1:启用)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (Customer) TableName() string {
return "customer"
}
func (Customer) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表'"
}

33
models/employee.go Normal file
View File

@ -0,0 +1,33 @@
package models
// Employee 员工
type Employee struct {
ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
EmployeeIDStr string `json:"employee_id_str" gorm:"size:10;not null;uniqueIndex"` // 工号
Fid int64 `json:"fid" gorm:"not null;default:0;comment:父级id"`
AboutId int64 `json:"about_id" gorm:"not null;default:0;comment:关联id"`
Code string `json:"code" gorm:"size:64;not null;default:'';comment:机械码"`
Username string `json:"username" gorm:"size:50;not null;default:'';comment:登录账号 (init_工号)"`
Password string `json:"password" gorm:"size:100;not null;default:'';comment:密码"`
Name string `json:"name" gorm:"size:50;not null;default:'';comment:姓名"`
Phone string `json:"phone" gorm:"size:11;not null;default:'';comment:手机号"`
Role int64 `json:"role" gorm:"not null;default:0;comment:权限"`
Status int8 `json:"status" gorm:"not null;default:1;comment:状态 1正常 0禁用"`
Score int64 `json:"score" gorm:"not null;default:0;comment:积分"`
From string `json:"from" gorm:"size:50;not null;default:'';comment:来源"`
TypeId int8 `json:"type_id" gorm:"not null;default:0;comment:关联类型"`
LastLoginAt int64 `json:"last_login_at" gorm:"not null;default:0;comment:最后登录时间"`
LastLoginIP string `json:"last_login_ip" gorm:"size:20;not null;default:'';comment:最后登录ip"`
CreatedAt int64 `json:"created_at" gorm:"not null;default:0;comment:创建时间"`
UpdatedAt int64 `json:"updated_at" gorm:"not null;default:0;comment:更新时间"`
DeletedAt int64 `json:"deleted_at" gorm:"not null;default:0;comment:删除时间"`
ExpireTime int64 `json:"expire_time" gorm:"not null;default:0;comment:过期时间"`
}
func (Employee) TableName() string {
return "employees"
}
func (Employee) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表'"
}

22
models/employee_level.go Normal file
View File

@ -0,0 +1,22 @@
package models
// EmployeeLevel 员工等级
type EmployeeLevel struct {
ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
EmpId int64 `json:"emp_id" gorm:"not null;default:0;comment:员工id"`
MaxNum int64 `json:"max_num" gorm:"not null;default:0;comment:最大数量"`
Level int8 `json:"level" gorm:"not null;default:0;comment:等级"`
PayTime int64 `json:"pay_time" gorm:"not null;default:0;comment:最新支付时间"`
ExpireTime int64 `json:"expire_time" gorm:"not null;default:0;comment:到期时间"`
CreatedAt int64 `json:"created_at" gorm:"not null;default:0;comment:创建时间"`
UpdatedAt int64 `json:"updated_at" gorm:"not null;default:0;comment:更新时间"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (EmployeeLevel) TableName() string {
return "employees_level"
}
func (EmployeeLevel) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工等级表'"
}

View File

@ -0,0 +1,29 @@
package models
// EmployeeLevelLog 员工等级变更日志表
type EmployeeLevelLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:日志ID"`
EmpId int64 `json:"emp_id" gorm:"not null;default:0;index;comment:员工ID"`
OperationType int8 `json:"operation_type" gorm:"not null;default:1;comment:操作类型(1:开通,2:升级,3:续费)"`
OldLevel int8 `json:"old_level" gorm:"not null;default:0;comment:原等级"`
NewLevel int8 `json:"new_level" gorm:"not null;default:0;comment:新等级"`
OldMaxNum int64 `json:"old_max_num" gorm:"not null;default:0;comment:原子账号数量上限"`
NewMaxNum int64 `json:"new_max_num" gorm:"not null;default:0;comment:新子账号数量上限"`
OldExpireTime int64 `json:"old_expire_time" gorm:"not null;default:0;comment:原到期时间"`
NewExpireTime int64 `json:"new_expire_time" gorm:"not null;default:0;comment:新到期时间"`
Price int64 `json:"price" gorm:"not null;default:0;comment:价格(单位:分)"`
PayTime int64 `json:"pay_time" gorm:"not null;default:0;comment:支付时间"`
OperatorID int64 `json:"operator_id" gorm:"not null;default:0;comment:操作人ID"`
OperatorName string `json:"operator_name" gorm:"size:50;not null;default:'';comment:操作人姓名"`
Remark string `json:"remark" gorm:"size:255;not null;default:'';comment:备注"`
CreatedAt int64 `json:"created_at" gorm:"not null;default:0;index;comment:创建时间戳"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (EmployeeLevelLog) TableName() string {
return "employees_level_log"
}
func (EmployeeLevelLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工等级变更日志表'"
}

View File

@ -0,0 +1,87 @@
package models
// EmployeeSettings 员工设置表
type EmployeeSettings struct {
ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
EmpID int64 `json:"emp_id" gorm:"not null;default:0;uniqueIndex;comment:员工ID(关联employees.id)"`
Settings string `json:"settings" gorm:"type:text;not null;comment:员工配置(JSON格式)"`
CreatedAt int64 `json:"created_at" gorm:"not null;default:0;comment:创建时间"`
UpdatedAt int64 `json:"updated_at" gorm:"not null;default:0;comment:更新时间"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (EmployeeSettings) TableName() string {
return "employees_settings"
}
func (EmployeeSettings) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工设置表'"
}
// EmployeeSettingsConfig 员工配置结构体JSON格式
type EmployeeSettingsConfig struct {
// 筛选配置
ShowPrice int8 `form:"show_price" json:"show_price"` // 比价结果开关
ShowCategory int8 `form:"show_category" json:"show_category"` // 市场竞争开关
ShowCache int8 `form:"show_cache" json:"show_cache"` // 缓存开启
CompareCount int8 `form:"compare_count" json:"compare_count"` // 比较条数
CompareCountEditable int8 `form:"compare_count_editable" json:"compare_count_editable"` // 比较条数是否可编辑
PriceCompare string `form:"price_compare" json:"price_compare"` // 价格比较符号
PriceValue float64 `form:"price_value" json:"price_value"` // 价格数值
PriceEditable int8 `form:"price_editable" json:"price_editable"` // 比较条数是否可编辑
SellCountCompare string `form:"sell_count_compare" json:"sell_count_compare"` // 在售数量比较符号
SellCountValue float64 `form:"sell_count_value" json:"sell_count_value"` // 在售数量数值
SellCountEditable int8 `form:"sell_count_editable" json:"sell_count_editable"` // 在售数量是否可编辑
BuyCountCompare string `form:"buy_count_compare" json:"buy_count_compare"` // 已售数量比较符号
BuyCountValue float64 `form:"buy_count_value" json:"buy_count_value"` // 已售数量数值
BuyCountEditable int8 `form:"buy_count_editable" json:"buy_count_editable"` // 已售数量是否可编辑
Condition string `form:"condition" json:"condition"` // 品相
ConditionEditable int8 `form:"condition_editable" json:"condition_editable"` // 品相是否可编辑
// 精品书配置
JingpinEnabled int8 `form:"jingpin_enabled" json:"jingpin_enabled"` // 开启精品书开关
JingpinPriceCompare string `form:"jingpin_price_compare" json:"jingpin_price_compare"` // 精品书价格比较符号
JingpinPriceValue float64 `form:"jingpin_price_value" json:"jingpin_price_value"` // 精品书价格数值
JingpinPriceEditable int8 `form:"jingpin_price_editable" json:"jingpin_price_editable"` // 精品书价格是否可编辑
JingpinSellCountCompare string `form:"jingpin_sell_count_compare" json:"jingpin_sell_count_compare"` // 精品书在售数量比较符号
JingpinSellCountValue float64 `form:"jingpin_sell_count_value" json:"jingpin_sell_count_value"` // 精品书在售数量数值
JingpinSellCountEditable int8 `form:"jingpin_sell_count_editable" json:"jingpin_sell_count_editable"` // 精品书在售数量是否可编辑
JingpinBuyCountCompare string `form:"jingpin_buy_count_compare" json:"jingpin_buy_count_compare"` // 精品书已售数量比较符号
JingpinBuyCountValue float64 `form:"jingpin_buy_count_value" json:"jingpin_buy_count_value"` // 精品书已售数量数值
JingpinBuyCountEditable int8 `form:"jingpin_buy_count_editable" json:"jingpin_buy_count_editable"` // 精品书已售数量是否可编辑
}
// GetDefaultEmployeeSettingsConfig 获取默认用户配置
func GetDefaultEmployeeSettingsConfig() EmployeeSettingsConfig {
return EmployeeSettingsConfig{
// 筛选配置
ShowPrice: 0, // 比价结果开启
ShowCategory: 0, // 市场竞争开启
ShowCache: 1, // 缓存开启
CompareCount: 1, // 比较条数
CompareCountEditable: 0, // 比较条数是否可编辑
PriceCompare: ">=", // 价格比较符
PriceValue: 4, // 价格阈值
PriceEditable: 0, // 价格是否可编辑
SellCountCompare: ">=", // 在售数量比较符
SellCountValue: 10, // 在售数量阈值
SellCountEditable: 0, // 在售数量是否可编辑
BuyCountCompare: ">=", // 已售数量比较符
BuyCountValue: 10, // 已售数量阈值
BuyCountEditable: 0, // 已售数量是否可编辑
Condition: "85~", // 品相等级
ConditionEditable: 0,
// 精品书配置
JingpinEnabled: 0, // 开启精品书
JingpinPriceCompare: ">=", // 精品书价格比较符
JingpinPriceValue: 10, // 精品书价格阈值
JingpinPriceEditable: 0, // 精品书价格是否可编辑
JingpinSellCountCompare: ">=", // 精品书在售数量比较符
JingpinSellCountValue: 10, // 精品书在售数量阈值
JingpinSellCountEditable: 0, // 精品书在售数量是否可编辑
JingpinBuyCountCompare: ">=", // 精品书已售数量比较符
JingpinBuyCountValue: 10, // 精品书已售数量阈值
JingpinBuyCountEditable: 0, // 精品书已售数量是否可编辑
}
}

25
models/inventory.go Normal file
View File

@ -0,0 +1,25 @@
package models
// Inventory 库存汇总表
type Inventory struct {
ID int64 `json:"id" gorm:"primarykey;comment:库存ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;uniqueIndex:uk_warehouse_product_batch;index;comment:仓库ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;uniqueIndex:uk_warehouse_product_batch;index;comment:商品ID"`
BatchNo string `json:"batch_no" gorm:"size:100;not null;default:'';uniqueIndex:uk_warehouse_product_batch;comment:批次号(无批次管理则为空字符串)"`
ProductionDate int64 `json:"production_date" gorm:"not null;default:0;comment:生产日期时间戳(秒)"`
ExpiryDate int64 `json:"expiry_date" gorm:"not null;default:0;comment:失效日期时间戳(秒)"`
Quantity int64 `json:"quantity" gorm:"not null;default:0;comment:库存数量(最小单位,如个)"`
LockedQuantity int64 `json:"locked_quantity" gorm:"not null;default:0;comment:锁定数量(已分配但未出库)"`
AvailableQuantity int64 `json:"available_quantity" gorm:"->;comment:可用数量(计算字段)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (Inventory) TableName() string {
return "inventory"
}
func (Inventory) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存汇总表'"
}

View File

@ -0,0 +1,26 @@
package models
// InventoryDetail 库存明细表(库位级)
type InventoryDetail struct {
ID int64 `json:"id" gorm:"primarykey;comment:明细ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;comment:仓库ID"`
LocationID int64 `json:"location_id" gorm:"not null;default:0;uniqueIndex:uk_location_product_batch;index;comment:库位ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;uniqueIndex:uk_location_product_batch;index;comment:商品ID"`
BatchNo string `json:"batch_no" gorm:"size:100;not null;default:'';uniqueIndex:uk_location_product_batch;index;comment:批次号"`
ProductionDate int64 `json:"production_date" gorm:"not null;default:0;comment:生产日期时间戳(秒)"`
ExpiryDate int64 `json:"expiry_date" gorm:"not null;default:0;index;comment:失效日期时间戳(秒)"`
Quantity int64 `json:"quantity" gorm:"not null;default:0;comment:当前数量(最小单位)"`
LockedQuantity int64 `json:"locked_quantity" gorm:"not null;default:0;comment:锁定数量"`
AvailableQuantity int64 `json:"available_quantity" gorm:"->;comment:可用数量"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (InventoryDetail) TableName() string {
return "inventory_detail"
}
func (InventoryDetail) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存明细表(库位级)'"
}

29
models/inventory_log.go Normal file
View File

@ -0,0 +1,29 @@
package models
// InventoryLog 库存流水表
type InventoryLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:流水ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;comment:仓库ID"`
LocationID int64 `json:"location_id" gorm:"not null;default:0;comment:库位ID可为空"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:商品ID"`
BatchNo string `json:"batch_no" gorm:"size:100;not null;default:'';comment:批次号"`
ChangeType int8 `json:"change_type" gorm:"not null;default:0;comment:变动类型(1:入库,2:出库,3:移库,4:盘点调整,5:锁定库存,6:解锁库存)"`
ChangeQuantity int64 `json:"change_quantity" gorm:"not null;default:0;comment:变动数量(正增负减,最小单位)"`
BeforeQuantity int64 `json:"before_quantity" gorm:"not null;default:0;comment:变动前数量"`
AfterQuantity int64 `json:"after_quantity" gorm:"not null;default:0;comment:变动后数量"`
RelatedOrderType string `json:"related_order_type" gorm:"size:50;not null;default:'';comment:关联单据类型(如:采购单/purchase, 销售单/sales, 波次单/wave"`
RelatedOrderNo string `json:"related_order_no" gorm:"size:100;not null;index;comment:关联单据号"`
Operator string `json:"operator" gorm:"size:100;not null;comment:操作人"`
OperatorID int64 `json:"operator_id" gorm:"not null;default:0;comment:操作人ID"`
Remark string `json:"remark" gorm:"size:255;not null;comment:备注"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;index;comment:操作时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (InventoryLog) TableName() string {
return "inventory_log"
}
func (InventoryLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存流水表'"
}

23
models/location.go Normal file
View File

@ -0,0 +1,23 @@
package models
// Location 库位表
type Location struct {
ID int64 `json:"id" gorm:"primarykey;comment:库位ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;uniqueIndex:uk_warehouse_code,priority:1;comment:所属仓库ID"`
Code string `json:"code" gorm:"size:50;not null;default:'';uniqueIndex:uk_warehouse_code,priority:2;comment:库位编码(如 A-01-02"`
Type int8 `json:"type" gorm:"not null;default:1;comment:库位类型(1:存储库位,2:拣货库位,3:收货库位,4:发货库位,5:退货库位)"`
Capacity int64 `json:"capacity" gorm:"not null;default:0;comment:容量(按体积立方厘米或重量克)"`
Sort int `json:"sort" gorm:"not null;default:0;comment:排序值(值越小越靠前)"`
Status int8 `json:"status" gorm:"type:tinyint(1);not null;default:1;comment:状态(0:禁用,1:启用)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;index:idx_is_del;priority:3;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (Location) TableName() string {
return "location"
}
func (Location) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库位表'"
}

40
models/logistics.go Normal file
View File

@ -0,0 +1,40 @@
package models
import "time"
type Logistics struct {
Id uint64 `gorm:"primaryKey;column:id" json:"id"`
TemplateName string `gorm:"column:template_name;size:255" json:"templateName"`
DeliveryProvince string `gorm:"column:delivery_province;size:255" json:"deliveryProvince"`
DeliveryCity string `gorm:"column:delivery_city;size:255" json:"deliveryCity"`
DeliveryArea string `gorm:"column:delivery_area;size:255" json:"deliveryArea"`
DeliveryAddress string `gorm:"column:delivery_address;size:255" json:"deliveryAddress"`
PricingMethod string `gorm:"column:pricing_method;size:1;default:0" json:"pricingMethod"`
Shipping string `gorm:"column:shipping;size:1;default:0" json:"shipping"`
FirWbv float64 `gorm:"column:fir_wbv;type:double(20,2)" json:"firWbv"`
FirPrice float64 `gorm:"column:fir_price;type:decimal(10,2)" json:"firPrice"`
ContinueWbv float64 `gorm:"column:continue_wbv;type:double(20,2)" json:"continueWbv"`
ContinuePrice float64 `gorm:"column:continue_price;type:decimal(10,2)" json:"continuePrice"`
CreateBy uint64 `gorm:"column:create_by" json:"createBy"`
CreateTime *time.Time `gorm:"column:create_time" json:"createTime"`
UpdateBy uint64 `gorm:"column:update_by" json:"updateBy"`
UpdateTime *time.Time `gorm:"column:update_time" json:"updateTime"`
Status string `gorm:"column:status;size:1;default:0" json:"status"`
DelFlag string `gorm:"column:del_flag;size:1;default:0" json:"delFlag"`
TenantId string `gorm:"column:tenant_id;size:20" json:"tenantId"`
CreateDept uint64 `gorm:"column:create_dept" json:"createDept"`
ShippingRange string `gorm:"column:shipping_range;type:text" json:"shippingRange"`
WarehouseId uint64 `gorm:"column:warehouse_id" json:"warehouseId"`
Remark string `gorm:"column:remark;size:255" json:"remark"`
PhoneNumber uint64 `gorm:"column:phone_number" json:"phoneNumber"`
Contact string `gorm:"column:contact;size:14" json:"contact"`
FullAddress string `gorm:"column:full_address;size:255" json:"fullAddress"`
}
func (Logistics) TableName() string {
return "logistics"
}
func (Logistics) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物流模板表'"
}

24
models/out_task.go Normal file
View File

@ -0,0 +1,24 @@
package models
// OutTask 外部任务表
type OutTask struct {
ID int64 `json:"id" gorm:"primarykey;comment:主键ID"`
ShopID int64 `json:"shop_id" gorm:"not null;default:0;index;comment:店铺ID"`
WaveTaskID int64 `json:"wave_task_id" gorm:"not null;default:0;index;comment:波次任务ID"`
OutTaskID int64 `json:"out_task_id" gorm:"not null;default:0;index;comment:外部任务ID"`
ShopType int8 `json:"shop_type" gorm:"not null;default:0;comment:店铺类型 1 拼多多 2 孔夫子 5 闲鱼"`
TaskType int8 `json:"task_type" gorm:"not null;default:0;comment:任务类型 1 核价发布 2 表格发布 3 商品拉取 4 商品详情拉取 5 操作商品 6 核价表格发布 10、按照数量删除任务 11、按照时间删除任务"`
ImgType int8 `json:"img_type" gorm:"not null;default:0;comment:图片类型 1仅官图 2 仅实拍图 3 优先官图 4 优先实拍图"`
TaskCount int64 `json:"task_count" gorm:"not null;default:0;comment:任务数(小车容量)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (OutTask) TableName() string {
return "out_task"
}
func (OutTask) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部任务表'"
}

31
models/out_task_log.go Normal file
View File

@ -0,0 +1,31 @@
package models
import "gorm.io/datatypes"
// OutTaskLog 外部任务日志表
type OutTaskLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:主键ID"`
ShopID int64 `json:"shop_id" gorm:"not null;default:0;index;comment:店铺ID"`
WaveTaskID int64 `json:"wave_task_id" gorm:"not null;default:0;index;comment:波次任务ID"`
OutTaskID int64 `json:"out_task_id" gorm:"not null;default:0;index;comment:外部任务ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:产品ID"`
ISBN string `json:"isbn" gorm:"type:varchar(100);not null;default:'';comment:isbn"`
LiveImage datatypes.JSON `json:"live_image" gorm:"type:json;not null;comment:实拍图json"`
Stock int64 `json:"stock" gorm:"not null;default:0;comment:库存"`
SalePrice int64 `json:"sale_price" gorm:"not null;default:0;comment:售价"`
Cost int64 `json:"cost" gorm:"not null;default:0;comment:运费"`
SkuCode string `json:"sku_code" gorm:"type:varchar(100);not null;default:'';comment:货号"`
Status int8 `json:"status" gorm:"type:tinyint(1);not null;comment:状态 0异常 1推送成功 2发布成功"`
Msg string `json:"msg" gorm:"type:varchar(255);not null;default:'';comment:错误信息"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (OutTaskLog) TableName() string {
return "out_task_log"
}
func (OutTaskLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='外部任务日志表'"
}

27
models/outbound_order.go Normal file
View File

@ -0,0 +1,27 @@
package models
// OutboundOrder 出库单主表
type OutboundOrder struct {
ID int64 `json:"id" gorm:"primarykey;comment:出库单ID"`
OutNo string `json:"out_no" gorm:"size:100;not null;default:'';uniqueIndex;comment:出库单号"`
WaveTaskID int64 `json:"wave_task_id" gorm:"not null;default:0;index;comment:波次任务ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;comment:仓库ID"`
CustomerID int64 `json:"customer_id" gorm:"not null;default:0;comment:客户ID"`
TotalQuantity int64 `json:"total_quantity" gorm:"not null;default:0;comment:出库总数量"`
TotalAmount int64 `json:"total_amount" gorm:"not null;default:0;comment:出库总金额(分)"`
Status int8 `json:"status" gorm:"not null;default:1;index;comment:状态(1:已创建,2:拣货中,3:已完成,4:已取消,5:发货中,6:已发货)"`
Operator string `json:"operator" gorm:"size:100;not null;default:'';comment:操作员"`
OperatorID int64 `json:"operator_id" gorm:"not null;default:0;comment:操作员ID"`
Remark string `json:"remark" gorm:"size:255;not null;default:'';comment:备注"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (OutboundOrder) TableName() string {
return "outbound_order"
}
func (OutboundOrder) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单主表'"
}

View File

@ -0,0 +1,26 @@
package models
// OutboundOrderItem 出库单明细表
type OutboundOrderItem struct {
ID int64 `json:"id" gorm:"primarykey;comment:明细ID"`
OutOrderID int64 `json:"out_order_id" gorm:"not null;default:0;index;comment:出库单ID"`
SalesOrderID int64 `json:"sales_order_id" gorm:"not null;default:0;index;comment:销售单ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:商品ID"`
LocationID int64 `json:"location_id" gorm:"not null;default:0;comment:库位ID"`
BatchNo string `json:"batch_no" gorm:"size:100;not null;default:'';comment:批次号"`
ProductionDate int64 `json:"production_date" gorm:"not null;default:0;comment:生产日期时间戳(秒)"`
ExpiryDate int64 `json:"expiry_date" gorm:"not null;default:0;comment:失效日期时间戳(秒)"`
Quantity int64 `json:"quantity" gorm:"not null;default:0;comment:出库数量"`
UnitPrice int64 `json:"unit_price" gorm:"not null;default:0;comment:单价(分/基本单位)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (OutboundOrderItem) TableName() string {
return "outbound_order_item"
}
func (OutboundOrderItem) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单明细表'"
}

View File

@ -0,0 +1,24 @@
package models
// OutboundOrderLocationLog 出库单库位变更记录表
type OutboundOrderLocationLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:记录ID"`
OutOrderID int64 `json:"out_order_id" gorm:"not null;default:0;index;comment:出库单ID"`
OutOrderItemID int64 `json:"out_order_item_id" gorm:"not null;default:0;index;comment:出库单明细ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;comment:商品ID"`
OldLocationID int64 `json:"old_location_id" gorm:"not null;default:0;comment:原库位ID"`
NewLocationID int64 `json:"new_location_id" gorm:"not null;default:0;comment:新库位ID"`
BatchNo string `json:"batch_no" gorm:"size:100;not null;default:'';comment:批次号"`
Operator string `json:"operator" gorm:"size:100;not null;default:'';comment:操作员"`
OperatorID int64 `json:"operator_id" gorm:"not null;default:0;comment:操作员ID"`
Remark string `json:"remark" gorm:"size:255;not null;default:'';comment:备注"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
}
func (OutboundOrderLocationLog) TableName() string {
return "outbound_order_location_log"
}
func (OutboundOrderLocationLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出库单库位变更记录表'"
}

19
models/print_log.go Normal file
View File

@ -0,0 +1,19 @@
package models
// PrintLog 打印日志表
type PrintLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:日志ID"`
PrintTaskID int64 `json:"print_task_id" gorm:"not null;default:0;index;comment:打印任务ID"`
PrintedAt int64 `json:"printed_at" gorm:"default:0;comment:打印时间戳(秒)"`
Result int8 `json:"result" gorm:"type:tinyint(1);default:1;comment:打印结果(1:成功,0:失败)"`
ErrorInfo string `json:"error_info" gorm:"size:255;default:'';comment:错误信息"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (PrintLog) TableName() string {
return "print_log"
}
func (PrintLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印日志表'"
}

31
models/print_task.go Normal file
View File

@ -0,0 +1,31 @@
package models
import "gorm.io/datatypes"
// PrintTask 打印任务表
type PrintTask struct {
ID int64 `json:"id" gorm:"primarykey;comment:打印任务ID"`
TaskNo string `json:"task_no" gorm:"size:100;not null;default:'';uniqueIndex;comment:打印任务编号"`
BusinessType int8 `json:"business_type" gorm:"not null;default:1;comment:业务类型(1:物流面单/shipment_label,2:拣货单/pick_list,3:装箱单/packing_list,4:发票/invoice)"`
RelatedID int64 `json:"related_id" gorm:"not null;default:0;index;comment:关联业务ID如出库单ID"`
RelatedNo string `json:"related_no" gorm:"size:100;not null;default:'';comment:关联业务单号"`
PrinterName string `json:"printer_name" gorm:"size:100;not null;default:'';comment:指定打印机"`
TemplateCode string `json:"template_code" gorm:"size:50;not null;default:'';comment:打印模板编码"`
PrintData datatypes.JSON `json:"print_data" gorm:"type:json;comment:打印数据快照JSON格式"`
Status int8 `json:"status" gorm:"not null;default:1;index;comment:状态(1:待打印/pending,2:打印中/printing,3:成功/success,4:失败/failed)"`
RetryCount int `json:"retry_count" gorm:"not null;default:0;comment:重试次数"`
ErrorMessage string `json:"error_message" gorm:"size:255;not null;default:'';comment:失败原因"`
Operator string `json:"operator" gorm:"size:100;not null;default:'';comment:操作人"`
OperatorID int64 `json:"operator_id" gorm:"not null;default:0;comment:操作人ID"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;index;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (PrintTask) TableName() string {
return "print_task"
}
func (PrintTask) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='打印任务表'"
}

31
models/product.go Normal file
View File

@ -0,0 +1,31 @@
package models
import "gorm.io/datatypes"
// Product 商品表
type Product struct {
ID int64 `json:"id" gorm:"primarykey;comment:商品ID"`
CategoryID int64 `json:"category_id" gorm:"not null;default:0;comment:分类ID"`
StandardProductID int64 `json:"standard_product_id" gorm:"not null;default:0;index;comment:关联标品ID"`
Name string `json:"name" gorm:"size:255;not null;default:'';comment:商品名称"`
Appearance int64 `json:"appearance" gorm:"not null;default:0;comment:品相"`
Barcode string `json:"barcode" gorm:"size:100;not null;default:'';index;comment:条码(可唯一,视业务而定)"`
Price int64 `json:"price" gorm:"not null;default:0;comment:价格"`
SalePrice int64 `json:"sale_price" gorm:"not null;default:0;comment:书价"`
Cost int64 `json:"cost" gorm:"not null;default:0;comment:最低运费"`
LiveImage datatypes.JSON `json:"live_image" gorm:"type:json;not null;comment:实拍图json"`
IsBatchManaged int8 `json:"is_batch_managed" gorm:"type:tinyint(1);not null;default:0;comment:是否批次管理(0:否,1:是)"`
IsShelfLifeManaged int8 `json:"is_shelf_life_managed" gorm:"type:tinyint(1);not null;default:0;comment:是否效期管理(0:否,1:是)"`
Status int8 `json:"status" gorm:"type:tinyint(1);not null;default:1;comment:状态(0:禁用,1:启用)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳"`
IsDel int8 `json:"is_del" gorm:"not null;default:0;comment:逻辑删除"`
}
func (Product) TableName() string {
return "product"
}
func (Product) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表'"
}

View File

@ -0,0 +1,20 @@
package models
// ProductCategory 商品分类表
type ProductCategory struct {
ID int64 `json:"id" gorm:"primarykey;comment:分类ID"`
ParentID int64 `json:"parent_id" gorm:"not null;default:0;index;comment:上级分类ID0表示顶级"`
Name string `json:"name" gorm:"size:100;not null;default:'';comment:分类名称"`
SortOrder int `json:"sort_order" gorm:"not null;default:0;comment:排序"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (ProductCategory) TableName() string {
return "product_category"
}
func (ProductCategory) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表'"
}

44
models/product_log.go Normal file
View File

@ -0,0 +1,44 @@
package models
import "gorm.io/datatypes"
// ProductLog 商品表
type ProductLog struct {
ID int64 `json:"id" gorm:"primarykey;comment:商品ID"`
Barcode string `json:"barcode" gorm:"size:100;not null;default:'';index;comment:条码(可唯一,视业务而定)"`
Md5Data string `json:"md5_data" gorm:"size:100;not null;default:'';comment:md5"`
OldName string `json:"old_name" gorm:"size:100;not null;default:'';comment:老书名"`
NewName string `json:"new_name" gorm:"size:100;not null;default:'';comment:新书名"`
OldPublisher string `json:"old_publisher" gorm:"size:100;not null;default:'';comment:老出版社"`
NewPublisher string `json:"new_publisher" gorm:"size:100;not null;default:'';comment:新出版社"`
OldAuthor string `json:"old_author" gorm:"size:100;not null;default:'';comment:老作者"`
NewAuthor string `json:"new_author" gorm:"size:100;not null;default:'';comment:新作者"`
OldPublicationTime int64 `json:"old_publication_time" gorm:"not null;default:0;comment:老出版时间"`
NewPublicationTime int64 `json:"new_publication_time" gorm:"not null;default:0;comment:新出版时间"`
OldPrice int64 `json:"old_price" gorm:"not null;default:0;comment:老价格"`
NewPrice int64 `json:"new_price" gorm:"not null;default:0;comment:新价格"`
OldBindingLayout string `json:"old_binding_layout" gorm:"size:20;not null;default:'';comment:老装帧"`
NewBindingLayout string `json:"new_binding_layout" gorm:"size:20;not null;default:'';comment:新装帧"`
OldPageCount int64 `json:"old_page_count" gorm:"not null;default:0;comment:老页数"`
NewPageCount int64 `json:"new_page_count" gorm:"not null;default:0;comment:新页数"`
OldWordCount int64 `json:"old_word_count" gorm:"not null;default:0;comment:老字数"`
NewWordCount int64 `json:"new_word_count" gorm:"not null;default:0;comment:新字数"`
OldLiveImage datatypes.JSON `json:"old_live_image" gorm:"type:json;not null;comment:老实拍图json"`
NewLiveImage datatypes.JSON `json:"new_live_image" gorm:"type:json;not null;comment:新实拍图json"`
OldIsSuit int8 `json:"old_is_suit" gorm:"type:tinyint(1);not null;default:0;comment:老是否是套装书(0:否,1:是)"`
NewIsSuit int8 `json:"new_is_suit" gorm:"type:tinyint(1);not null;default:0;comment:新是否是套装书(0:否,1:是)"`
Status int8 `json:"status" gorm:"type:tinyint(1);not null;default:0;comment:状态(0:审核中,1:通过 2:驳回)"`
CreateBy int64 `json:"create_by" gorm:"not null;default:0;comment:创建人"`
AuditBy int64 `json:"audit_by" gorm:"not null;default:0;comment:审核人"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳"`
IsDel int8 `json:"is_del" gorm:"not null;default:0;comment:逻辑删除"`
}
func (ProductLog) TableName() string {
return "product_log"
}
func (ProductLog) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品日志表'"
}

21
models/product_serial.go Normal file
View File

@ -0,0 +1,21 @@
package models
// ProductSerial 商品序列号表
type ProductSerial struct {
ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:商品ID"`
SerialNumber string `json:"serial_number" gorm:"size:100;not null;default:'';uniqueIndex:uk_serial;comment:序列号"`
Status int8 `json:"status" gorm:"not null;default:1;comment:状态(1:在库/in_stock,2:已售/sold,3:已退货/returned,4:已报废/scrapped)"`
InboundDate int64 `json:"inbound_date" gorm:"not null;default:0;comment:入库时间戳(秒)"`
OutboundDate int64 `json:"outbound_date" gorm:"not null;default:0;comment:出库时间戳(秒)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (ProductSerial) TableName() string {
return "product_serial"
}
func (ProductSerial) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品序列号表'"
}

View File

@ -0,0 +1,19 @@
package models
// ProductUnitConversion 商品单位换算表
type ProductUnitConversion struct {
ID int64 `json:"id" gorm:"primarykey;comment:自增ID"`
ProductID int64 `json:"product_id" gorm:"not null;uniqueIndex:uk_product_unit;comment:商品ID"`
FromUnit string `json:"from_unit" gorm:"size:20;not null;default:'';uniqueIndex:uk_product_unit;comment:源单位"`
ToUnit string `json:"to_unit" gorm:"size:20;not null;default:'';uniqueIndex:uk_product_unit;comment:目标单位"`
ConversionRate int64 `json:"conversion_rate" gorm:"not null;comment:换算率放大10000倍的整数如1箱=12个则存120000"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (ProductUnitConversion) TableName() string {
return "product_unit_conversion"
}
func (ProductUnitConversion) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品单位换算表'"
}

27
models/purchase_order.go Normal file
View File

@ -0,0 +1,27 @@
package models
// PurchaseOrder 采购订单主表
type PurchaseOrder struct {
ID int64 `json:"id" gorm:"primarykey;comment:采购单ID"`
PoNo string `json:"po_no" gorm:"size:100;not null;default:'';uniqueIndex;comment:采购单号(唯一)"`
SupplierID int64 `json:"supplier_id" gorm:"not null;default:0;index;comment:供应商ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;comment:预计入库仓库ID"`
OrderDate int64 `json:"order_date" gorm:"not null;default:0;comment:订单日期时间戳(秒)"`
ExpectedArrivalDate int64 `json:"expected_arrival_date" gorm:"not null;default:0;comment:预计到货日期时间戳(秒)"`
TotalAmount int64 `json:"total_amount" gorm:"not null;default:0;comment:采购总金额(单位:分)"`
Status int8 `json:"status" gorm:"not null;default:1;index;comment:状态(1:草稿/draft, 2:已提交/submitted, 3:已审核/approved, 4:部分收货/partial_received, 5:已收货/received, 6:已取消/cancelled)"`
Creator string `json:"creator" gorm:"size:100;not null;default:'';comment:创建人"`
CreatorID int64 `json:"creator_id" gorm:"not null;default:0;comment:创建人ID"`
Remark string `json:"remark" gorm:"size:255;not null;default:'';comment:备注"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (PurchaseOrder) TableName() string {
return "purchase_order"
}
func (PurchaseOrder) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购订单主表'"
}

View File

@ -0,0 +1,23 @@
package models
// PurchaseOrderItem 采购订单明细表
type PurchaseOrderItem struct {
ID int64 `json:"id" gorm:"primarykey;comment:明细ID"`
PurchaseOrderID int64 `json:"purchase_order_id" gorm:"not null;default:0;index;comment:采购单ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:商品ID"`
Quantity int64 `json:"quantity" gorm:"not null;default:0;comment:采购数量(最小单位)"`
ReceivedQuantity int64 `json:"received_quantity" gorm:"not null;default:0;comment:已入库数量"`
UnitPrice int64 `json:"unit_price" gorm:"not null;default:0;comment:单价(单位:分/基本单位)"`
Amount int64 `json:"amount" gorm:"comment:金额(分)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (PurchaseOrderItem) TableName() string {
return "purchase_order_item"
}
func (PurchaseOrderItem) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='采购订单明细表'"
}

27
models/receiving_order.go Normal file
View File

@ -0,0 +1,27 @@
package models
// ReceivingOrder 入库单主表
type ReceivingOrder struct {
ID int64 `json:"id" gorm:"primarykey;comment:入库单ID"`
ReceivingNo string `json:"receiving_no" gorm:"size:100;not null;default:'';uniqueIndex;comment:入库单号"`
PurchaseOrderID int64 `json:"purchase_order_id" gorm:"not null;default:0;index;comment:关联采购单ID可为空支持无来源入库"`
WaveTaskID int64 `json:"wave_task_id" gorm:"not null;default:0;index;comment:关联波次任务ID"`
WarehouseID int64 `json:"warehouse_id" gorm:"not null;default:0;index;comment:入库仓库ID"`
SupplierID int64 `json:"supplier_id" gorm:"not null;default:0;comment:供应商ID"`
ReceivingDate int64 `json:"receiving_date" gorm:"not null;default:0;comment:入库日期时间戳(秒)"`
Status int8 `json:"status" gorm:"not null;default:1;index;comment:状态(1:待收货/pending, 2:验收中/checking, 3:已完成/completed, 4:已取消/cancelled)"`
Operator string `json:"operator" gorm:"size:100;not null;default:'';comment:操作人"`
OperatorID int64 `json:"operator_id" gorm:"not null;default:0;comment:操作人ID"`
Remark string `json:"remark" gorm:"size:255;not null;default:'';comment:备注"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (ReceivingOrder) TableName() string {
return "receiving_order"
}
func (ReceivingOrder) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库单主表'"
}

View File

@ -0,0 +1,24 @@
package models
// ReceivingOrderItem 入库单明细表
type ReceivingOrderItem struct {
ID int64 `json:"id" gorm:"primarykey;comment:明细ID"`
ReceivingOrderID int64 `json:"receiving_order_id" gorm:"not null;default:0;index;comment:入库单ID"`
ProductID int64 `json:"product_id" gorm:"not null;default:0;index;comment:商品ID"`
LocationID int64 `json:"location_id" gorm:"not null;default:0;index;comment:入库库位ID"`
BatchNo string `json:"batch_no" gorm:"size:100;not null;default:'';comment:批次号"`
ProductionDate int64 `json:"production_date" gorm:"not null;default:0;comment:生产日期时间戳(秒)"`
ExpiryDate int64 `json:"expiry_date" gorm:"not null;default:0;comment:失效日期时间戳(秒)"`
Quantity int64 `json:"quantity" gorm:"not null;comment:入库数量(最小单位)"`
CreatedAt int64 `json:"created_at" gorm:"type:bigint;not null;default:0;comment:创建时间戳(秒)"`
UpdatedAt int64 `json:"updated_at" gorm:"type:bigint;not null;default:0;comment:更新时间戳(秒)"`
IsDel int8 `json:"is_del" gorm:"type:tinyint(1);not null;default:0;comment:逻辑删除标记(0:未删除,1:已删除)"`
}
func (ReceivingOrderItem) TableName() string {
return "receiving_order_item"
}
func (ReceivingOrderItem) TableOptions() string {
return "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='入库单明细表'"
}

View File

@ -0,0 +1,64 @@
package request
// LoginRequest 登录请求
type LoginRequest struct {
Username string `form:"username" binding:"required"`
Password string `form:"password" binding:"required"`
AboutID int64 `form:"about_id"` // 租户ID从ERP关联
Type int64 `form:"type"` // 类型 1web 2pda
Code string `form:"code"` // 机械码
}
// GetEmployeeListRequest 获取员工列表请求
type GetEmployeeListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Fid int64 `form:"fid"`
Status string `form:"status"`
Keyword string `form:"keyword"`
}
// AddEmployeeRequest 添加员工请求
type AddEmployeeRequest struct {
Name string `form:"name" binding:"required"`
UserName string `form:"username"`
AboutId int64 `form:"about_id"`
Fid int64 `form:"fid"`
Phone string `form:"phone"`
Password string `form:"password" binding:"required,min=6"`
From string `form:"from"`
ExpireTime int64 `form:"expire_time"`
}
// UpdatePasswordEmployeeRequest 修改员工密码请求
type UpdatePasswordEmployeeRequest struct {
Id int64 `form:"id" binding:"required"`
AboutId int64 `form:"about_id"`
Password string `form:"password" binding:"required,min=6"`
}
// UpdateExpireTimeEmployeeRequest 修改员工过期时间请求
type UpdateExpireTimeEmployeeRequest struct {
Id int64 `form:"id" binding:"required"`
AboutId int64 `form:"about_id"`
ExpireTime int64 `form:"expire_time" binding:"required"`
}
// CheckCodeEmployeeRequest 校验员工机械码
type CheckCodeEmployeeRequest struct {
UserName string `form:"username" binding:"required"`
}
// ClearCodeEmployeeRequest 清除员工机械码请求
type ClearCodeEmployeeRequest struct {
UserName string `form:"username" binding:"required"`
}
// SetEmployeeLevelRequest 设置员工等级
type SetEmployeeLevelRequest struct {
EmpId int64 `form:"emp_id" binding:"required"`
Level int8 `form:"level" binding:"required,min=1,max=5"` // 等级(1-5)
PayTime int64 `form:"pay_time" binding:"required"`
OperationType int8 `form:"operation_type" binding:"required,oneof=1 2 3"` // 操作类型(1:开通,2:升级,3:续费)
Duration int64 `form:"duration" binding:"required,min=1"` // 时长(月数)按30天/月计算
}

View File

@ -0,0 +1,5 @@
package request
type BarcodeRequest struct {
Content string `form:"content" binding:"required"`
}

35
models/request/book.go Normal file
View File

@ -0,0 +1,35 @@
package request
type BookRequest struct {
Isbn string `form:"isbn" binding:"required"`
}
type GetCodeRequest struct {
BookName string `form:"book_name" binding:"required"`
Author string `form:"author" binding:"required"`
Publisher string `form:"publisher" binding:"required"`
}
type AddBookRequest struct {
Fid int64 `form:"fid"` // 父ID
Type int8 `form:"type" binding:"required"` // 类型 1:普通图书 2:套装图书 3:一号多书 4:无书号
Isbn string `form:"isbn" binding:"required"` // ISBN
FIsbn string `form:"f_isbn" binding:"required"` // 副ISBN
BookName string `form:"book_name" binding:"required"` // 书名
FBookName string `form:"f_book_name"` // 副书名
Author string `form:"author"` // 作者
Publisher string `form:"publisher"` // 出版社
PublicationTime int64 `form:"publication_time"` // 出版时间
BindingLayout string `form:"binding_layout"` // 装帧
FixPrice int64 `form:"fix_price"` // 定价
PageCount int64 `form:"page_count"` // 页数
WordCount int64 `form:"word_count"` // 字数
BookFormat int64 `form:"book_format"` // 图书格式
LiveImage []string `form:"live_image"` // 图片
}
type GetNoIsbnBookRequest struct {
BookName string `form:"book_name" binding:"required"` // 书名
Author string `form:"author"` // 作者
Publisher string `form:"publisher"` // 出版社
}

View File

@ -0,0 +1,7 @@
package request
// CancelLogisticsRequest 取消物流单号请求
type CancelLogisticsRequest struct {
UserID int64 `form:"user_id" binding:"required"`
LogisticsNo string `form:"logistics_no" binding:"required"`
}

32
models/request/car.go Normal file
View File

@ -0,0 +1,32 @@
package request
type QueryCarRequest struct {
Keyword string `form:"keyword"`
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=10"`
}
type CreateCarRequest struct {
WarehouseID int64 `form:"warehouse_id" binding:"required"`
PushType int8 `form:"push_type" binding:"required"`
ReleaseType int8 `form:"release_type" binding:"required"`
Code int64 `form:"code" binding:"required"`
Name string `form:"name" binding:"required,max=50"`
Capacity int64 `form:"capacity" binding:"required"`
Appearance int64 `form:"appearance" binding:"required"`
ShopInfo []map[string]interface{} `form:"shop_info[]"`
}
type UpdateCarRequest struct {
PushType *int8 `form:"push_type" binding:"required"`
ReleaseType *int8 `form:"release_type" binding:"required"`
ID int64 `form:"id" binding:"required"`
Name string `form:"name" binding:"omitempty,max=50"`
Capacity *int64 `form:"capacity" binding:"required"`
Appearance *int64 `form:"appearance" binding:"required"`
ShopInfo []map[string]interface{} `form:"shop_info[]"`
}
type DeleteCarRequest struct {
ID int64 `form:"id" binding:"required"`
}

26
models/request/config.go Normal file
View File

@ -0,0 +1,26 @@
package request
// GetConfigListRequest 获取配置列表请求
type GetConfigListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Keyword string `form:"keyword"`
}
// AddConfigRequest 添加配置请求
type AddConfigRequest struct {
Key string `form:"key" binding:"required"`
Value string `form:"value" binding:"required"`
}
// UpdateConfigRequest 更新配置请求
type UpdateConfigRequest struct {
ID int64 `form:"id" binding:"required"`
Key string `form:"key"`
Value string `form:"value"`
}
// DeleteConfigRequest 删除配置请求
type DeleteConfigRequest struct {
ID int64 `form:"id" binding:"required"`
}

View File

@ -0,0 +1,14 @@
package request
import "psi/models"
// GetEmployeeSettingsRequest 获取员工设置请求
type GetEmployeeSettingsRequest struct {
EmpId int64 `form:"emp_id"`
}
// SaveEmployeeSettingsRequest 保存员工设置请求
type SaveEmployeeSettingsRequest struct {
EmpId int64 `form:"emp_id"`
models.EmployeeSettingsConfig
}

View File

@ -0,0 +1,34 @@
package request
// GoodsImportRow Excel 表格中每一行的数据结构(对应货号批量修改工具导出格式)
type GoodsImportRow struct {
GoodsNo string // 商品编号
GoodsName string // 商品名称
Huohao string // 货号(原)
HuohaoNew string // 货号[新](新货号,用作库位编码)
ISBN string // ISBN
Author string // 作者
Publisher string // 出版社
PriceListing string // 商品定价
PriceSale string // 商品售价
Category string // 商品分类
ShopCategory string // 本店分类
Appearance string // 品相
Inventory string // 库存
ProductID string // 商品ID
ShopID string // 店铺ID
LiveImage string // 商品图片工具导出时已处理为URL
}
// GoodsImportResult 导入结果
type GoodsImportResult struct {
SuccessCount int
FailCount int
FailDetails []string
Message string
}
func (r *GoodsImportResult) AddFail(detail string) {
r.FailCount++
r.FailDetails = append(r.FailDetails, detail)
}

View File

@ -0,0 +1,61 @@
package request
// GetInventoryListRequest 获取库存汇总列表请求
type GetInventoryListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ProductId int64 `form:"product_id"`
WarehouseID int64 `form:"warehouse_id"`
ISBN string `form:"isbn"`
Name string `form:"name"`
}
// GetInventoryGroupedListRequest 获取按仓库库位分组的库存列表请求
type GetInventoryGroupedListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ProductId int64 `form:"product_id"`
WarehouseID int64 `form:"warehouse_id"`
}
// GetInventoryDetailRequest 获取库存明细请求
type GetInventoryDetailRequest struct {
ProductID int64 `form:"product_id" binding:"required"`
}
// GetInventoryLogListRequest 获取库存流水列表请求
type GetInventoryLogListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ISBN string `form:"isbn"`
BookName string `form:"book_name"`
WarehouseID int64 `form:"warehouse_id"`
ChangeType int8 `form:"change_type"`
RelatedOrderNo string `form:"related_order_no"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// InventoryStatistRequest 获取条码/品相库存总数请求
type InventoryStatistRequest struct {
Barcode string `form:"barcode"`
Appearance int64 `form:"appearance"`
}
// GetStockCheckListRequest 获取盘库列表请求
type GetStockCheckListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
WarehouseID int64 `form:"warehouse_id"`
Status int8 `form:"status"`
CheckNo string `form:"check_no"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// GetStockCheckDetailRequest 获取盘库明细列表请求
type GetStockCheckDetailRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
StockCheckID int64 `form:"stock_check_id" binding:"required"`
}

125
models/request/location.go Normal file
View File

@ -0,0 +1,125 @@
package request
// QueryAllLocationRequest 查询所有库位请求仓库ID可选
type QueryAllLocationRequest struct {
WarehouseID *int64 `json:"warehouse_id" form:"warehouse_id"`
Code string `json:"code" form:"code"`
Type *int8 `json:"type" form:"type"`
Status *int8 `json:"status" form:"status"`
Page int `json:"page" form:"page,default=1"`
PageSize int `json:"page_size" form:"page_size,default=10"`
}
type QueryLocationRequest struct {
WarehouseID int64 `json:"warehouse_id" form:"warehouse_id" binding:"required"`
Code string `json:"code" form:"code"`
Type *int8 `json:"type" form:"type"`
Status *int8 `json:"status" form:"status"`
Page int `json:"page" form:"page,default=1"`
PageSize int `json:"page_size" form:"page_size,default=10"`
}
type CreateLocationRequest struct {
WarehouseID int64 `form:"warehouse_id" binding:"required"`
Code string `form:"code" binding:"required,max=50"`
Type int8 `form:"type" binding:"required"`
Capacity int64 `form:"capacity" binding:"required"`
Status int8 `form:"status" binding:"required"`
}
type BatchGenerateLocationRequest struct {
WarehouseID int64 `form:"warehouse_id" binding:"required"`
Groups []GroupConfig `form:"groups[]"`
Type int8 `form:"type"`
Capacity int64 `form:"capacity"`
Status int8 `form:"status"`
}
type GroupConfig struct {
FormatType int `form:"format_type" binding:"required,oneof=1 2 3 4"`
StartValue string `form:"start_value" binding:"required"`
EndValue string `form:"end_value" binding:"required"`
PaddingLen int `form:"padding_len"`
Separator string `form:"separator" binding:"max=5"`
}
type UpdateLocationRequest struct {
WarehouseID int64 `form:"warehouse_id" binding:"required"`
IDs []int64 `form:"ids[]"`
Code string `form:"code"`
Type *int8 `form:"type"`
Capacity *int64 `form:"capacity"`
Sort *int `form:"sort"`
Status *int8 `form:"status"`
}
type DeleteLocationRequest struct {
IDs []int64 `form:"ids[]"`
}
type GetLocationIdRequest struct {
Code string `form:"code"`
WarehouseCode string `form:"warehouse_code"`
}
type SyncLocationRequest struct {
UserID int64 `json:"user_id" binding:"required"`
Data []SyncLocationAreaRequest `json:"data" binding:"required"`
}
type SyncLocationAreaRequest struct {
Code string `json:"code" binding:"required"`
Name string `json:"name" binding:"required"`
TemplateId int64 `json:"templateId" binding:"required"`
Status string `json:"status" binding:"required"`
Logistics SyncLogisticsRequest `json:"logistics" binding:"required"`
Data []SyncLocationItemRequest `json:"data" binding:"required"`
}
type SyncLogisticsRequest struct {
Id int64 `json:"id"`
TemplateName string `json:"templateName" binding:"required"`
DeliveryProvince string `json:"deliveryProvince" binding:"required"`
DeliveryCity string `json:"deliveryCity" binding:"required"`
DeliveryArea string `json:"deliveryArea" binding:"required"`
DeliveryAddress string `json:"deliveryAddress" binding:"required"`
PricingMethod string `json:"pricingMethod" binding:"required"`
Shipping string `json:"shipping" binding:"required"`
FirWbv float64 `json:"firWbv"`
FirPrice float64 `json:"firPrice"`
ContinueWbv float64 `json:"continueWbv"`
ContinuePrice float64 `json:"continuePrice"`
CreateBy int64 `json:"createBy"`
CreateTime string `json:"createTime"`
UpdateBy int64 `json:"updateBy"`
UpdateTime string `json:"updateTime"`
Status string `json:"status"`
DelFlag string `json:"delFlag"`
TenantId string `json:"tenantId"`
CreateDept int64 `json:"createDept"`
ShippingRange string `json:"shippingRange"`
WarehouseId int64 `json:"warehouseId"`
Remark string `json:"remark"`
PhoneNumber int64 `json:"phoneNumber"`
Contact string `json:"contact"`
FullAddress string `json:"fullAddress"`
}
type SyncLocationItemRequest struct {
Code string `json:"code" binding:"required"`
SheQuantityMax int64 `json:"sheQuantityMax" binding:"required"`
}
type SyncGoodsRequest struct {
UserID int64 `json:"user_id" binding:"required"`
Data []SyncGoodsItem `json:"data" binding:"required"`
}
type SyncGoodsItem struct {
GoodsName string `json:"goods_name" binding:"required"`
ISBN string `json:"isbn" binding:"required"`
Price int64 `json:"price" binding:"required"`
Appearance string `json:"appearance" binding:"required"`
LiveImage []string `json:"live_image" binding:"required"`
LocationCode string `json:"location_code" binding:"required"`
Inventory int64 `json:"inventory" binding:"required"`
}

View File

@ -0,0 +1,6 @@
package request
// LocationImportRow Excel中单行库位数据
type LocationImportRow struct {
Code string // 库位编码
}

View File

@ -0,0 +1,55 @@
package request
type QueryLogisticsRequest struct {
Keyword string `form:"keyword"`
Status *int `form:"status"`
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=10"`
}
type CreateLogisticsRequest struct {
TemplateName string `form:"template_name" binding:"required,max=255"`
DeliveryProvince string `form:"delivery_province" binding:"required,max=255"`
DeliveryCity string `form:"delivery_city" binding:"required,max=255"`
DeliveryArea string `form:"delivery_area" binding:"required,max=255"`
DeliveryAddress string `form:"delivery_address" binding:"required,max=255"`
PricingMethod string `form:"pricing_method" binding:"required,oneof=0 1 2 3"`
Shipping string `form:"shipping" binding:"required,oneof=0 1 2"`
FirWbv float64 `form:"fir_wbv"`
FirPrice float64 `form:"fir_price"`
ContinueWbv float64 `form:"continue_wbv"`
ContinuePrice float64 `form:"continue_price"`
Contact string `form:"contact" binding:"required,max=14"`
PhoneNumber uint64 `form:"phone_number" binding:"required"`
FullAddress string `form:"full_address" binding:"required,max=255"`
ShippingRange string `form:"shipping_range" binding:"required"`
WarehouseId uint64 `form:"warehouse_id"`
Remark string `form:"remark" binding:"max=255"`
Status string `form:"status" binding:"oneof=0 1"`
}
type UpdateLogisticsRequest struct {
Id uint64 `form:"id" binding:"required"`
TemplateName string `form:"template_name" binding:"required,max=255"`
DeliveryProvince string `form:"delivery_province" binding:"required,max=255"`
DeliveryCity string `form:"delivery_city" binding:"required,max=255"`
DeliveryArea string `form:"delivery_area" binding:"required,max=255"`
DeliveryAddress string `form:"delivery_address" binding:"required,max=255"`
PricingMethod string `form:"pricing_method" binding:"required,oneof=0 1 2 3"`
Shipping string `form:"shipping" binding:"required,oneof=0 1 2"`
FirWbv float64 `form:"fir_wbv"`
FirPrice float64 `form:"fir_price"`
ContinueWbv float64 `form:"continue_wbv"`
ContinuePrice float64 `form:"continue_price"`
Contact string `form:"contact" binding:"required,max=14"`
PhoneNumber uint64 `form:"phone_number" binding:"required"`
FullAddress string `form:"full_address" binding:"required,max=255"`
ShippingRange string `form:"shipping_range" binding:"required"`
WarehouseId uint64 `form:"warehouse_id"`
Remark string `form:"remark" binding:"max=255"`
Status string `form:"status" binding:"oneof=0 1"`
}
type DeleteLogisticsRequest struct {
Id uint64 `form:"id" binding:"required"`
}

View File

@ -0,0 +1,44 @@
package request
// GetOutTaskListRequest 获取外部任务列表请求
type GetOutTaskListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ShopID int64 `form:"shop_id"`
WaveTaskID int64 `form:"wave_task_id"`
ShopType int8 `form:"shop_type"`
TaskType int8 `form:"task_type"`
Status int8 `form:"status"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// GetOutTaskLogListRequest 获取外部任务日志列表请求
type GetOutTaskLogListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ShopID int64 `form:"shop_id"`
WaveTaskID int64 `form:"wave_task_id"`
OutTaskID int64 `form:"out_task_id" binding:"required"`
ProductID int64 `form:"product_id"`
Status int8 `form:"status"` // 1正常 0异常
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// UpdateOutTaskLogRequest 修改外部任务日志
type UpdateOutTaskLogRequest struct {
UserID int64 `form:"user_id" binding:"required"`
OutTaskID int64 `form:"out_task_id" binding:"required"`
ProductID int64 `form:"product_id" binding:"required"`
}
// GetOutTaskByShopRequest 按店铺获取外部任务列表请求
type GetOutTaskByShopRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ShopID int64 `form:"shop_id"`
ShopType int8 `form:"shop_type"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}

View File

@ -0,0 +1,24 @@
package request
// GetOutboundOrderListRequest 获取出库单列表请求
type GetOutboundOrderListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
OutNo string `form:"out_no"`
Status int8 `form:"status"`
CustomerID int64 `form:"customer_id"`
WarehouseID int64 `form:"warehouse_id"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// GetOutboundOrderDetailRequest 获取出库单详情请求
type GetOutboundOrderDetailRequest struct {
ID int64 `form:"id" binding:"required"`
}
// ChangeLocationRequest 出库单切换库位请求
type ChangeLocationRequest struct {
OutOrderItemID int64 `form:"out_order_item_id" binding:"required"`
Remark string `form:"remark"`
}

156
models/request/process.go Normal file
View File

@ -0,0 +1,156 @@
package request
type PurchaseOrderCreateRequest struct {
CarID int64 `form:"car_id" binding:"required"` // 采购订单ID
CarCode int64 `form:"car_code" binding:"required"` // 采购订单编号
WarehouseID int64 `form:"warehouse_id" binding:"required"` // 仓库ID
SupplierID int64 `form:"supplier_id"` // 供应商ID
Direction int8 `form:"direction"` // 采购订单方向
ExpectedArrivalDate int64 `form:"expected_arrival_date"` // 预计到达时间
Remark string `form:"remark"` // 备注
Items []PurchaseOrderItemRequest `form:"items[]"` // 采购订单项
}
type PurchaseOrderItemRequest struct {
ProductID int64 `form:"product_id" binding:"required"` // 商品ID
Quantity int64 `form:"quantity" binding:"required,gt=0"` // 数量
UnitPrice int64 `form:"unit_price" binding:"required,gt=0"` // 单价
}
type WaveRequest struct {
WaveID int64 `form:"wave_id" binding:"required"`
CarID int64 `form:"car_id"`
CarCode int64 `form:"car_code"`
RelatedOrderID int64 `form:"related_order_id" binding:"required"`
Assignee string `form:"assignee"`
AssigneeId int64 `form:"assignee_id"`
Items []WaveItemRequest `form:"items[]"`
}
type WaveItemRequest struct {
ProductID int64 `form:"product_id" binding:"required"`
Quantity int64 `form:"quantity" binding:"required,gt=0"`
UnitPrice int64 `form:"unit_price" binding:"required,gt=0"`
}
type BindWaveRequest struct {
WaveNo string `form:"wave_no" binding:"required"`
Operator string `form:"operator" binding:"required"`
OperatorID int64 `form:"operator_id" binding:"required"`
Remark string `form:"remark"`
}
type GetIdRequest struct {
ID int64 `form:"id" binding:"required"`
}
type ReceivingSubmitRequest struct {
ReceivingOrderID int64 `form:"receiving_order_id" binding:"required"`
WaveTaskID int64 `form:"wave_task_id" binding:"required"`
Force int8 `form:"force"`
Items []ReceivingItemRequest `form:"items[]"`
}
type ReceivingItemRequest struct {
ProductID int64 `form:"product_id" binding:"required"`
LocationID int64 `form:"location_id" binding:"required"`
BatchNo string `form:"batch_no" binding:"required"`
ProductionDate int64 `form:"production_date"`
ExpiryDate int64 `form:"expiry_date"`
Quantity int64 `form:"quantity" binding:"required,gt=0"`
//SerialNumbers []string `form:"serial_numbers"`
}
type SalesOrderCreateRequest struct {
AboutId int64 `form:"about_id"`
AssociationOrderID int64 `form:"association_order_id" binding:"required"`
AssociationOrderNo string `form:"association_order_no" binding:"required"`
FromType int8 `form:"from_type"`
ShopType int8 `form:"shop_type"`
CustomerID int64 `form:"customer_id"`
RequiredDeliveryDate int64 `form:"required_delivery_date"`
Remark string `form:"remark"`
SalesPerson string `form:"sales_person"`
SalesPersonID int64 `form:"sales_person_id"`
ReceiverName string `form:"receiver_name"`
ReceiverPhone string `form:"receiver_phone"`
ReceiverAddress string `form:"receiver_address"`
IsDistribution int8 `form:"is_distribution"`
Items []SalesOrderItemRequest `form:"items[]"`
}
// CreateOutboundOrderRequest 基于销售订单创建出库单请求
type CreateOutboundOrderRequest struct {
SalesOrderIDs []int64 `form:"order_ids[]"`
Total int `form:"total"`
Remark string `form:"remark"`
}
type CreateOutboundWaveRequest struct {
OutboundOrderID int64 `form:"outbound_order_id" binding:"required"`
}
type SalesOrderItemRequest struct {
ProductID int64 `form:"product_id" binding:"required"`
Quantity int64 `form:"quantity" binding:"required,gt=0"`
UnitPrice int64 `form:"unit_price" binding:"required,gt=0"`
}
type OutboundSubmitRequest struct {
OutboundOrderID int64 `form:"outbound_order_id" binding:"required"`
WaveTaskID int64 `form:"wave_task_id" binding:"required"`
Force int8 `form:"force"`
Items []OutboundItemRequest `form:"items[]"`
}
type OutboundItemRequest struct {
ProductID int64 `form:"product_id" binding:"required"`
LocationID int64 `form:"location_id" binding:"required"`
BatchNo string `form:"batch_no"`
ProductionDate int64 `form:"production_date"`
ExpiryDate int64 `form:"expiry_date"`
Quantity int64 `form:"quantity" binding:"required,gt=0"`
}
// CreateShippingOrderRequest 基于出库单创建发货单请求
type CreateShippingOrderRequest struct {
OutboundOrderIDs []int64 `form:"order_ids[]"`
Total int `form:"total"`
ExpectedArriveTime *int64 `form:"expected_arrive_time"`
Remark string `form:"remark"`
}
// UpdateShippingLogisticsRequest 更新发货单物流信息请求
type UpdateShippingLogisticsRequest struct {
ShippingOrderID int64 `form:"shipping_order_id" binding:"required"` // 发货单ID
Total int `form:"total"` // 发货单总数
SalesOrderItemID int64 `form:"sales_order_item_id" binding:"required"` // 销售订单项ID
LogisticsCompany string `form:"logistics_company" binding:"required"` // 物流公司名称
LogisticsNo string `form:"logistics_no" binding:"required"` // 物流单号
}
type CancelSalesOrderRequest struct {
OrderID int64 `form:"order_id" binding:"required"`
}
type CancelOutboundWaveRequest struct {
WaveID int64 `form:"wave_id" binding:"required"`
}
// StockCheckAdjustRequest 盘库调整请求(加库存/减库存)
type StockCheckAdjustRequest struct {
WarehouseID int64 `form:"warehouse_id" binding:"required"` // 仓库ID
ProductID int64 `form:"product_id" binding:"required"` // 商品ID
LocationID int64 `form:"location_id" binding:"required"` // 库位ID
BatchNo string `form:"batch_no"` // 批次号(可选)
Quantity int64 `form:"quantity" binding:"required,min=1"` // 调整数量(正数)
AdjustType int8 `form:"adjust_type" binding:"required,oneof=1 2"` // 调整类型:1=加库存,2=减库存
Remark string `form:"remark"` // 备注
}
// StockCheckReturnRequest 盘库退货请求
type StockCheckReturnRequest struct {
SalesOrderID int64 `form:"sales_order_id" binding:"required"` // 销售订单ID
SalesOrderItemID int64 `form:"sales_order_item_id" binding:"required"` // 销售订单明细ID
Remark string `form:"remark"` // 备注
}

181
models/request/product.go Normal file
View File

@ -0,0 +1,181 @@
package request
// ProductRequest 创建商品请求
type ProductRequest struct {
ID int64 `form:"id"` // 商品ID
CategoryID int64 `form:"category_id"` // 分类ID
StandardProductID int64 `form:"standard_product_id"` // 标准商品ID
Name string `form:"name" binding:"required"` // 商品名称
Appearance int64 `form:"appearance" binding:"required"` // 商品外观
Barcode string `form:"barcode" binding:"required"` // 商品条码
Price int64 `form:"price"` // 商品价格
LiveImage []string `form:"live_image[]"` // 商品图片
IsBatchManaged int8 `form:"is_batch_managed"` // 批次管理
IsShelfLifeManaged int8 `form:"is_shelf_life_managed"` // 保质期管理
Status int8 `form:"status"` // 商品状态
}
// GetProductListRequest 获取商品列表请求
type GetProductListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Keyword string `form:"keyword"`
Name string `form:"name"`
Barcode string `form:"barcode"`
Status string `form:"status"`
IDs []int64 `form:"ids[]"`
StartCreatedAt int64 `form:"start_created_at"`
EndCreatedAt int64 `form:"end_created_at"`
MinSalePrice int64 `form:"min_sale_price"`
MaxSalePrice int64 `form:"max_sale_price"`
MinStock int64 `form:"min_stock"`
MaxStock int64 `form:"max_stock"`
WarehouseID int64 `form:"warehouse_id"`
LocationID int64 `form:"location_id"`
}
// GetDistributionProductListRequest 获取分销商品列表请求
type GetDistributionProductListRequest struct {
UserID int64 `form:"user_id" binding:"required"`
Page int `form:"page"`
PageSize int `form:"page_size"`
Keyword string `form:"keyword"`
Name string `form:"name"`
Barcode string `form:"barcode"`
StartCreatedAt int64 `form:"start_created_at"`
EndCreatedAt int64 `form:"end_created_at"`
MinSalePrice int64 `form:"min_sale_price"`
MaxSalePrice int64 `form:"max_sale_price"`
MinStock int64 `form:"min_stock"`
MaxStock int64 `form:"max_stock"`
StockOperator string `form:"stock_operator"` // 库存运算符: ">", "<", "=", ">=", "<="
StockValue int64 `form:"stock_value"` // 库存数值
SalePriceOperator string `form:"saleprice_operator"` // 售价运算符: ">", "<", "=", ">=", "<="
SalePriceValue int64 `form:"saleprice_value"` // 售价数值
WarehouseID int64 `form:"warehouse_id"`
LocationID int64 `form:"location_id"`
Appearance int64 `form:"appearance"`
StartStockAt int64 `form:"start_stock_at"`
EndStockAt int64 `form:"end_stock_at"`
}
// UpdatePriceRequest 修改售价请求
type UpdatePriceRequest struct {
ProductID int64 `form:"product_id" binding:"required"` // 商品ID
UserID int64 `form:"user_id" binding:"required"` // 关联ID
SalePrice int64 `form:"sale_price" binding:"required"` // 售价
Cost int64 `form:"cost"` // 运费
}
// RetryOutTaskRequest 重试外部任务请求
type RetryOutTaskRequest struct {
ShopID int64 `form:"shop_id" binding:"required"` // 店铺ID
ShopType int8 `form:"shop_type" binding:"required"` // 店铺类型
ProductID int64 `form:"product_id" binding:"required"` // 商品ID
}
// BatchPushProductRequest 批量推送商品到店铺请求
type BatchPushProductRequest struct {
UserID int64 `form:"user_id" json:"user_id" binding:"required"` // 用户ID(租户)
ShopIDs []int64 `form:"shop_ids" json:"shop_ids" binding:"required"` // 商家ID数组
ShopTypes []int8 `form:"shop_types" json:"shop_types" binding:"required"` // 商家类型数组(与shop_ids一一对应)
ProductIDs []int64 `form:"product_ids" json:"product_ids" binding:"required"` // 商品ID数组
}
// ExportProductRequest 导出商品请求
type ExportProductRequest struct {
StartCreatedAt int64 `form:"start_created_at"` // 创建时间开始
EndCreatedAt int64 `form:"end_created_at"` // 创建时间结束
MinSalePrice int64 `form:"min_sale_price"` // 售价最小值
MaxSalePrice int64 `form:"max_sale_price"` // 售价最大值
MinStock int64 `form:"min_stock"` // 库存最小值
MaxStock int64 `form:"max_stock"` // 库存最大值
WarehouseID int64 `form:"warehouse_id"` // 仓库ID
Type int8 `form:"type" binding:"oneof=0 1"` // 0: 仓库商品, 1: 标准商品
}
// DeleteProductRequest 删除商品请求
type DeleteProductRequest struct {
ID int64 `form:"id" binding:"required"` // 商品ID
}
type ProductLogRequest struct {
ID int64 `form:"id"`
Barcode string `form:"barcode"`
OldName string `form:"old_name"`
NewName string `form:"new_name"`
OldPublisher string `form:"old_publisher"`
NewPublisher string `form:"new_publisher"`
OldAuthor string `form:"old_author"`
NewAuthor string `form:"new_author"`
OldPublicationTime int64 `form:"old_publication_time"`
NewPublicationTime int64 `form:"new_publication_time"`
OldPrice int64 `form:"old_price"`
NewPrice int64 `form:"new_price"`
OldBindingLayout string `form:"old_binding_layout"`
NewBindingLayout string `form:"new_binding_layout"`
OldPageCount int64 `form:"old_page_count"`
NewPageCount int64 `form:"new_page_count"`
OldWordCount int64 `form:"old_word_count"`
NewWordCount int64 `form:"new_word_count"`
OldLiveImage []string `form:"old_live_image[]"`
NewLiveImage []string `form:"new_live_image[]"`
OldIsSuit int8 `form:"old_is_suit"`
NewIsSuit int8 `form:"new_is_suit"`
}
type GetProductLogListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
Barcode string `form:"barcode"`
Status *int8 `form:"status"`
}
type AuditProductLogRequest struct {
ID int64 `form:"id" binding:"required"`
Status int8 `form:"status" binding:"required,oneof=1 2"`
NewName string `form:"new_name"`
NewPublisher string `form:"new_publisher"`
NewAuthor string `form:"new_author"`
NewPublicationTime int64 `form:"new_publication_time"`
NewPrice int64 `form:"new_price"`
NewBindingLayout string `form:"new_binding_layout"`
NewPageCount int64 `form:"new_page_count"`
NewWordCount int64 `form:"new_word_count"`
NewLiveImage []string `form:"new_live_image[]"`
NewIsSuit int8 `form:"new_is_suit"`
}
type DeleteProductLogRequest struct {
ID int64 `form:"id" binding:"required"`
}
// GetProductInventoryRequest 获取商品库存请求
type GetProductInventoryRequest struct {
UserID int64 `form:"user_id" binding:"required"` // 用户ID
ProductID int64 `form:"product_id" binding:"required"` // 商品ID
}
type GetShopProductDetailRequest struct {
ShopID int64 `form:"shop_id" binding:"required"`
Status int8 `form:"status"`
Page int `form:"page"`
PageSize int `form:"page_size"`
}
type GetProductDetailRequest struct {
ID int64 `form:"id" binding:"required"`
}
// PushProductToShopRequest 上架到商铺请求
type PushProductToShopRequest struct {
UserID int64 `form:"user_id" json:"user_id" binding:"required"` // 用户ID(租户)
WarehouseID int64 `form:"warehouse_id" json:"warehouse_id" binding:"required"` // 仓库ID
LocationID int64 `form:"location_id" json:"location_id" binding:"required"` // 货位ID
ISBN string `form:"isbn" json:"isbn" binding:"required"` // ISBN
ProductName string `form:"product_name" json:"product_name"` // 图书名称
Price int64 `form:"price" json:"price" binding:"required"` // 价格(分)
Stock int64 `form:"stock" json:"stock" binding:"required"` // 存量
Photos []string `form:"photos" json:"photos"` // 照片数组
Appearance int64 `form:"appearance" json:"appearance" binding:"required"` // 品相
}

View File

@ -0,0 +1,18 @@
package request
// GetPurchaseOrderListRequest 获取采购订单列表请求
type GetPurchaseOrderListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
PoNo string `form:"po_no"`
Status int8 `form:"status"`
SupplierID int64 `form:"supplier_id"`
WarehouseID int64 `form:"warehouse_id"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// GetPurchaseOrderDetailRequest 获取采购订单详情请求
type GetPurchaseOrderDetailRequest struct {
ID int64 `form:"id" binding:"required"`
}

View File

@ -0,0 +1,45 @@
package request
// GetReceivingOrderListRequest 获取入库单列表请求
type GetReceivingOrderListRequest struct {
Page int `form:"page"`
PageSize int `form:"page_size"`
ReceivingNo string `form:"receiving_no"`
Status int8 `form:"status"`
SupplierID int64 `form:"supplier_id"`
WarehouseID int64 `form:"warehouse_id"`
PurchaseOrderID int64 `form:"purchase_order_id"`
WaveTaskID int64 `form:"wave_task_id"`
StartDate int64 `form:"start_date"`
EndDate int64 `form:"end_date"`
}
// GetReceivingOrderDetailRequest 获取入库单详情请求
type GetReceivingOrderDetailRequest struct {
ID int64 `form:"id" binding:"required"`
}
// ExternalProductSyncRequest 外部商品同步请求
type ExternalProductSyncRequest struct {
ShopIDs string `json:"shopIds"` // 店铺id,多个用逗号分隔
Data []ExternalProductSyncData `json:"data"` // 商品数据数组
}
// ExternalProductSyncData 外部商品同步数据项
type ExternalProductSyncData struct {
ProductID int64 `json:"productId"` // 商品id
WarehouseID int64 `json:"warehouseId"` // 仓库id
TrilateralID int64 `json:"trilateralId"` // 平台商品id
Img string `json:"img"` // 图片地址
FinishTime int64 `json:"finishTime"` // 创建时间
TotalPrice int64 `json:"totalPrice"` // 价格 单位分
ISBN string `json:"isbn"` // ISBN
Title string `json:"title"` // 标题
Quality int64 `json:"quality"` // 品相 八五品就是85 全新就是100
GoodsCode string `json:"goodsCode"` // 商品编码
ShopType int8 `json:"shopType"` // 店铺类型 1拼多多 2孔夫子 5闲鱼
Stock int64 `json:"stock"` // 库存
IsOnSale int8 `json:"isOnSale"` // 上下架状态 1上架 0下架
SkuCode string `json:"skuCode"` // sku编号
SkuID int64 `json:"skuId"` // skuId
}

Some files were not shown because too many files have changed in this diff Show More