优化小程序报销模块

This commit is contained in:
hero920103 committed 2026-10-10 14:24:44 +08:00
1 parent 1e9666cefd
commit 48f3bd997c
21 files changed
+5738 -1750

No files matched your search

+344
View File
@@ -0,0 +1,344 @@
package controllers
import (
"encoding/json"
"io"
"strconv"
"strings"
"server/services"
)
// 移动端 OA 报销管理接口,挂在 AppOaController 上。
//
// 与 Web 管理端 /backend/reimbursements* 共用同一套数据
// (yz_backend_reimbursements / _items / _expense_types,按 JWT 中 tid + user_id
// 双重隔离,仅本人单据可见可操作)与核心逻辑(reimburse*Core 共享函数)。
// 小程序「报销管理」页面走这里,不直接调用 /backend/* 接口。
// expenseReadPayload 读取并校验新建/编辑报销单请求体;失败时直接输出错误响应。
func (c *AppOaController) expenseReadPayload() (reimbursementPayload, bool) {
var payload reimbursementPayload
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil || json.Unmarshal(raw, &payload) != nil {
c.oaErr(400, 400, "参数错误")
return payload, false
}
return payload, true
}
// expenseReadItemPayload 读取并校验费用明细请求体;失败时直接输出错误响应。
func (c *AppOaController) expenseReadItemPayload() (reimbursementItemPayload, bool) {
var payload reimbursementItemPayload
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil || json.Unmarshal(raw, &payload) != nil {
c.oaErr(400, 400, "参数错误")
return payload, false
}
return payload, true
}
// ExpenseList GET /api/oa/expense/list
// 移动端报销单列表:参数与返回结构与 /backend/reimbursements 一致
// (page/pageSize/keyword/status,返回 list/total)。
func (c *AppOaController) ExpenseList() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 10)
status, statusErr := c.GetInt("status")
data, code, msg := reimburseListData(claims, page, pageSize,
strings.TrimSpace(c.GetString("keyword")), statusErr == nil, status)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(data)
}
// ExpenseDetail GET /api/oa/expense/detail/:id
// 报销单详情(主单 + 费用明细 + 发票文件)。
func (c *AppOaController) ExpenseDetail() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效ID")
return
}
scheme, host := services.PublicRequestBaseURL(&c.Controller)
data, code, msg := reimburseDetailData(claims, id, scheme, host)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(data)
}
// ExpenseCreate POST /api/oa/expense/create 新建报销单(草稿)。
func (c *AppOaController) ExpenseCreate() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
payload, ok := c.expenseReadPayload()
if !ok {
return
}
id, code, msg := reimburseCreateCore(claims, payload)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(map[string]interface{}{"id": id})
}
// ExpenseUpdate POST /api/oa/expense/update/:id 编辑报销单(仅草稿)。
func (c *AppOaController) ExpenseUpdate() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效ID")
return
}
payload, ok := c.expenseReadPayload()
if !ok {
return
}
code, msg := reimburseUpdateCore(claims, id, payload)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(map[string]interface{}{"id": id})
}
// ExpenseDelete DELETE /api/oa/expense/delete/:id 删除报销单(仅草稿)。
func (c *AppOaController) ExpenseDelete() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效ID")
return
}
code, msg := reimburseDeleteCore(claims, id)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(nil)
}
// ExpenseAddItem POST /api/oa/expense/item/add/:id 新增费用明细(仅草稿)。
func (c *AppOaController) ExpenseAddItem() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效报销单ID")
return
}
payload, ok := c.expenseReadItemPayload()
if !ok {
return
}
scheme, host := services.PublicRequestBaseURL(&c.Controller)
item, code, msg := reimburseAddItemCore(claims, id, payload, scheme, host)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(item)
}
// ExpenseUpdateItem POST /api/oa/expense/item/update/:id/:item_id 更新费用明细(仅草稿)。
func (c *AppOaController) ExpenseUpdateItem() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
itemID := c.expenseItemIDParam()
if id == 0 || itemID == 0 {
c.oaErr(400, 400, "无效ID")
return
}
payload, ok := c.expenseReadItemPayload()
if !ok {
return
}
scheme, host := services.PublicRequestBaseURL(&c.Controller)
item, code, msg := reimburseUpdateItemCore(claims, id, itemID, payload, scheme, host)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(item)
}
// ExpenseDeleteItem DELETE /api/oa/expense/item/delete/:id/:item_id 删除费用明细(仅草稿)。
func (c *AppOaController) ExpenseDeleteItem() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
itemID := c.expenseItemIDParam()
if id == 0 || itemID == 0 {
c.oaErr(400, 400, "无效ID")
return
}
code, msg := reimburseDeleteItemCore(claims, id, itemID)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(nil)
}
// ExpenseSubmit POST /api/oa/expense/submit/:id 提交审批(仅草稿,需已配置审批流)。
func (c *AppOaController) ExpenseSubmit() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效ID")
return
}
code, msg := reimburseSubmitCore(claims, id)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(nil)
}
// ExpenseStatus POST /api/oa/expense/status/:id 手动确认更改单据状态(body: {status})。
func (c *AppOaController) ExpenseStatus() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效ID")
return
}
var payload reimbursementStatusPayload
raw, err := io.ReadAll(c.Ctx.Request.Body)
if len(raw) > 0 && (err != nil || json.Unmarshal(raw, &payload) != nil) {
c.oaErr(400, 400, "参数错误")
return
}
newStatus, code, msg := reimburseUpdateStatusCore(claims, id, payload.Status)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(map[string]interface{}{"status": newStatus})
}
// ExpenseRecords GET /api/oa/expense/records/:id 审批记录时间线。
func (c *AppOaController) ExpenseRecords() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
id := c.oaScheduleIDParam()
if id == 0 {
c.oaErr(400, 400, "无效ID")
return
}
rows, code, msg := reimburseRecordsData(claims, id)
if msg != "" {
c.oaErr(code, code, msg)
return
}
c.oaOk(rows)
}
// ExpenseTypes GET /api/oa/expense/types 费用类型下拉(全局通用 + 本租户专有,仅启用)。
func (c *AppOaController) ExpenseTypes() {
claims, err := c.oaClaims()
if err != nil {
c.oaErr(401, 401, "未登录或无权限")
return
}
rows, err := queryReimburseExpenseTypes(claims.TenantId, true)
if err != nil {
c.oaErr(500, 500, "查询费用类型失败")
return
}
list := make([]map[string]interface{}, 0, len(rows))
for _, row := range rows {
list = append(list, expenseTypeDTO(row, claims.TenantId))
}
c.oaOk(list)
}
// ExpenseUploadImage POST /api/oa/expense/upload(multipart,字段名 file)
// 上传报销发票图片,共享 uploadTenantImage,返回 {id,url,name};
// 随后由费用明细接口把文件 ID 写进 receipt_file_ids。
func (c *AppOaController) ExpenseUploadImage() {
c.uploadTenantImage()
}
// expenseItemIDParam 解析 :item_id 路由参数,非法返回 0。
func (c *AppOaController) expenseItemIDParam() uint64 {
id, err := strconv.ParseUint(c.Ctx.Input.Param(":item_id"), 10, 64)
if err != nil {
return 0
}
return id
}