增加报销模块功能
This commit is contained in:
@@ -0,0 +1,701 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
"github.com/beego/beego/v2/client/orm"
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendReimburseController 报销及审批接口。
|
||||
type BackendReimburseController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type reimbursementItemPayload struct {
|
||||
ID uint64 `json:"id"`
|
||||
ExpenseType string `json:"expense_type"`
|
||||
ExpenseDate string `json:"expense_date"`
|
||||
Amount float64 `json:"amount"`
|
||||
Quantity int `json:"quantity"`
|
||||
UnitPrice float64 `json:"unit_price"`
|
||||
Description string `json:"description"`
|
||||
ReceiptFlag int8 `json:"receipt_flag"`
|
||||
ReceiptURL string `json:"receipt_url"`
|
||||
}
|
||||
|
||||
type reimbursementPayload struct {
|
||||
Title string `json:"title"`
|
||||
DepartmentID *uint64 `json:"department_id"`
|
||||
ExpenseType string `json:"expense_type"`
|
||||
ApplyDate string `json:"apply_date"`
|
||||
Description string `json:"description"`
|
||||
Items []reimbursementItemPayload `json:"items"`
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) claims() (*jwtutil.Claims, bool) {
|
||||
auth := strings.TrimSpace(c.Ctx.Request.Header.Get("Authorization"))
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return nil, false
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(parts[1])
|
||||
if err != nil || claims.UserType != "backend" || claims.TenantId <= 0 || claims.UserID <= 0 {
|
||||
return nil, false
|
||||
}
|
||||
return claims, true
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) reply(code int, msg string, data interface{}) {
|
||||
if code != 200 {
|
||||
c.Ctx.Output.SetStatus(code)
|
||||
}
|
||||
result := map[string]interface{}{"code": code, "msg": msg}
|
||||
if data != nil {
|
||||
result["data"] = data
|
||||
}
|
||||
c.Data["json"] = result
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) parsePayload() (reimbursementPayload, error) {
|
||||
var payload reimbursementPayload
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
return payload, err
|
||||
}
|
||||
err = json.Unmarshal(raw, &payload)
|
||||
return payload, err
|
||||
}
|
||||
|
||||
func parseReimburseDate(value string) (time.Time, error) {
|
||||
return time.Parse("2006-01-02", strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) getID() (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
||||
return id, err == nil && id > 0
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) findOwned(id uint64, claims *jwtutil.Claims) (*models.BackendReimbursement, error) {
|
||||
row := &models.BackendReimbursement{}
|
||||
err := models.Orm.QueryTable(new(models.BackendReimbursement)).
|
||||
Filter("id", id).
|
||||
Filter("tid", claims.TenantId).
|
||||
Filter("user_id", claims.UserID).
|
||||
Filter("is_deleted", 0).
|
||||
One(row)
|
||||
return row, err
|
||||
}
|
||||
|
||||
func reimbursementItemDTO(item models.BackendReimbursementItem) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"id": item.ID, "reimbursement_id": item.ReimbursementID,
|
||||
"expense_type": item.ExpenseType, "expense_date": item.ExpenseDate.Format("2006-01-02"),
|
||||
"amount": item.Amount, "quantity": item.Quantity, "unit_price": item.UnitPrice,
|
||||
"description": item.Description, "receipt_flag": item.ReceiptFlag, "receipt_url": item.ReceiptURL,
|
||||
}
|
||||
}
|
||||
|
||||
func reimbursementDTO(row models.BackendReimbursement, items []models.BackendReimbursementItem) map[string]interface{} {
|
||||
result := map[string]interface{}{
|
||||
"id": row.ID, "tid": row.Tid, "uid": row.UID, "user_id": row.UserID,
|
||||
"department_id": row.DepartmentID, "title": row.Title, "total_amount": row.TotalAmount,
|
||||
"status": row.Status, "expense_type": row.ExpenseType, "apply_date": row.ApplyDate.Format("2006-01-02"),
|
||||
"description": row.Description, "current_approver_id": row.CurrentApproverID,
|
||||
"current_step": row.CurrentStep, "create_time": row.CreateTime, "update_time": row.UpdateTime,
|
||||
}
|
||||
list := make([]map[string]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
list = append(list, reimbursementItemDTO(item))
|
||||
}
|
||||
result["items"] = list
|
||||
return result
|
||||
}
|
||||
|
||||
// List GET /backend/reimbursements
|
||||
func (c *BackendReimburseController) List() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
page, _ := c.GetInt("page", 1)
|
||||
pageSize, _ := c.GetInt("pageSize", 10)
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 10
|
||||
}
|
||||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||||
status, statusErr := c.GetInt("status")
|
||||
hasStatus := statusErr == nil
|
||||
|
||||
qs := models.Orm.QueryTable(new(models.BackendReimbursement)).
|
||||
Filter("tid", claims.TenantId).Filter("user_id", claims.UserID).Filter("is_deleted", 0)
|
||||
if keyword != "" {
|
||||
qs = qs.Filter("description__icontains", keyword)
|
||||
}
|
||||
if hasStatus {
|
||||
qs = qs.Filter("status", status)
|
||||
}
|
||||
total, err := qs.Count()
|
||||
if err != nil {
|
||||
c.reply(500, "查询失败", nil)
|
||||
return
|
||||
}
|
||||
var rows []models.BackendReimbursement
|
||||
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.reply(500, "查询失败", nil)
|
||||
return
|
||||
}
|
||||
if rows == nil {
|
||||
rows = []models.BackendReimbursement{}
|
||||
}
|
||||
list := make([]map[string]interface{}, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
list = append(list, map[string]interface{}{
|
||||
"id": row.ID,
|
||||
"tid": row.Tid,
|
||||
"uid": row.UID,
|
||||
"user_id": row.UserID,
|
||||
"department_id": row.DepartmentID,
|
||||
"title": row.Title,
|
||||
"total_amount": row.TotalAmount,
|
||||
"status": row.Status,
|
||||
"expense_type": row.ExpenseType,
|
||||
"apply_date": row.ApplyDate.Format("2006-01-02"),
|
||||
"description": row.Description,
|
||||
"current_approver_id": row.CurrentApproverID,
|
||||
"current_step": row.CurrentStep,
|
||||
"create_time": row.CreateTime,
|
||||
"update_time": row.UpdateTime,
|
||||
})
|
||||
}
|
||||
c.reply(200, "success", map[string]interface{}{"list": list, "total": total})
|
||||
}
|
||||
|
||||
// Detail GET /backend/reimbursements/:id
|
||||
func (c *BackendReimburseController) Detail() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效ID", nil)
|
||||
return
|
||||
}
|
||||
row, err := c.findOwned(id, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "报销单不存在", nil)
|
||||
return
|
||||
}
|
||||
var items []models.BackendReimbursementItem
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursementItem)).
|
||||
Filter("reimbursement_id", id).Filter("tid", claims.TenantId).Filter("is_deleted", 0).
|
||||
OrderBy("id").All(&items)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.reply(500, "查询明细失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", reimbursementDTO(*row, items))
|
||||
}
|
||||
|
||||
// Create POST /backend/reimbursements
|
||||
func (c *BackendReimburseController) Create() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
payload, err := c.parsePayload()
|
||||
if err != nil {
|
||||
c.reply(400, "参数错误", nil)
|
||||
return
|
||||
}
|
||||
applyDate, err := parseReimburseDate(payload.ApplyDate)
|
||||
if err != nil {
|
||||
c.reply(400, "申请日期格式错误", nil)
|
||||
return
|
||||
}
|
||||
uid := uint64(claims.UserID)
|
||||
row := &models.BackendReimbursement{
|
||||
Tid: uint64(claims.TenantId), UID: &uid, UserID: uid, DepartmentID: payload.DepartmentID,
|
||||
Title: "", TotalAmount: 0, Status: 0,
|
||||
ExpenseType: strings.TrimSpace(payload.ExpenseType), ApplyDate: applyDate,
|
||||
Description: nullableReimburseString(payload.Description),
|
||||
}
|
||||
if err := saveReimbursement(row); err != nil {
|
||||
c.reply(500, "创建报销单失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", map[string]interface{}{"id": row.ID})
|
||||
}
|
||||
|
||||
// Update POST /backend/reimbursements/:id
|
||||
func (c *BackendReimburseController) Update() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效ID", nil)
|
||||
return
|
||||
}
|
||||
row, err := c.findOwned(id, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "报销单不存在", nil)
|
||||
return
|
||||
}
|
||||
if row.Status != 0 {
|
||||
c.reply(400, "只有草稿可以编辑", nil)
|
||||
return
|
||||
}
|
||||
payload, err := c.parsePayload()
|
||||
if err != nil {
|
||||
c.reply(400, "参数错误", nil)
|
||||
return
|
||||
}
|
||||
applyDate, err := parseReimburseDate(payload.ApplyDate)
|
||||
if err != nil {
|
||||
c.reply(400, "申请日期格式错误", nil)
|
||||
return
|
||||
}
|
||||
row.ApplyDate, row.Description = applyDate, nullableReimburseString(payload.Description)
|
||||
if err := saveReimbursement(row); err != nil {
|
||||
c.reply(500, "更新报销单失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", map[string]interface{}{"id": row.ID})
|
||||
}
|
||||
|
||||
// Delete DELETE /backend/reimbursements/:id
|
||||
func (c *BackendReimburseController) Delete() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效ID", nil)
|
||||
return
|
||||
}
|
||||
row, err := c.findOwned(id, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "报销单不存在", nil)
|
||||
return
|
||||
}
|
||||
if row.Status != 0 {
|
||||
c.reply(400, "只有草稿可以删除", nil)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursement)).Filter("id", id).
|
||||
Update(orm.Params{"is_deleted": 1, "delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
c.reply(500, "删除失败", nil)
|
||||
return
|
||||
}
|
||||
_, _ = models.Orm.QueryTable(new(models.BackendReimbursementItem)).
|
||||
Filter("reimbursement_id", id).Update(orm.Params{"is_deleted": 1, "delete_time": now})
|
||||
c.reply(200, "success", nil)
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) parseItemPayload() (reimbursementItemPayload, error) {
|
||||
var payload reimbursementItemPayload
|
||||
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
||||
if err != nil {
|
||||
return payload, err
|
||||
}
|
||||
err = json.Unmarshal(raw, &payload)
|
||||
return payload, err
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) getItemID() (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Ctx.Input.Param(":item_id"), 10, 64)
|
||||
return id, err == nil && id > 0
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) findOwnedItem(id, reimbursementID uint64, claims *jwtutil.Claims) (*models.BackendReimbursementItem, error) {
|
||||
item := &models.BackendReimbursementItem{}
|
||||
err := models.Orm.QueryTable(new(models.BackendReimbursementItem)).
|
||||
Filter("id", id).
|
||||
Filter("reimbursement_id", reimbursementID).
|
||||
Filter("tid", claims.TenantId).
|
||||
Filter("uid", claims.UserID).
|
||||
Filter("is_deleted", 0).
|
||||
One(item)
|
||||
return item, err
|
||||
}
|
||||
|
||||
func validateReimbursementItem(item reimbursementItemPayload) (time.Time, error) {
|
||||
if strings.TrimSpace(item.ExpenseType) == "" {
|
||||
return time.Time{}, fmt.Errorf("费用类型不能为空")
|
||||
}
|
||||
date, err := parseReimburseDate(item.ExpenseDate)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("费用日期格式错误")
|
||||
}
|
||||
if item.Amount <= 0 {
|
||||
return time.Time{}, fmt.Errorf("费用金额必须大于0")
|
||||
}
|
||||
return date, nil
|
||||
}
|
||||
|
||||
func (c *BackendReimburseController) ensureDraftReimbursement(id uint64, claims *jwtutil.Claims) (*models.BackendReimbursement, bool) {
|
||||
row, err := c.findOwned(id, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "报销单不存在", nil)
|
||||
return nil, false
|
||||
}
|
||||
if row.Status != 0 {
|
||||
c.reply(400, "只有草稿可以维护费用明细", nil)
|
||||
return nil, false
|
||||
}
|
||||
return row, true
|
||||
}
|
||||
|
||||
func recalculateReimbursementTotal(id uint64) error {
|
||||
var items []models.BackendReimbursementItem
|
||||
_, err := models.Orm.QueryTable(new(models.BackendReimbursementItem)).
|
||||
Filter("reimbursement_id", id).Filter("is_deleted", 0).All(&items)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
var total float64
|
||||
for _, item := range items {
|
||||
total += item.Amount
|
||||
}
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursement)).
|
||||
Filter("id", id).Update(orm.Params{
|
||||
"total_amount": total,
|
||||
"update_time": time.Now(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// AddItem POST /backend/reimbursements/:id/items
|
||||
func (c *BackendReimburseController) AddItem() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
reimbursementID, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效报销单ID", nil)
|
||||
return
|
||||
}
|
||||
if _, ok := c.ensureDraftReimbursement(reimbursementID, claims); !ok {
|
||||
return
|
||||
}
|
||||
payload, err := c.parseItemPayload()
|
||||
if err != nil {
|
||||
c.reply(400, "参数错误", nil)
|
||||
return
|
||||
}
|
||||
date, err := validateReimbursementItem(payload)
|
||||
if err != nil {
|
||||
c.reply(400, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
uid := uint64(claims.UserID)
|
||||
quantity := payload.Quantity
|
||||
if quantity <= 0 {
|
||||
quantity = 1
|
||||
}
|
||||
item := &models.BackendReimbursementItem{
|
||||
Tid: uint64(claims.TenantId), UID: &uid, ReimbursementID: reimbursementID,
|
||||
ExpenseType: strings.TrimSpace(payload.ExpenseType), ExpenseDate: date,
|
||||
Amount: payload.Amount, Quantity: quantity, UnitPrice: payload.UnitPrice,
|
||||
Description: strings.TrimSpace(payload.Description), ReceiptFlag: payload.ReceiptFlag,
|
||||
ReceiptURL: strings.TrimSpace(payload.ReceiptURL),
|
||||
}
|
||||
if _, err := models.Orm.Insert(item); err != nil {
|
||||
c.reply(500, "新增费用明细失败", nil)
|
||||
return
|
||||
}
|
||||
if err := recalculateReimbursementTotal(reimbursementID); err != nil {
|
||||
c.reply(500, "更新报销总额失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", reimbursementItemDTO(*item))
|
||||
}
|
||||
|
||||
// UpdateItem POST /backend/reimbursements/:id/items/:item_id
|
||||
func (c *BackendReimburseController) UpdateItem() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
reimbursementID, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效报销单ID", nil)
|
||||
return
|
||||
}
|
||||
itemID, ok := c.getItemID()
|
||||
if !ok {
|
||||
c.reply(400, "无效费用明细ID", nil)
|
||||
return
|
||||
}
|
||||
if _, ok := c.ensureDraftReimbursement(reimbursementID, claims); !ok {
|
||||
return
|
||||
}
|
||||
item, err := c.findOwnedItem(itemID, reimbursementID, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "费用明细不存在", nil)
|
||||
return
|
||||
}
|
||||
payload, err := c.parseItemPayload()
|
||||
if err != nil {
|
||||
c.reply(400, "参数错误", nil)
|
||||
return
|
||||
}
|
||||
date, err := validateReimbursementItem(payload)
|
||||
if err != nil {
|
||||
c.reply(400, err.Error(), nil)
|
||||
return
|
||||
}
|
||||
quantity := payload.Quantity
|
||||
if quantity <= 0 {
|
||||
quantity = 1
|
||||
}
|
||||
item.ExpenseType = strings.TrimSpace(payload.ExpenseType)
|
||||
item.ExpenseDate = date
|
||||
item.Amount = payload.Amount
|
||||
item.Quantity = quantity
|
||||
item.UnitPrice = payload.UnitPrice
|
||||
item.Description = strings.TrimSpace(payload.Description)
|
||||
item.ReceiptFlag = payload.ReceiptFlag
|
||||
item.ReceiptURL = strings.TrimSpace(payload.ReceiptURL)
|
||||
if _, err := models.Orm.Update(item); err != nil {
|
||||
c.reply(500, "更新费用明细失败", nil)
|
||||
return
|
||||
}
|
||||
if err := recalculateReimbursementTotal(reimbursementID); err != nil {
|
||||
c.reply(500, "更新报销总额失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", reimbursementItemDTO(*item))
|
||||
}
|
||||
|
||||
// DeleteItem DELETE /backend/reimbursements/:id/items/:item_id
|
||||
func (c *BackendReimburseController) DeleteItem() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
reimbursementID, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效报销单ID", nil)
|
||||
return
|
||||
}
|
||||
itemID, ok := c.getItemID()
|
||||
if !ok {
|
||||
c.reply(400, "无效费用明细ID", nil)
|
||||
return
|
||||
}
|
||||
if _, ok := c.ensureDraftReimbursement(reimbursementID, claims); !ok {
|
||||
return
|
||||
}
|
||||
item, err := c.findOwnedItem(itemID, reimbursementID, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "费用明细不存在", nil)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursementItem)).
|
||||
Filter("id", item.ID).Update(orm.Params{"is_deleted": 1, "delete_time": now, "update_time": now})
|
||||
if err != nil {
|
||||
c.reply(500, "删除费用明细失败", nil)
|
||||
return
|
||||
}
|
||||
if err := recalculateReimbursementTotal(reimbursementID); err != nil {
|
||||
c.reply(500, "更新报销总额失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", nil)
|
||||
}
|
||||
|
||||
// Submit POST /backend/reimbursements/:id/submit
|
||||
func (c *BackendReimburseController) Submit() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效ID", nil)
|
||||
return
|
||||
}
|
||||
row, err := c.findOwned(id, claims)
|
||||
if err != nil {
|
||||
c.reply(404, "报销单不存在", nil)
|
||||
return
|
||||
}
|
||||
if row.Status != 0 {
|
||||
c.reply(400, "当前状态不可提交", nil)
|
||||
return
|
||||
}
|
||||
var flow models.BackendApprovalFlow
|
||||
err = models.Orm.QueryTable(new(models.BackendApprovalFlow)).
|
||||
Filter("tid", claims.TenantId).Filter("is_active", 1).Filter("is_deleted", 0).
|
||||
OrderBy("step_order").One(&flow)
|
||||
if err != nil {
|
||||
c.reply(400, "尚未配置审批流程", nil)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
_, err = models.Orm.QueryTable(new(models.BackendReimbursement)).Filter("id", id).Update(orm.Params{
|
||||
"status": 1, "current_step": flow.StepOrder, "current_approver_id": 0, "update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
c.reply(500, "提交失败", nil)
|
||||
return
|
||||
}
|
||||
_, err = models.Orm.Insert(&models.BackendApprovalRecord{
|
||||
Tid: uint64(claims.TenantId), UID: uint64Ptr(uint64(claims.UserID)), ReimbursementID: id,
|
||||
ApproverID: uint64(claims.UserID), ApproverName: claims.Username, Action: 1,
|
||||
Step: flow.StepOrder, PrevStep: 0,
|
||||
})
|
||||
if err != nil {
|
||||
c.reply(500, "提交审批记录失败", nil)
|
||||
return
|
||||
}
|
||||
c.reply(200, "success", nil)
|
||||
}
|
||||
|
||||
// Records GET /backend/reimbursements/:id/records
|
||||
func (c *BackendReimburseController) Records() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
id, ok := c.getID()
|
||||
if !ok {
|
||||
c.reply(400, "无效ID", nil)
|
||||
return
|
||||
}
|
||||
if _, err := c.findOwned(id, claims); err != nil {
|
||||
c.reply(404, "报销单不存在", nil)
|
||||
return
|
||||
}
|
||||
var rows []models.BackendApprovalRecord
|
||||
_, err := models.Orm.QueryTable(new(models.BackendApprovalRecord)).
|
||||
Filter("tid", claims.TenantId).Filter("reimbursement_id", id).Filter("is_deleted", 0).
|
||||
OrderBy("step", "id").All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.reply(500, "查询审批记录失败", nil)
|
||||
return
|
||||
}
|
||||
if rows == nil {
|
||||
rows = []models.BackendApprovalRecord{}
|
||||
}
|
||||
c.reply(200, "success", rows)
|
||||
}
|
||||
|
||||
// Types GET /backend/reimbursements/types
|
||||
func (c *BackendReimburseController) Types() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
var rows []models.BackendReimbursementExpenseType
|
||||
_, err := models.Orm.QueryTable(new(models.BackendReimbursementExpenseType)).
|
||||
Filter("tid", claims.TenantId).Filter("is_active", 1).Filter("is_deleted", 0).
|
||||
OrderBy("sort_order", "id").All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.reply(500, "查询费用类型失败", nil)
|
||||
return
|
||||
}
|
||||
if rows == nil {
|
||||
rows = []models.BackendReimbursementExpenseType{}
|
||||
}
|
||||
c.reply(200, "success", rows)
|
||||
}
|
||||
|
||||
// Flows GET /backend/reimbursements/flows
|
||||
func (c *BackendReimburseController) Flows() {
|
||||
claims, ok := c.claims()
|
||||
if !ok {
|
||||
c.reply(401, "未登录或无权限", nil)
|
||||
return
|
||||
}
|
||||
var rows []models.BackendApprovalFlow
|
||||
_, err := models.Orm.QueryTable(new(models.BackendApprovalFlow)).
|
||||
Filter("tid", claims.TenantId).Filter("is_active", 1).Filter("is_deleted", 0).
|
||||
OrderBy("step_order").All(&rows)
|
||||
if err != nil && err != orm.ErrNoRows {
|
||||
c.reply(500, "查询审批流程失败", nil)
|
||||
return
|
||||
}
|
||||
if rows == nil {
|
||||
rows = []models.BackendApprovalFlow{}
|
||||
}
|
||||
c.reply(200, "success", rows)
|
||||
}
|
||||
|
||||
func validateItems(items []reimbursementItemPayload) (float64, error) {
|
||||
var total float64
|
||||
for i, item := range items {
|
||||
if strings.TrimSpace(item.ExpenseType) == "" {
|
||||
return 0, fmt.Errorf("第%d条费用类型不能为空", i+1)
|
||||
}
|
||||
if _, err := parseReimburseDate(item.ExpenseDate); err != nil {
|
||||
return 0, fmt.Errorf("第%d条费用日期格式错误", i+1)
|
||||
}
|
||||
if item.Amount <= 0 {
|
||||
return 0, fmt.Errorf("第%d条费用金额必须大于0", i+1)
|
||||
}
|
||||
if item.Quantity <= 0 {
|
||||
item.Quantity = 1
|
||||
}
|
||||
total += item.Amount
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func saveReimbursement(row *models.BackendReimbursement) error {
|
||||
if row.ID == 0 {
|
||||
id, err := models.Orm.Insert(row)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
row.ID = uint64(id)
|
||||
return nil
|
||||
}
|
||||
_, err := models.Orm.Update(row)
|
||||
return err
|
||||
}
|
||||
|
||||
func nullableReimburseString(value string) *string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
return &value
|
||||
}
|
||||
|
||||
func uint64Ptr(value uint64) *uint64 {
|
||||
return &value
|
||||
}
|
||||
Reference in New Issue
Block a user