Files
yunzerwebsiteallinone/go/controllers/app_oa_expense.go
T
2026-10-16 23:46:49 +08:00

346 lines
8.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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:5})。
// 仅限「已通过」的单据确认收款,其它状态由审批引擎推进。
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
}