1413 lines
30 KiB
Markdown
1413 lines
30 KiB
Markdown
# 月度核算中心与实时考勤看板 Implementation Plan
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**Goal:** 将当前薪酬考勤系统从“多个功能页分散操作”升级为“实时考勤看板 + 月度核算中心”的主流程系统,支持钉钉实时同步、Excel 导入、考勤标准化、薪资预估、月度工资复核、锁定和导出。
|
||
|
||
**Architecture:** 后端继续沿用 FastAPI 分层架构:router 只处理 HTTP 和权限,schema 只定义请求响应,service 负责编排,repository 负责 ORM 读写,domain 负责纯计算。新增实时考勤和月度核算模块时,复用现有 `PayrollCalculator`、`MonthlySummaryParser`、`DingTalkAttendanceAdapter`、`attendance_record`、`payroll_results` 等能力,只补齐同步批次、实时预估、月度核算状态和异常记录。
|
||
|
||
**Tech Stack:** Python 3 / FastAPI / SQLAlchemy / MySQL / openpyxl / Vue 3 / Pinia / Vue Router / TypeScript / Vite / lucide-vue。
|
||
|
||
---
|
||
|
||
## 目标业务流
|
||
|
||
```text
|
||
实时考勤看板
|
||
-> 读取在职员工和钉钉 userId
|
||
-> 同步今天或本月至今的钉钉打卡
|
||
-> 标准化为每日考勤
|
||
-> 展示今日打卡、迟到、缺卡、请假、加班和本月薪资预估
|
||
|
||
月度核算中心
|
||
-> 选择工资月份
|
||
-> 同步钉钉或导入 Excel
|
||
-> 标准化考勤
|
||
-> 生成异常清单
|
||
-> 计算工资
|
||
-> 复核员工明细
|
||
-> 锁定本月工资
|
||
-> 导出工资表和报表
|
||
```
|
||
|
||
## 阶段 0:开发前保护
|
||
|
||
### Task 0.1: 建立当前基线
|
||
|
||
**Files:**
|
||
- Read: `README.md`
|
||
- Read: `financial_system/database/orm.py`
|
||
- Read: `financial_system/services/payroll_service.py`
|
||
- Read: `financial_system/api/routers/payroll.py`
|
||
- Read: `frontend/src/router/index.ts`
|
||
- Read: `frontend/src/components/SidebarNav.vue`
|
||
- Read: `frontend/src/views/PayrollView.vue`
|
||
|
||
**Step 1: 查看工作区状态**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
git status --short
|
||
```
|
||
|
||
Expected: 确认哪些文件已有用户改动。不要回滚任何非本任务改动。
|
||
|
||
**Step 2: 编译当前后端**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: `financial_system` 编译成功。
|
||
|
||
**Step 3: 构建当前前端**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: Vite build 通过。
|
||
|
||
**Step 4: Commit**
|
||
|
||
本任务只做基线验证,不需要 commit。
|
||
|
||
---
|
||
|
||
## 阶段 1:数据库模型与 SQL
|
||
|
||
### Task 1.1: 增加实时同步和月度核算 ORM
|
||
|
||
**Files:**
|
||
- Modify: `financial_system/database/orm.py`
|
||
- Modify: `financial_system/database/sql/schema.sql`
|
||
- Modify: `financial_system/database/sql/init_mysql.sql`
|
||
- Create: `financial_system/database/sql/upgrade_20260618_monthly_payroll_center.sql`
|
||
|
||
**Step 1: 设计新增表**
|
||
|
||
新增 4 张表,所有表和字段都必须有 MySQL 注释:
|
||
|
||
```text
|
||
attendance_sync_jobs
|
||
id
|
||
source_type
|
||
sync_mode
|
||
attendance_date
|
||
salary_month
|
||
start_date
|
||
end_date
|
||
status
|
||
employee_count
|
||
record_count
|
||
error_message
|
||
created_by
|
||
created_at
|
||
updated_at
|
||
|
||
salary_preview
|
||
id
|
||
sync_job_id
|
||
employee_id
|
||
employee_no
|
||
employee_name
|
||
department
|
||
position
|
||
salary_month
|
||
attendance_days
|
||
actual_work_hours
|
||
punch_overtime_hours
|
||
leave_hours
|
||
remaining_overtime_hours
|
||
late_deduction
|
||
missing_card_deduction
|
||
commission_amount
|
||
overtime_pay
|
||
estimated_gross_salary
|
||
estimated_net_salary
|
||
status
|
||
note
|
||
created_at
|
||
updated_at
|
||
|
||
monthly_payroll_runs
|
||
id
|
||
salary_month
|
||
source_type
|
||
source_job_id
|
||
status
|
||
employee_count
|
||
gross_total
|
||
net_total
|
||
deduction_total
|
||
overtime_total
|
||
locked_by
|
||
locked_at
|
||
created_by
|
||
created_at
|
||
updated_at
|
||
|
||
payroll_exceptions
|
||
id
|
||
run_id
|
||
sync_job_id
|
||
employee_id
|
||
employee_no
|
||
employee_name
|
||
salary_month
|
||
exception_type
|
||
severity
|
||
message
|
||
status
|
||
created_at
|
||
resolved_at
|
||
```
|
||
|
||
**Step 2: 修改 ORM**
|
||
|
||
在 `financial_system/database/orm.py` 中新增:
|
||
|
||
```python
|
||
class AttendanceSyncJobORM(Base):
|
||
"""钉钉或 Excel 考勤同步批次,用于实时看板和月度核算追踪。"""
|
||
__tablename__ = "attendance_sync_jobs"
|
||
...
|
||
|
||
class SalaryPreviewORM(Base):
|
||
"""本月进行中的薪资预估结果,不作为最终工资发放依据。"""
|
||
__tablename__ = "salary_preview"
|
||
...
|
||
|
||
class MonthlyPayrollRunORM(Base):
|
||
"""月度正式核算批次,支持复核、锁定和导出。"""
|
||
__tablename__ = "monthly_payroll_runs"
|
||
...
|
||
|
||
class PayrollExceptionORM(Base):
|
||
"""核算异常记录,例如缺员工、缺薪资档案、缺卡、请假无法识别。"""
|
||
__tablename__ = "payroll_exceptions"
|
||
...
|
||
```
|
||
|
||
字段类型沿用现有风格:金额用 `Float`,月份用 `String(7)`,状态用 `String(32)`,时间用 `DateTime`。
|
||
|
||
**Step 3: 更新 SQL**
|
||
|
||
在 `schema.sql` 和 `init_mysql.sql` 中增加建表语句。新增 `upgrade_20260618_monthly_payroll_center.sql`,包含 `CREATE TABLE IF NOT EXISTS`,可对已有库安全执行。
|
||
|
||
**Step 4: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add financial_system/database/orm.py financial_system/database/sql/schema.sql financial_system/database/sql/init_mysql.sql financial_system/database/sql/upgrade_20260618_monthly_payroll_center.sql
|
||
git commit -m "feat: add monthly payroll center tables"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 1.2: 增加权限和菜单
|
||
|
||
**Files:**
|
||
- Modify: `financial_system/core/permissions.py`
|
||
- Modify: `frontend/src/router/index.ts`
|
||
- Modify: `frontend/src/components/SidebarNav.vue`
|
||
|
||
**Step 1: 增加权限码**
|
||
|
||
在 `financial_system/core/permissions.py` 增加:
|
||
|
||
```python
|
||
PERMISSION_ATTENDANCE_REALTIME_VIEW = "attendance:realtime:view"
|
||
PERMISSION_ATTENDANCE_REALTIME_SYNC = "attendance:realtime:sync"
|
||
PERMISSION_MONTHLY_PAYROLL_VIEW = "monthly_payroll:view"
|
||
PERMISSION_MONTHLY_PAYROLL_CALCULATE = "monthly_payroll:calculate"
|
||
PERMISSION_MONTHLY_PAYROLL_LOCK = "monthly_payroll:lock"
|
||
PERMISSION_MONTHLY_PAYROLL_EXPORT = "monthly_payroll:export"
|
||
```
|
||
|
||
**Step 2: 增加菜单**
|
||
|
||
新增菜单:
|
||
|
||
```python
|
||
"attendance_realtime": MenuPermission(
|
||
code="attendance_realtime",
|
||
name="实时考勤",
|
||
path="/attendance/realtime",
|
||
),
|
||
"monthly_payroll": MenuPermission(
|
||
code="monthly_payroll",
|
||
name="月度核算",
|
||
path="/payroll/monthly",
|
||
),
|
||
```
|
||
|
||
超级用户和管理者可见两个菜单;查看权限至少可见 `实时考勤` 和报表,是否能看月度工资按业务需要决定,默认仅查看 `实时考勤`。
|
||
|
||
**Step 3: 前端路由占位**
|
||
|
||
在 `frontend/src/router/index.ts` 先注册页面:
|
||
|
||
```typescript
|
||
{
|
||
path: "attendance/realtime",
|
||
name: "attendance-realtime",
|
||
component: RealtimeAttendanceView,
|
||
meta: { permission: "attendance:realtime:view" },
|
||
},
|
||
{
|
||
path: "payroll/monthly",
|
||
name: "monthly-payroll",
|
||
component: MonthlyPayrollView,
|
||
meta: { permission: "monthly_payroll:view" },
|
||
},
|
||
```
|
||
|
||
**Step 4: 侧边栏图标**
|
||
|
||
在 `frontend/src/components/SidebarNav.vue` 中增加图标映射:
|
||
|
||
```typescript
|
||
attendance_realtime: CalendarClock,
|
||
monthly_payroll: Calculator,
|
||
```
|
||
|
||
**Step 5: 验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add financial_system/core/permissions.py frontend/src/router/index.ts frontend/src/components/SidebarNav.vue
|
||
git commit -m "feat: add realtime attendance and monthly payroll menus"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 2:后端实时考勤看板
|
||
|
||
### Task 2.1: 增加实时考勤 schema
|
||
|
||
**Files:**
|
||
- Create: `financial_system/api/schemas/realtime_attendance.py`
|
||
- Modify: `financial_system/api/schemas/__init__.py`
|
||
|
||
**Step 1: 编写 schema**
|
||
|
||
Create `financial_system/api/schemas/realtime_attendance.py`:
|
||
|
||
```python
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, datetime
|
||
from pydantic import BaseModel
|
||
|
||
|
||
class RealtimeAttendanceSyncRequest(BaseModel):
|
||
attendance_date: date
|
||
include_salary_preview: bool = True
|
||
|
||
|
||
class TodayAttendanceItem(BaseModel):
|
||
employee_id: int | None
|
||
employee_no: str
|
||
employee_name: str
|
||
department: str
|
||
position: str
|
||
first_punch: str
|
||
last_punch: str
|
||
attendance_status: str
|
||
late_minutes: int
|
||
missing_card_count: int
|
||
leave_hours: float
|
||
today_overtime_hours: float
|
||
month_remaining_overtime_hours: float
|
||
estimated_net_salary: float | None
|
||
note: str
|
||
|
||
|
||
class RealtimeAttendanceSummary(BaseModel):
|
||
attendance_date: date
|
||
employee_total: int
|
||
checked_in_count: int
|
||
unchecked_count: int
|
||
late_count: int
|
||
leave_count: int
|
||
missing_card_count: int
|
||
estimated_overtime_count: int
|
||
estimated_net_total: float
|
||
|
||
|
||
class RealtimeAttendanceResponse(BaseModel):
|
||
sync_job_id: str | None
|
||
synced_at: datetime | None
|
||
summary: RealtimeAttendanceSummary
|
||
items: list[TodayAttendanceItem]
|
||
```
|
||
|
||
**Step 2: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add financial_system/api/schemas/realtime_attendance.py financial_system/api/schemas/__init__.py
|
||
git commit -m "feat: add realtime attendance schemas"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.2: 增加实时考勤 Repository
|
||
|
||
**Files:**
|
||
- Create: `financial_system/repositories/realtime_attendance_repository.py`
|
||
- Modify: `financial_system/repositories/__init__.py`
|
||
|
||
**Step 1: 实现 repository**
|
||
|
||
Repository 负责:
|
||
|
||
- 查询在职员工和钉钉 userId。
|
||
- 创建 `attendance_sync_jobs`。
|
||
- 保存 `attendance_record`。
|
||
- 保存 `salary_preview`。
|
||
- 查询某日考勤记录。
|
||
- 查询某月薪资预估。
|
||
|
||
核心方法:
|
||
|
||
```python
|
||
class RealtimeAttendanceRepository:
|
||
def list_active_employees_with_dingtalk(self) -> list[EmployeeORM]: ...
|
||
def create_sync_job(...) -> AttendanceSyncJobORM: ...
|
||
def mark_sync_completed(...) -> None: ...
|
||
def mark_sync_failed(...) -> None: ...
|
||
def replace_attendance_records(...) -> None: ...
|
||
def replace_salary_previews(...) -> None: ...
|
||
def list_today_records(...) -> list[AttendanceRecordORM]: ...
|
||
def list_salary_previews(...) -> list[SalaryPreviewORM]: ...
|
||
```
|
||
|
||
**Step 2: 注意幂等**
|
||
|
||
同一天多次同步时,应按 `source_type=dingtalk + work_date + employee_id` 替换当天记录,避免列表越来越多。
|
||
|
||
**Step 3: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add financial_system/repositories/realtime_attendance_repository.py financial_system/repositories/__init__.py
|
||
git commit -m "feat: add realtime attendance repository"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.3: 增加实时薪资预估服务
|
||
|
||
**Files:**
|
||
- Create: `financial_system/services/realtime_attendance_service.py`
|
||
- Modify: `financial_system/services/__init__.py`
|
||
- Modify: `financial_system/api/dependencies.py`
|
||
|
||
**Step 1: 实现服务职责**
|
||
|
||
`RealtimeAttendanceService` 负责:
|
||
|
||
- 从数据库拿在职员工。
|
||
- 自动收集员工 `dingtalk_user_id`,不再让用户手填。
|
||
- 调用 `DingTalkClient.list_attendance_records`。
|
||
- 使用 `DingTalkAttendanceAdapter` 转成 `EmployeeAttendance`。
|
||
- 使用 `PayrollCalculator` 计算本月薪资预估。
|
||
- 写入 `attendance_record` 和 `salary_preview`。
|
||
- 输出今日考勤列表和汇总。
|
||
|
||
**Step 2: 保持预估语义**
|
||
|
||
返回字段命名使用 `estimated_*`,页面文案必须展示“预估金额,最终以月度核算锁定结果为准”。
|
||
|
||
**Step 3: 缺少钉钉配置**
|
||
|
||
复用 `PayrollApplicationService._dingtalk_settings()` 的逻辑,或者抽出公共 helper。缺少 `app_key/app_secret` 时返回 400,提示用户修改 `config/app_settings.json`。
|
||
|
||
**Step 4: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add financial_system/services/realtime_attendance_service.py financial_system/services/__init__.py financial_system/api/dependencies.py
|
||
git commit -m "feat: add realtime attendance service"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 2.4: 增加实时考勤 API
|
||
|
||
**Files:**
|
||
- Create: `financial_system/api/routers/realtime_attendance.py`
|
||
- Modify: `financial_system/api/app.py`
|
||
|
||
**Step 1: 新增路由**
|
||
|
||
Create router prefix:
|
||
|
||
```python
|
||
router = APIRouter(prefix="/api/attendance/realtime", tags=["realtime-attendance"])
|
||
```
|
||
|
||
Endpoints:
|
||
|
||
```text
|
||
GET /api/attendance/realtime/today?date=YYYY-MM-DD
|
||
POST /api/attendance/realtime/sync
|
||
```
|
||
|
||
**Step 2: 权限**
|
||
|
||
- `GET today` 使用 `PERMISSION_ATTENDANCE_REALTIME_VIEW`
|
||
- `POST sync` 使用 `PERMISSION_ATTENDANCE_REALTIME_SYNC`
|
||
|
||
**Step 3: 操作日志**
|
||
|
||
同步成功记录:
|
||
|
||
```text
|
||
module=attendance
|
||
action=attendance.realtime.sync
|
||
target_type=attendance_sync_job
|
||
```
|
||
|
||
查看成功记录:
|
||
|
||
```text
|
||
module=attendance
|
||
action=attendance.realtime.view
|
||
```
|
||
|
||
**Step 4: 注册路由**
|
||
|
||
在 `financial_system/api/app.py` 导入并 `include_router(realtime_attendance.router)`。
|
||
|
||
**Step 5: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add financial_system/api/routers/realtime_attendance.py financial_system/api/app.py
|
||
git commit -m "feat: add realtime attendance api"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 3:后端月度核算中心
|
||
|
||
### Task 3.1: 增加月度核算 schema
|
||
|
||
**Files:**
|
||
- Create: `financial_system/api/schemas/monthly_payroll.py`
|
||
- Modify: `financial_system/api/schemas/__init__.py`
|
||
|
||
**Step 1: 定义请求响应**
|
||
|
||
Create:
|
||
|
||
```python
|
||
class MonthlyPayrollCreateRequest(BaseModel):
|
||
salary_month: str
|
||
source_type: str
|
||
export_excel: bool = True
|
||
|
||
|
||
class MonthlyPayrollImportExcelResponse(BaseModel):
|
||
run_id: int
|
||
status: str
|
||
employee_count: int
|
||
exception_count: int
|
||
|
||
|
||
class MonthlyPayrollRunResponse(BaseModel):
|
||
id: int
|
||
salary_month: str
|
||
source_type: str
|
||
status: str
|
||
employee_count: int
|
||
gross_total: float
|
||
net_total: float
|
||
deduction_total: float
|
||
overtime_total: float
|
||
locked_by: str
|
||
locked_at: datetime | None
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
|
||
class MonthlyPayrollDetailResponse(BaseModel):
|
||
run: MonthlyPayrollRunResponse
|
||
results: list[PayrollResultItem]
|
||
exceptions: list[PayrollExceptionItem]
|
||
```
|
||
|
||
`PayrollResultItem` 可复用现有 `financial_system/api/schemas/payroll.py` 中的结果 schema,避免重复字段。
|
||
|
||
**Step 2: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add financial_system/api/schemas/monthly_payroll.py financial_system/api/schemas/__init__.py
|
||
git commit -m "feat: add monthly payroll schemas"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.2: 增加月度核算 Repository
|
||
|
||
**Files:**
|
||
- Create: `financial_system/repositories/monthly_payroll_repository.py`
|
||
- Modify: `financial_system/repositories/__init__.py`
|
||
|
||
**Step 1: 实现方法**
|
||
|
||
Repository 负责:
|
||
|
||
```python
|
||
class MonthlyPayrollRepository:
|
||
def create_run(...) -> MonthlyPayrollRunORM: ...
|
||
def get_run(run_id: int) -> MonthlyPayrollRunORM | None: ...
|
||
def get_run_by_month(salary_month: str) -> MonthlyPayrollRunORM | None: ...
|
||
def update_run_totals(...) -> None: ...
|
||
def lock_run(...) -> None: ...
|
||
def replace_exceptions(...) -> None: ...
|
||
def list_exceptions(...) -> list[PayrollExceptionORM]: ...
|
||
```
|
||
|
||
**Step 2: 锁定保护**
|
||
|
||
如果 `monthly_payroll_runs.status == "locked"`,任何重新计算或覆盖结果都必须拒绝。
|
||
|
||
**Step 3: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add financial_system/repositories/monthly_payroll_repository.py financial_system/repositories/__init__.py
|
||
git commit -m "feat: add monthly payroll repository"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.3: 增加异常识别规则
|
||
|
||
**Files:**
|
||
- Create: `financial_system/domain/payroll_exceptions.py`
|
||
|
||
**Step 1: 实现纯函数**
|
||
|
||
Create:
|
||
|
||
```python
|
||
def detect_payroll_exceptions(results: list[PayrollResult]) -> list[PayrollException]:
|
||
...
|
||
```
|
||
|
||
异常类型:
|
||
|
||
```text
|
||
missing_salary_profile
|
||
missing_employee_match
|
||
missing_dingtalk_user_id
|
||
missing_card
|
||
late_penalty
|
||
negative_remaining_overtime
|
||
empty_attendance
|
||
```
|
||
|
||
**Step 2: 规则建议**
|
||
|
||
- `base_pay is None` -> `missing_salary_profile`
|
||
- `missing_card_count > 0` -> `missing_card`
|
||
- `penalized_late_count > 0` -> `late_penalty`
|
||
- `remaining_overtime_hours < 0` -> `negative_remaining_overtime`
|
||
- `attendance_days == 0` -> `empty_attendance`
|
||
|
||
**Step 3: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add financial_system/domain/payroll_exceptions.py
|
||
git commit -m "feat: add payroll exception detection"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.4: 增加月度核算服务
|
||
|
||
**Files:**
|
||
- Create: `financial_system/services/monthly_payroll_service.py`
|
||
- Modify: `financial_system/services/__init__.py`
|
||
- Modify: `financial_system/api/dependencies.py`
|
||
|
||
**Step 1: 服务职责**
|
||
|
||
`MonthlyPayrollService` 负责:
|
||
|
||
- Excel 导入并创建月度 run。
|
||
- 钉钉全量同步并创建月度 run。
|
||
- 复用 `PayrollApplicationService` 或直接复用 parser/calculator。
|
||
- 生成工资结果。
|
||
- 生成异常记录。
|
||
- 更新 run 汇总金额。
|
||
- 锁定工资。
|
||
- 导出工资文件。
|
||
|
||
**Step 2: 状态机**
|
||
|
||
状态只保留必要值:
|
||
|
||
```text
|
||
draft 草稿/已导入
|
||
calculated 已计算
|
||
reviewed 已复核
|
||
locked 已锁定
|
||
failed 失败
|
||
```
|
||
|
||
**Step 3: 锁定规则**
|
||
|
||
锁定后:
|
||
|
||
- 不允许重新计算。
|
||
- 不允许覆盖结果。
|
||
- 允许导出。
|
||
- 操作日志必须记录锁定人。
|
||
|
||
**Step 4: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add financial_system/services/monthly_payroll_service.py financial_system/services/__init__.py financial_system/api/dependencies.py
|
||
git commit -m "feat: add monthly payroll service"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 3.5: 增加月度核算 API
|
||
|
||
**Files:**
|
||
- Create: `financial_system/api/routers/monthly_payroll.py`
|
||
- Modify: `financial_system/api/app.py`
|
||
|
||
**Step 1: 新增路由**
|
||
|
||
Create router prefix:
|
||
|
||
```python
|
||
router = APIRouter(prefix="/api/payroll/monthly", tags=["monthly-payroll"])
|
||
```
|
||
|
||
Endpoints:
|
||
|
||
```text
|
||
GET /api/payroll/monthly/runs?month=YYYY-MM
|
||
GET /api/payroll/monthly/runs/{run_id}
|
||
POST /api/payroll/monthly/excel
|
||
POST /api/payroll/monthly/dingtalk
|
||
POST /api/payroll/monthly/runs/{run_id}/recalculate
|
||
POST /api/payroll/monthly/runs/{run_id}/lock
|
||
GET /api/payroll/monthly/runs/{run_id}/export
|
||
```
|
||
|
||
**Step 2: 权限**
|
||
|
||
- 查看:`PERMISSION_MONTHLY_PAYROLL_VIEW`
|
||
- 计算:`PERMISSION_MONTHLY_PAYROLL_CALCULATE`
|
||
- 锁定:`PERMISSION_MONTHLY_PAYROLL_LOCK`
|
||
- 导出:`PERMISSION_MONTHLY_PAYROLL_EXPORT`
|
||
|
||
**Step 3: 操作日志**
|
||
|
||
记录以下动作:
|
||
|
||
```text
|
||
monthly_payroll.view
|
||
monthly_payroll.excel.import
|
||
monthly_payroll.dingtalk.calculate
|
||
monthly_payroll.recalculate
|
||
monthly_payroll.lock
|
||
monthly_payroll.export
|
||
```
|
||
|
||
**Step 4: 注册路由**
|
||
|
||
在 `financial_system/api/app.py` 导入并 include。
|
||
|
||
**Step 5: 编译验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add financial_system/api/routers/monthly_payroll.py financial_system/api/app.py
|
||
git commit -m "feat: add monthly payroll api"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 4:前端信息架构和 API
|
||
|
||
### Task 4.1: 重组菜单展示
|
||
|
||
**Files:**
|
||
- Modify: `frontend/src/components/SidebarNav.vue`
|
||
- Modify: `frontend/src/assets/styles.css`
|
||
|
||
**Step 1: 分组菜单**
|
||
|
||
将菜单视觉分组为:
|
||
|
||
```text
|
||
工作台
|
||
实时考勤
|
||
月度核算
|
||
工资核算
|
||
计算记录
|
||
提成录入
|
||
员工薪资
|
||
员工维护
|
||
薪资档案
|
||
组织架构
|
||
报表分析
|
||
工资报表
|
||
系统管理
|
||
规则配置
|
||
用户管理
|
||
操作日志
|
||
```
|
||
|
||
**Step 2: 收起侧栏体验**
|
||
|
||
收起时隐藏分组文字,只保留图标;悬停显示 `title`。保持现在的平滑过渡变量:
|
||
|
||
```css
|
||
--motion-duration: 280ms;
|
||
--motion-easing: cubic-bezier(0.22, 1, 0.36, 1);
|
||
```
|
||
|
||
**Step 3: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/components/SidebarNav.vue frontend/src/assets/styles.css
|
||
git commit -m "feat: group sidebar navigation"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 4.2: 增加前端 API 类型
|
||
|
||
**Files:**
|
||
- Modify: `frontend/src/api/types.ts`
|
||
- Create: `frontend/src/api/realtimeAttendance.ts`
|
||
- Create: `frontend/src/api/monthlyPayroll.ts`
|
||
|
||
**Step 1: 增加类型**
|
||
|
||
在 `types.ts` 增加:
|
||
|
||
```typescript
|
||
export interface RealtimeAttendanceSummary { ... }
|
||
export interface TodayAttendanceItem { ... }
|
||
export interface RealtimeAttendanceResponse { ... }
|
||
export interface MonthlyPayrollRun { ... }
|
||
export interface PayrollException { ... }
|
||
export interface MonthlyPayrollDetailResponse { ... }
|
||
```
|
||
|
||
**Step 2: 增加 API 方法**
|
||
|
||
Create `realtimeAttendance.ts`:
|
||
|
||
```typescript
|
||
export function getTodayAttendance(date: string): Promise<RealtimeAttendanceResponse> { ... }
|
||
export function syncRealtimeAttendance(date: string): Promise<RealtimeAttendanceResponse> { ... }
|
||
```
|
||
|
||
Create `monthlyPayroll.ts`:
|
||
|
||
```typescript
|
||
export function listMonthlyRuns(month?: string): Promise<MonthlyPayrollRun[]> { ... }
|
||
export function getMonthlyRun(runId: number): Promise<MonthlyPayrollDetailResponse> { ... }
|
||
export function calculateMonthlyFromExcel(...): Promise<MonthlyPayrollDetailResponse> { ... }
|
||
export function calculateMonthlyFromDingTalk(month: string): Promise<MonthlyPayrollDetailResponse> { ... }
|
||
export function lockMonthlyRun(runId: number): Promise<MonthlyPayrollRun> { ... }
|
||
export function exportMonthlyRun(runId: number): Promise<void> { ... }
|
||
```
|
||
|
||
**Step 3: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 4: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/api/types.ts frontend/src/api/realtimeAttendance.ts frontend/src/api/monthlyPayroll.ts
|
||
git commit -m "feat: add realtime and monthly payroll api clients"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 5:实时考勤页面
|
||
|
||
### Task 5.1: 创建实时考勤页面
|
||
|
||
**Files:**
|
||
- Create: `frontend/src/views/RealtimeAttendanceView.vue`
|
||
- Modify: `frontend/src/router/index.ts`
|
||
|
||
**Step 1: 页面布局**
|
||
|
||
页面结构:
|
||
|
||
```text
|
||
顶部:今日考勤与薪资预估 / 日期选择 / 同步钉钉 / 自动刷新开关
|
||
摘要:应到人数、已打卡、未打卡、迟到、请假、预估加班
|
||
主表:员工今日考勤
|
||
右侧抽屉:员工本月明细
|
||
```
|
||
|
||
**Step 2: 主表字段**
|
||
|
||
```text
|
||
员工
|
||
部门/岗位
|
||
上班打卡
|
||
下班打卡
|
||
今日状态
|
||
迟到分钟
|
||
缺卡
|
||
请假工时
|
||
今日加班
|
||
本月剩余加班
|
||
本月预估实发
|
||
```
|
||
|
||
**Step 3: 文案**
|
||
|
||
页面必须展示:
|
||
|
||
```text
|
||
预估金额,最终以月度核算锁定结果为准
|
||
```
|
||
|
||
**Step 4: 视觉要求**
|
||
|
||
- 不使用大面积营销式卡片。
|
||
- 摘要卡片压扁,适合后台扫描。
|
||
- 表格上方保留紧凑筛选栏。
|
||
- 员工明细使用右侧抽屉,不跳页面。
|
||
|
||
**Step 5: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 6: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/views/RealtimeAttendanceView.vue frontend/src/router/index.ts
|
||
git commit -m "feat: add realtime attendance view"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 5.2: 接入实时考勤页面数据
|
||
|
||
**Files:**
|
||
- Modify: `frontend/src/views/RealtimeAttendanceView.vue`
|
||
|
||
**Step 1: 加载数据**
|
||
|
||
页面 mounted 后默认请求今天:
|
||
|
||
```typescript
|
||
const today = new Date().toISOString().slice(0, 10);
|
||
await getTodayAttendance(today);
|
||
```
|
||
|
||
**Step 2: 同步钉钉**
|
||
|
||
点击 `同步钉钉` 调用:
|
||
|
||
```typescript
|
||
await syncRealtimeAttendance(selectedDate.value);
|
||
```
|
||
|
||
同步时按钮显示 loading,成功后刷新摘要和表格。
|
||
|
||
**Step 3: 错误处理**
|
||
|
||
钉钉未配置时显示:
|
||
|
||
```text
|
||
钉钉配置缺失,请先在 config/app_settings.json 中配置 app_key 和 app_secret
|
||
```
|
||
|
||
**Step 4: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/views/RealtimeAttendanceView.vue
|
||
git commit -m "feat: connect realtime attendance data"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 6:月度核算中心页面
|
||
|
||
### Task 6.1: 创建月度核算中心页面
|
||
|
||
**Files:**
|
||
- Create: `frontend/src/views/MonthlyPayrollView.vue`
|
||
- Modify: `frontend/src/router/index.ts`
|
||
|
||
**Step 1: 页面结构**
|
||
|
||
```text
|
||
顶部:月份选择 / 当前状态 / 同步钉钉 / 导入 Excel / 导出 / 锁定
|
||
步骤条:数据导入 -> 考勤转换 -> 异常处理 -> 工资计算 -> 复核锁定
|
||
摘要条:员工数、应发、实发、扣款、剩余加班
|
||
主表:员工工资核算结果
|
||
右侧抽屉:员工每日考勤和工资拆分
|
||
异常面板:缺薪资档案、缺卡、迟到扣款、未匹配员工
|
||
```
|
||
|
||
**Step 2: 状态文案**
|
||
|
||
```text
|
||
draft: 草稿
|
||
calculated: 已计算
|
||
reviewed: 已复核
|
||
locked: 已锁定
|
||
failed: 失败
|
||
```
|
||
|
||
**Step 3: 锁定按钮**
|
||
|
||
`locked` 状态下禁用重新计算和导入,只保留导出。
|
||
|
||
**Step 4: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/views/MonthlyPayrollView.vue frontend/src/router/index.ts
|
||
git commit -m "feat: add monthly payroll center view"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 6.2: 接入月度核算 API
|
||
|
||
**Files:**
|
||
- Modify: `frontend/src/views/MonthlyPayrollView.vue`
|
||
|
||
**Step 1: 默认月份**
|
||
|
||
默认当前月份:
|
||
|
||
```typescript
|
||
const selectedMonth = ref(new Date().toISOString().slice(0, 7));
|
||
```
|
||
|
||
**Step 2: 加载 run**
|
||
|
||
进入页面或月份变化时加载:
|
||
|
||
```typescript
|
||
await listMonthlyRuns(selectedMonth.value);
|
||
```
|
||
|
||
如果已有 run,自动打开最新 run;否则显示空状态和操作按钮。
|
||
|
||
**Step 3: Excel 导入**
|
||
|
||
导入按钮使用隐藏 file input,不让用户填配置路径。
|
||
|
||
**Step 4: 钉钉计算**
|
||
|
||
点击 `同步钉钉并计算`,后端自动读取在职员工 `dingtalk_user_id`。
|
||
|
||
**Step 5: 锁定确认**
|
||
|
||
锁定前弹窗确认:
|
||
|
||
```text
|
||
锁定后本月工资不能重新计算,只能导出。确认锁定?
|
||
```
|
||
|
||
**Step 6: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 7: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/views/MonthlyPayrollView.vue
|
||
git commit -m "feat: connect monthly payroll center api"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 7:工作台改造
|
||
|
||
### Task 7.1: 工作台改为流程总览
|
||
|
||
**Files:**
|
||
- Modify: `frontend/src/views/DashboardView.vue`
|
||
- Modify: `frontend/src/assets/styles.css`
|
||
|
||
**Step 1: 改成经营总览**
|
||
|
||
工作台展示:
|
||
|
||
```text
|
||
今日考勤状态
|
||
本月工资预估
|
||
本月核算状态
|
||
待处理异常
|
||
最近同步任务
|
||
```
|
||
|
||
**Step 2: 快捷入口**
|
||
|
||
保留按钮:
|
||
|
||
```text
|
||
进入实时考勤
|
||
进入月度核算
|
||
提成录入
|
||
员工维护
|
||
```
|
||
|
||
**Step 3: 移除假图表**
|
||
|
||
现在 `chartBars` 是静态假数据。要么接后端报表,要么先移除,避免误导。
|
||
|
||
**Step 4: 构建验证**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 5: Commit**
|
||
|
||
```bash
|
||
git add frontend/src/views/DashboardView.vue frontend/src/assets/styles.css
|
||
git commit -m "feat: redesign dashboard around payroll workflow"
|
||
```
|
||
|
||
---
|
||
|
||
## 阶段 8:文档与验证
|
||
|
||
### Task 8.1: 更新 README
|
||
|
||
**Files:**
|
||
- Modify: `README.md`
|
||
- Modify: `frontend/README.md`
|
||
|
||
**Step 1: README 增加模块说明**
|
||
|
||
补充:
|
||
|
||
```text
|
||
实时考勤看板
|
||
月度核算中心
|
||
薪资预估和正式工资的区别
|
||
锁定规则
|
||
新增 SQL 升级脚本
|
||
```
|
||
|
||
**Step 2: 更新运行说明**
|
||
|
||
说明后端仍然使用:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python main.py
|
||
```
|
||
|
||
前端仍然使用:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run dev
|
||
```
|
||
|
||
**Step 3: Commit**
|
||
|
||
```bash
|
||
git add README.md frontend/README.md
|
||
git commit -m "docs: update payroll center documentation"
|
||
```
|
||
|
||
---
|
||
|
||
### Task 8.2: 全量验证
|
||
|
||
**Files:**
|
||
- No source edits unless verification fails.
|
||
|
||
**Step 1: 后端编译**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python -m compileall financial_system
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 2: 前端构建**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run build
|
||
```
|
||
|
||
Expected: PASS。
|
||
|
||
**Step 3: 启动后端**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
/Users/jiaolongyan/miniconda3/envs/Financial_System/bin/python main.py
|
||
```
|
||
|
||
Expected: 后端启动成功,`/health` 返回正常。
|
||
|
||
**Step 4: 启动前端**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm run dev
|
||
```
|
||
|
||
Expected: 前端启动成功。
|
||
|
||
**Step 5: 浏览器验收**
|
||
|
||
检查:
|
||
|
||
```text
|
||
/attendance/realtime
|
||
/payroll/monthly
|
||
/dashboard
|
||
/system/employees
|
||
/system/salary-profiles
|
||
/payroll/commissions
|
||
```
|
||
|
||
验收点:
|
||
|
||
- 菜单显示分组合理。
|
||
- 实时考勤能进入,未配置钉钉时提示明确。
|
||
- 月度核算能进入,Excel 导入入口存在。
|
||
- 锁定状态不会允许重新计算。
|
||
- 侧栏收起/展开仍然平滑。
|
||
- 控制台无 error/warn。
|
||
|
||
**Step 6: Commit**
|
||
|
||
若修复了验证问题,按实际文件提交:
|
||
|
||
```bash
|
||
git add <changed-files>
|
||
git commit -m "fix: stabilize payroll center flow"
|
||
```
|
||
|
||
---
|
||
|
||
## 实施顺序建议
|
||
|
||
优先级从高到低:
|
||
|
||
1. 阶段 1:数据库和菜单权限。
|
||
2. 阶段 2:实时考勤后端。
|
||
3. 阶段 5:实时考勤前端。
|
||
4. 阶段 3:月度核算后端。
|
||
5. 阶段 6:月度核算前端。
|
||
6. 阶段 7:工作台改造。
|
||
7. 阶段 8:文档和全量验证。
|
||
|
||
这样可以先让你看到“今天打卡和实时薪资预估”的效果,再继续完善月底正式核算。
|
||
|
||
## 风险点
|
||
|
||
- 钉钉接口真实字段可能和当前 `DingTalkAttendanceAdapter` 的假设不完全一致,需要用真实返回样例校准。
|
||
- 实时薪资只能作为预估,不能覆盖正式工资结果。
|
||
- 包月员工的“本月预估工资”要确认是否按自然日、工作日进度、还是直接展示已产生工资。
|
||
- 计件员工如果没有每日计件数据,只能显示已录入计件金额,不能凭考勤自动估算。
|
||
- 锁定工资后是否允许管理员解锁,需要单独确认。当前计划默认不提供解锁。
|
||
|
||
## 后续可选增强
|
||
|
||
- 自动同步定时任务:每天 09:30、12:00、18:30 同步钉钉。
|
||
- 异常提醒:缺卡、迟到、未绑定钉钉 userId 自动出现在工作台。
|
||
- 工资条:锁定后生成员工工资条。
|
||
- 审批流:人事核算、财务复核、老板确认。
|
||
- 导入计件数据:为计件员工增加单独的产量录入或 Excel 导入。
|