# 洗车系统 (car-wash-java)

## 项目结构

```
car-wash-java/
├── car-wash-entity/      # 实体、VO、SQL 迁移脚本
├── car-wash-mapper/      # MyBatis-Plus Mapper
├── car-wash-service/     # 核心业务逻辑
├── car-wash-common/      # 共享工具（Sa-Token、Redis、AOP）
├── car-wash-admin/       # 管理后台 API（Spring Boot）
├── car-wash-miniapp/     # 微信小程序 API（Spring Boot）
├── car-wash-mp/          # 微信公众号 API（Spring Boot）
├── admin-web/            # 运营管理后台前端（Vue 3 + Element Plus + Vite）
├── admin-h5/             # 运营商 H5（uni-app）
└── admin-web-new/        # 管理后台前端重构版（Vite + Vue 3）
```

## 技术栈

- **后端**: Java 21, Spring Boot 3.3.3, MyBatis-Plus 3.x, Sa-Token, Redis + Redisson, RabbitMQ, 微信支付 APIv3
- **前端**: Vue 3 + TypeScript + Vite + Element Plus + Pinia + uni-app
- **构建**: Maven 多模块

## 核心业务表

| 表 | 用途 |
|---|---|
| `t_user` | 微信小程序用户（`station_id` 标识归属站点） |
| `t_wash_order` | 洗车订单（`is_cross` 标记是否跨店消费） |
| `t_split_record` | 分账流水，按类型追踪站点间资金流向 |
| `t_settlement_record` | 月度结算单（幂等键 `uk_station_period`） |
| `t_station_account` | 站点账户余额（`available_balance` 可提现额） |
| `t_wallet_detail` | 用户钱包流水（充值/消费/退款） |
| `t_pay_log` | 微信支付日志（`out_trade_no` 商户订单号，`transaction_id` 微信交易号） |
| `t_recharge_config` | 充值配置（归属 `group_id` 分组） |
| `t_recharge_config_group` | 充值配置分组（`is_default=1` 为默认分组） |

## 结算 V2 模型

月度结算由 `SettlementServiceImpl.executeMonthlySettlement()` 驱动，每月对上月数据进行结算。

**SplitRecord 资金类型**:
| 类型 | 值 | 方向 |
|---|---|---|
| `TYPE_RECHARGE` | 1 | 同一站点收支，`from=to` |
| `TYPE_CROSS_EXPEND` | 4 | 跨店：归属站 → 消费站 |
| `TYPE_CROSS_INCOME` | 6 | 跨店：归属站 → 消费站（与 EXPEND 配对） |
| `TYPE_REFUND` | 5 | 退款支出 |

**月结公式**:
```
feeBase = totalRecharge - totalRefund - totalCrossExpend + totalCrossIncome
platformFee = feeBase × 10%
available = openingBalance + feeBase - platformFee
```

`available > 0` 时正常结算，转入站点可提现余额，同时记录平台服务费。负数则结转下期。

## 关键业务链路

### 用户注册与站点归属
- 扫码设备注册：直接通过 `shortId` 确定 `stationId`（`UserServiceImpl.java:135-137`）
- 自主注册：`stationId = null`，首次消费时在 `WashOrderServiceImpl.createOrder()` 中自动归属

### 充值 → 分账
- 有归属站：`WxPayServiceImpl.wxNotify()` 中创建 `SplitRecord(TYPE_RECHARGE, from=stationId, to=stationId)`
- 无归属站：不创建 SplitRecord，由 `OrderSettlementServiceImpl.backfillUnattributedRecharges()` 在首次消费结算时追溯补建

### 消费 → 结算
- `createOrder()` → 扣预付款，设置 `isCross` 标记
- `settleOrder()` → 扣款，回填无归属充值，本店跳过/跨店创建分账

## 注意事项
- `SaToken` 注解 `@SaIgnore` 需要确保被 Spring 代理拦截才能生效
- 分账记录 `tradeNo` 与微信支付 `transactionId` 对应，通过 `t_pay_log` 桥接 `WalletDetail`
- 月结有幂等保护（`uk_station_period`），重复执行不会产生重复结算单
