Files
2026-10-22 17:55:50 +08:00

158 lines
4.5 KiB
Go

package controllers
import (
"encoding/json"
"io"
"strconv"
"strings"
"time"
"server/pkg/identity"
"server/pkg/jwtutil"
beego "github.com/beego/beego/v2/server/web"
)
// AppErpBaseController 移动端进销存接口公共底座。
//
// 与 /backend/erp/** 的关系(接口隔离规范):两端各有独立 handler 与路由,
// 但单据校验、明细装配、库存/往来账联动与报表统计的口径只能有一份实现,
// 全部在 erp_*_core.go 里;这里只负责「解移动端令牌 → 组装 erpActor → 调核心 → 输出响应」。
//
// 鉴权:/api/** 只被 uniapp 调用,放行 app 与 backend 两类令牌,平台令牌不允许打移动端接口。
// 数据隔离:tenant_id 一律取自 JWT,前端无法指定租户。
type AppErpBaseController struct {
beego.Controller
}
func (c *AppErpBaseController) erpClaims() (*jwtutil.Claims, error) {
return identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndApp, identity.EndBackend)
}
// actor 组装核心逻辑需要的调用者身份
func (c *AppErpBaseController) actor(cl *jwtutil.Claims) erpActor {
return erpNewActor(cl)
}
func (c *AppErpBaseController) erpOk(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
// erpListOK 分页出参形状与 Web 端保持一致:{list,total,page,pageSize,...extra}
func (c *AppErpBaseController) erpListOK(list interface{}, total int64, page, pageSize int, extra map[string]interface{}) {
data := map[string]interface{}{"list": list, "total": total, "page": page, "pageSize": pageSize}
for k, v := range extra {
data[k] = v
}
c.erpOk(data)
}
func (c *AppErpBaseController) erpFail(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
// erpDenied 鉴权失败统一出口(返回 true 表示已响应,调用方应 return)
func (c *AppErpBaseController) erpDenied(err error) bool {
if err != nil {
code := identity.Status(err)
c.erpFail(code, code, err.Error())
return true
}
return false
}
func (c *AppErpBaseController) erpPathID() uint64 {
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
return id
}
// erpPage 分页参数:默认 20,上限 100(与 Web 端口径一致)
func (c *AppErpBaseController) erpPage() (int, int) {
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
return page, pageSize
}
// erpBody 解析 JSON 请求体;allowEmpty 为 true 时允许空 body(自动核销等场景)
func (c *AppErpBaseController) erpBody(v interface{}, allowEmpty bool) bool {
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
c.erpFail(400, 400, "参数错误")
return false
}
if len(strings.TrimSpace(string(raw))) == 0 {
if allowEmpty {
return true
}
c.erpFail(400, 400, "请求体为空")
return false
}
if err := json.Unmarshal(raw, v); err != nil {
c.erpFail(400, 400, "参数错误: "+err.Error())
return false
}
return true
}
func (c *AppErpBaseController) erpQuery(name string) string {
return strings.TrimSpace(c.GetString(name))
}
func (c *AppErpBaseController) erpUint(name string) uint64 {
return erpUintParam(c.GetString(name))
}
func (c *AppErpBaseController) erpFlag(name string) bool {
return c.erpQuery(name) == "1"
}
// erpDateRange 报表/流水的日期区间:默认当月,date_to 含当天
func (c *AppErpBaseController) erpDateRange(defMonths int) (time.Time, time.Time) {
from, to := erpMonthRange(defMonths)
if d := c.erpQuery("date_from"); d != "" {
if t, e := time.ParseInLocation("2006-01-02", d, time.Local); e == nil {
from = t
}
}
if d := c.erpQuery("date_to"); d != "" {
if t, e := time.ParseInLocation("2006-01-02", d, time.Local); e == nil {
to = t.AddDate(0, 0, 1)
}
}
return from, to
}
// ---- 经营看板 / 字典 ----
// AppErpController 移动端进销存首页聚合与全局字典。
type AppErpController struct {
AppErpBaseController
}
// Dashboard GET /api/erp/dashboard 经营看板(与 Web 端同一份统计口径)
func (c *AppErpController) Dashboard() {
cl, err := c.erpClaims()
if c.erpDenied(err) {
return
}
c.erpOk(erpDashboardCore(c.actor(cl), c.erpQuery("period")))
}
// Meta GET /api/erp/meta 单据类型、状态、收付款方式等下拉字典
func (c *AppErpController) Meta() {
cl, err := c.erpClaims()
if c.erpDenied(err) {
return
}
c.erpOk(erpDocMetaDict(c.actor(cl)))
}