优化小程序报销模块

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

+125 -62
View File
@@ -160,6 +160,21 @@ func (c *BackendOaScheduleController) List() {
endDate := strings.TrimSpace(c.GetString("end_date"))
category := strings.TrimSpace(c.GetString("category"))
data, code, msg := oaScheduleListData(claims.TenantId, claims.UserID,
page, pageSize, keyword, status, priority, startDate, endDate, category)
if msg != "" {
c.oasJsonErr(code, code, msg)
return
}
c.oasOk(data)
}
// oaScheduleListData 日程列表的共享查询逻辑,Web 后台(/backend/oa/schedule/list)
// 与移动端(/api/oa/schedule/list)共用。
// 返回 (数据, HTTP 状态码, 错误信息),错误信息为空串表示成功。
func oaScheduleListData(tid, userID, page, pageSize int,
keyword, status, priority, startDate, endDate, category string) (map[string]interface{}, int, string) {
if page < 1 {
page = 1
}
@@ -181,7 +196,7 @@ func (c *BackendOaScheduleController) List() {
// 分类角标按全量统计(只受关键词/优先级影响),不随日历翻月或区间筛选跳动
countOf := func(f oaScheduleFilter) int64 {
n, _ := oaScheduleQS(claims.TenantId, claims.UserID, f).Count()
n, _ := oaScheduleQS(tid, userID, f).Count()
return n
}
today := time.Now().Format("2006-01-02")
@@ -214,16 +229,15 @@ func (c *BackendOaScheduleController) List() {
listFilter.Status = 1
}
qs := oaScheduleQS(claims.TenantId, claims.UserID, listFilter)
qs := oaScheduleQS(tid, userID, listFilter)
total, _ := qs.Count()
var list []models.OaSchedule
_, err = qs.OrderBy("schedule_date", "start_time", "-id").
_, err := qs.OrderBy("schedule_date", "start_time", "-id").
Limit(pageSize).Offset((page - 1) * pageSize).
All(&list)
if err != nil && err != orm.ErrNoRows {
c.oasJsonErr(500, 500, "查询失败")
return
return nil, 500, "查询失败"
}
if list == nil {
list = []models.OaSchedule{}
@@ -234,11 +248,11 @@ func (c *BackendOaScheduleController) List() {
list[i].ScheduleDate = normalizeOaDate(list[i].ScheduleDate)
}
c.oasOk(map[string]interface{}{
return map[string]interface{}{
"list": list,
"total": total,
"counts": counts,
})
}, 200, ""
}
// Holidays GET /backend/oa/holiday
@@ -590,25 +604,36 @@ func parseOaScheduleImages(raw string) []oaScheduleImage {
// parseOaSchedulePayload 读取并校验新建/编辑日程的请求体;校验失败时直接输出错误响应。
func (c *BackendOaScheduleController) parseOaSchedulePayload() (oaSchedulePayload, bool) {
var payload oaSchedulePayload
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil || json.Unmarshal(raw, &payload) != nil {
if err != nil {
c.oasJsonErr(400, 400, "参数错误")
return oaSchedulePayload{}, false
}
payload, msg := parseOaSchedulePayloadBody(raw)
if msg != "" {
c.oasJsonErr(400, 400, msg)
return payload, false
}
return payload, true
}
// parseOaSchedulePayloadBody 新建/编辑日程请求体的共享校验逻辑,
// Web 后台与移动端共用。返回 (校验后的载荷, 错误信息),错误信息为空串表示成功。
func parseOaSchedulePayloadBody(raw []byte) (oaSchedulePayload, string) {
var payload oaSchedulePayload
if len(raw) == 0 || json.Unmarshal(raw, &payload) != nil {
return payload, "参数错误"
}
payload.Title = strings.TrimSpace(payload.Title)
if payload.Title == "" {
c.oasJsonErr(400, 400, "请输入日程标题")
return payload, false
return payload, "请输入日程标题"
}
if utf8.RuneCountInString(payload.Title) > 80 {
c.oasJsonErr(400, 400, "日程标题不能超过80字")
return payload, false
return payload, "日程标题不能超过80字"
}
if !isValidOaDate(payload.ScheduleDate) {
c.oasJsonErr(400, 400, "日程日期无效")
return payload, false
return payload, "日程日期无效"
}
if payload.AllDay != 0 {
payload.AllDay = 1
@@ -616,17 +641,14 @@ func (c *BackendOaScheduleController) parseOaSchedulePayload() (oaSchedulePayloa
payload.EndTime = ""
} else {
if !isValidOaTime(payload.StartTime) {
c.oasJsonErr(400, 400, "开始时间无效")
return payload, false
return payload, "开始时间无效"
}
if payload.EndTime != "" {
if !isValidOaTime(payload.EndTime) {
c.oasJsonErr(400, 400, "结束时间无效")
return payload, false
return payload, "结束时间无效"
}
if payload.EndTime < payload.StartTime {
c.oasJsonErr(400, 400, "结束时间不能早于开始时间")
return payload, false
return payload, "结束时间不能早于开始时间"
}
}
}
@@ -649,18 +671,16 @@ func (c *BackendOaScheduleController) parseOaSchedulePayload() (oaSchedulePayloa
}
payload.RemindChannels = validChannels
if len(validChannels) == 0 {
c.oasJsonErr(400, 400, "请至少选择一个提醒渠道")
return payload, false
return payload, "请至少选择一个提醒渠道"
}
if _, err := time.ParseInLocation("2006-01-02 15:04:05", payload.ReminderTime, time.Local); err != nil {
c.oasJsonErr(400, 400, "提醒时间格式不合法,支持 YYYY-MM-DD HH:mm:ss")
return payload, false
return payload, "提醒时间格式不合法,支持 YYYY-MM-DD HH:mm:ss"
}
if payload.AdvanceMinutes < 0 {
payload.AdvanceMinutes = 0
}
}
return payload, true
return payload, ""
}
// Create POST /backend/oa/schedule/create
@@ -676,6 +696,17 @@ func (c *BackendOaScheduleController) Create() {
return
}
item, code, msg := oaScheduleCreateCore(claims, payload)
if msg != "" {
c.oasJsonErr(code, code, msg)
return
}
c.oasOk(item)
}
// oaScheduleCreateCore 创建日程(含提醒绑定)的共享逻辑,Web 后台与移动端共用。
// 返回 (新建日程, HTTP 状态码, 错误信息),错误信息为空串表示成功。
func oaScheduleCreateCore(claims *jwtutil.Claims, payload oaSchedulePayload) (*models.OaSchedule, int, string) {
now := time.Now()
item := &models.OaSchedule{
Tid: claims.TenantId,
@@ -695,8 +726,7 @@ func (c *BackendOaScheduleController) Create() {
UpdateTime: &now,
}
if _, err := models.Orm.Insert(item); err != nil {
c.oasJsonErr(500, 500, "保存失败")
return
return nil, 500, "保存失败"
}
// 开启提醒:生成绑定的提醒任务,并把其 ID 回写到日程。
@@ -713,17 +743,15 @@ func (c *BackendOaScheduleController) Create() {
if rerr != nil {
// 回滚已创建的日程,避免产生孤立日程
_, _ = models.Orm.QueryTable(new(models.OaSchedule)).Filter("id", item.ID).Delete()
c.oasJsonErr(500, 500, rerr.Error())
return
return nil, 500, rerr.Error()
}
item.ReminderID = rid
if _, err := models.Orm.Update(item, "ReminderID"); err != nil {
c.oasJsonErr(500, 500, "保存失败")
return
return nil, 500, "保存失败"
}
}
c.oasOk(item)
return item, 200, ""
}
// Update POST /backend/oa/schedule/update/:id
@@ -745,16 +773,26 @@ func (c *BackendOaScheduleController) Update() {
return
}
item, code, msg := oaScheduleUpdateCore(claims, id, payload)
if msg != "" {
c.oasJsonErr(code, code, msg)
return
}
c.oasOk(item)
}
// oaScheduleUpdateCore 更新日程(含提醒重绑)的共享逻辑,Web 后台与移动端共用。
// 返回 (更新后日程, HTTP 状态码, 错误信息),错误信息为空串表示成功。
func oaScheduleUpdateCore(claims *jwtutil.Claims, id uint64, payload oaSchedulePayload) (*models.OaSchedule, int, string) {
var item models.OaSchedule
err = models.Orm.QueryTable(new(models.OaSchedule)).
err := models.Orm.QueryTable(new(models.OaSchedule)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&item)
if err != nil {
c.oasJsonErr(404, 404, "日程不存在")
return
return nil, 404, "日程不存在"
}
now := time.Now()
@@ -781,8 +819,7 @@ func (c *BackendOaScheduleController) Update() {
payload.AdvanceMinutes,
)
if rerr != nil {
c.oasJsonErr(500, 500, rerr.Error())
return
return nil, 500, rerr.Error()
}
if item.ReminderID > 0 {
deleteBoundReminder(item.ReminderID)
@@ -796,11 +833,10 @@ func (c *BackendOaScheduleController) Update() {
if _, err := models.Orm.Update(&item,
"Title", "Content", "ScheduleDate", "StartTime", "EndTime",
"AllDay", "Color", "Priority", "ReminderID", "UpdateTime"); err != nil {
c.oasJsonErr(500, 500, "保存失败")
return
return nil, 500, "保存失败"
}
c.oasOk(item)
return &item, 200, ""
}
// SetImages POST /backend/oa/schedule/images/:id
@@ -827,33 +863,42 @@ func (c *BackendOaScheduleController) SetImages() {
c.oasJsonErr(400, 400, "参数错误")
return
}
imagesJSON, ok := normalizeOaScheduleImages(payload.Images)
if !ok {
c.oasJsonErr(400, 400, "图片数量过多")
images, code, msg := oaScheduleSetImagesCore(claims, id, payload.Images)
if msg != "" {
c.oasJsonErr(code, code, msg)
return
}
c.oasOk(map[string]interface{}{"images": images})
}
// oaScheduleSetImagesCore 保存日程相关图片(只存文件引用)的共享逻辑,
// Web 后台与移动端共用。返回 (图片列表, HTTP 状态码, 错误信息),错误信息为空串表示成功。
func oaScheduleSetImagesCore(claims *jwtutil.Claims, id uint64, images []oaScheduleImage) ([]oaScheduleImage, int, string) {
imagesJSON, ok := normalizeOaScheduleImages(images)
if !ok {
return nil, 400, "图片数量过多"
}
var item models.OaSchedule
err = models.Orm.QueryTable(new(models.OaSchedule)).
err := models.Orm.QueryTable(new(models.OaSchedule)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&item)
if err != nil {
c.oasJsonErr(404, 404, "日程不存在")
return
return nil, 404, "日程不存在"
}
now := time.Now()
item.Images = imagesJSON
item.UpdateTime = &now
if _, err := models.Orm.Update(&item, "Images", "UpdateTime"); err != nil {
c.oasJsonErr(500, 500, "保存失败")
return
return nil, 500, "保存失败"
}
c.oasOk(map[string]interface{}{"images": parseOaScheduleImages(imagesJSON)})
return parseOaScheduleImages(imagesJSON), 200, ""
}
// UpdateContent POST /backend/oa/schedule/content/:id 仅更新备注内容。
@@ -918,17 +963,27 @@ func (c *BackendOaScheduleController) Delete() {
return
}
code, msg := oaScheduleDeleteCore(claims, id)
if msg != "" {
c.oasJsonErr(code, code, msg)
return
}
c.oasOk(nil)
}
// oaScheduleDeleteCore 软删除日程并清理绑定提醒的共享逻辑,Web 后台与移动端共用。
// 返回 (HTTP 状态码, 错误信息),错误信息为空串表示成功。
func oaScheduleDeleteCore(claims *jwtutil.Claims, id uint64) (int, string) {
now := time.Now()
var item models.OaSchedule
err = models.Orm.QueryTable(new(models.OaSchedule)).
err := models.Orm.QueryTable(new(models.OaSchedule)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&item)
if err != nil {
c.oasJsonErr(404, 404, "日程不存在或已删除")
return
return 404, "日程不存在或已删除"
}
// 先删除绑定的提醒任务,避免提醒工具里残留孤立任务
@@ -940,11 +995,10 @@ func (c *BackendOaScheduleController) Delete() {
item.DeleteTime = &now
item.UpdateTime = &now
if _, err := models.Orm.Update(&item, "IsDeleted", "DeleteTime", "UpdateTime"); err != nil {
c.oasJsonErr(500, 500, "删除失败")
return
return 500, "删除失败"
}
c.oasOk(nil)
return 200, ""
}
// FinishSchedule POST /backend/oa/schedule/finish/:id
@@ -964,16 +1018,26 @@ func (c *BackendOaScheduleController) FinishSchedule() {
return
}
newStatus, code, msg := oaScheduleToggleCore(claims, id)
if msg != "" {
c.oasJsonErr(code, code, msg)
return
}
c.oasOk(map[string]interface{}{"status": newStatus})
}
// oaScheduleToggleCore 切换日程完成状态的共享逻辑,Web 后台与移动端共用。
// 返回 (新状态, HTTP 状态码, 错误信息),错误信息为空串表示成功。
func oaScheduleToggleCore(claims *jwtutil.Claims, id uint64) (int8, int, string) {
var item models.OaSchedule
err = models.Orm.QueryTable(new(models.OaSchedule)).
err := models.Orm.QueryTable(new(models.OaSchedule)).
Filter("id", id).
Filter("is_deleted", 0).
Filter("tid", claims.TenantId).
Filter("user_id", claims.UserID).
One(&item)
if err != nil {
c.oasJsonErr(404, 404, "日程不存在")
return
return 0, 404, "日程不存在"
}
now := time.Now()
@@ -990,11 +1054,10 @@ func (c *BackendOaScheduleController) FinishSchedule() {
item.UpdateTime = &now
if _, err := models.Orm.Update(&item, "Status", "FinishTime", "UpdateTime"); err != nil {
c.oasJsonErr(500, 500, "操作失败")
return
return 0, 500, "操作失败"
}
c.oasOk(map[string]interface{}{"status": newStatus})
return newStatus, 200, ""
}
// nextOaDate 返回 dateStr 之后的第 days 天,格式仍为 "YYYY-MM-DD"。