feat(server): 完成 P0+P1 后端能力(工作流、RBAC、付款归档)
P0 MVP: - 认证:login/refresh/logout/forgot/reset + tokenVersion - RBAC:角色到权限映射 + requirePermission - 发票池:upload/bind/signed-url + 判重(VALID_003/VALID_005) - 报销:create/view/submit + 单号生成 - 工作流:approve + department_manager 解析 P1 扩展: - 动作:reject/return/withdraw(含 comment) - transfer:任务转审 + 链路上限 3 - pay:approved -> paid + 发票归档 used_complete - 动态审批链:approval_rules 驱动 回归脚本: - smoke-test.sh / regression-dedup.sh / regression-workflow-actions.sh
这个提交包含在:
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "d5575dd2-7e6f-4675-9f82-b52f22de8de0",
|
||||
"name": "hyb-oa P0 MVP",
|
||||
"description": "P0 vertical slice: login -> draft -> upload -> bind -> submit -> approve",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"item": [
|
||||
{
|
||||
"name": "1. Login Employee",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" },
|
||||
{ "key": "x-device-fingerprint", "value": "{{deviceFingerprint}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/auth/login",
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"identifier\": \"{{employeeIdentifier}}\",\n \"password\": \"{{employeePassword}}\"\n}"
|
||||
}
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const json = pm.response.json();",
|
||||
"pm.environment.set('employeeAccessToken', json.data.accessToken);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2. Create Reimbursement",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" },
|
||||
{ "key": "Authorization", "value": "Bearer {{employeeAccessToken}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/reimbursements",
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"description\": \"P0 冒烟报销单\"\n}"
|
||||
}
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const json = pm.response.json();",
|
||||
"pm.environment.set('reimbursementId', json.data.id);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "3. Upload Invoice",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Authorization", "value": "Bearer {{employeeAccessToken}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/invoice-pool/upload",
|
||||
"body": {
|
||||
"mode": "formdata",
|
||||
"formdata": [
|
||||
{
|
||||
"key": "file",
|
||||
"type": "file",
|
||||
"src": "{{sampleInvoiceFile}}"
|
||||
},
|
||||
{
|
||||
"key": "ocr_type",
|
||||
"type": "text",
|
||||
"value": "general"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const json = pm.response.json();",
|
||||
"pm.environment.set('invoicePoolId', json.data.invoicePoolId);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "4. Bind Invoice",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" },
|
||||
{ "key": "Authorization", "value": "Bearer {{employeeAccessToken}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/invoice-pool/{{invoicePoolId}}/bind",
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"reimbursementId\": \"{{reimbursementId}}\"\n}"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "5. Submit Reimbursement",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Authorization", "value": "Bearer {{employeeAccessToken}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/reimbursements/{{reimbursementId}}/submit"
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const json = pm.response.json();",
|
||||
"pm.environment.set('workflowTaskId', json.data.currentTaskId);",
|
||||
"pm.environment.set('workflowInstanceId', json.data.workflowInstanceId);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "6. Login CEO",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Content-Type", "value": "application/json" },
|
||||
{ "key": "x-device-fingerprint", "value": "{{deviceFingerprint}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/auth/login",
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"identifier\": \"{{ceoIdentifier}}\",\n \"password\": \"{{ceoPassword}}\"\n}"
|
||||
}
|
||||
},
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"type": "text/javascript",
|
||||
"exec": [
|
||||
"const json = pm.response.json();",
|
||||
"pm.environment.set('ceoAccessToken', json.data.accessToken);"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "7. Approve Workflow Task",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Authorization", "value": "Bearer {{ceoAccessToken}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/workflow/tasks/{{workflowTaskId}}/approve"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8. Get Signed URL",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{ "key": "Authorization", "value": "Bearer {{employeeAccessToken}}" }
|
||||
],
|
||||
"url": "{{baseUrl}}/api/invoice-pool/{{invoicePoolId}}/signed-url"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户