✨ 请假功能

This commit is contained in:
sugarBecomer
2026-03-25 13:47:11 +08:00
parent adbf885d95
commit b890929d0b
7 changed files with 255 additions and 3 deletions
+49
View File
@@ -35,3 +35,52 @@ type CpLeaveUploadReq struct {
File *ghttp.UploadFile `json:"file"`
CpUserId int64 `json:"cpUserId"`
}
// 请假 - 检查是否允许请假
type CpLeaveCheckReq struct {
g.Meta `path:"/leave/check/{cpUserId}" method:"get" tags:"CpLeave" summary:"检查是否允许请假"`
CpUserId int64 `json:"cpUserId" v:"required#今日校园账号ID不能为空"`
}
// 请假 - 获取请假类型
type CpLeaveTypesReq struct {
g.Meta `path:"/leave/types/{cpUserId}" method:"get" tags:"CpLeave" summary:"获取请假类型列表"`
CpUserId int64 `json:"cpUserId" v:"required#今日校园账号ID不能为空"`
}
// 请假 - 查询请假类型配置(是否需要审批等)
type CpLeaveTypeCfgReq struct {
g.Meta `path:"/leave/typeCfg" method:"post" tags:"CpLeave" summary:"查询请假类型配置"`
CpUserId int64 `json:"cpUserId" v:"required#今日校园账号ID不能为空"`
Code int `json:"code" v:"required#请假类型code不能为空"`
}
// 请假 - 获取审批人列表
type CpLeaveApproveFlowReq struct {
g.Meta `path:"/leave/approveFlow" method:"post" tags:"CpLeave" summary:"获取审批人列表"`
CpUserId int64 `json:"cpUserId" v:"required#今日校园账号ID不能为空"`
LeaveType int `json:"leaveType" v:"required#请假类型不能为空"`
StartTime string `json:"startTime" v:"required#开始时间不能为空"`
EndTime string `json:"endTime" v:"required#结束时间不能为空"`
}
// 请假 - 提交请假申请
type CpLeaveApplyReq struct {
g.Meta `path:"/leave/apply" method:"post" tags:"CpLeave" summary:"提交请假申请"`
CpUserId int64 `json:"cpUserId" v:"required#今日校园账号ID不能为空"`
LeaveType int `json:"leaveType" v:"required#请假类型不能为空"`
StartTime string `json:"startTime" v:"required#开始时间不能为空"`
EndTime string `json:"endTime" v:"required#结束时间不能为空"`
UrgencyMobile string `json:"urgencyMobile" v:"required#紧急联系人手机号不能为空"`
LeaveReason string `json:"leaveReason" v:"required#请假原因不能为空"`
OutStatus bool `json:"outStatus"`
Address string `json:"address" v:"required#目的地不能为空"`
DetailAddress string `json:"detailAddress" v:"required#详细地址不能为空"`
Longitude string `json:"longitude" v:"required#经度不能为空"`
Latitude string `json:"latitude" v:"required#纬度不能为空"`
LocationAddr string `json:"locationAddr" v:"required#定位地址不能为空"`
ImageUrl string `json:"imageUrl"`
ApproverWid string `json:"approverWid"`
ApproverId string `json:"approverId"`
ApproverName string `json:"approverName"`
}
@@ -31,6 +31,31 @@ func (c CpLeaveCtrlV1) Upload(ctx context.Context, req *v1.CpLeaveUploadReq) (re
return service.Global.CpLeave.Upload(ctx, req), nil
}
func (c CpLeaveCtrlV1) CheckLeave(ctx context.Context, req *v1.CpLeaveCheckReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpLeave.CheckLeave(ctx, req), nil
}
func (c CpLeaveCtrlV1) GetLeaveTypes(ctx context.Context, req *v1.CpLeaveTypesReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpLeave.GetLeaveTypes(ctx, req), nil
}
func (c CpLeaveCtrlV1) GetLeaveTypeCfg(ctx context.Context, req *v1.CpLeaveTypeCfgReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpLeave.GetLeaveTypeCfg(ctx, req), nil
}
func (c CpLeaveCtrlV1) GetApproveFlow(ctx context.Context, req *v1.CpLeaveApproveFlowReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpLeave.GetApproveFlow(ctx, req), nil
}
func (c CpLeaveCtrlV1) Apply(ctx context.Context, req *v1.CpLeaveApplyReq) (res *utils.Response[any], err error) {
defer utils.Pass(&err)
return service.Global.CpLeave.Apply(ctx, req), nil
}
func NewCpLeaveCtrlV1() ICpLeaveCtrlV1 {
return &CpLeaveCtrlV1{}
}
@@ -12,4 +12,9 @@ type ICpLeaveCtrlV1 interface {
List(ctx context.Context, req *v1.CpLeaveListReq) (*utils.Response[[]*ent.CpLeave], error)
Report(ctx context.Context, req *v1.CpLeaveReportReq) (*utils.Response[any], error)
Upload(ctx context.Context, req *v1.CpLeaveUploadReq) (*utils.Response[string], error)
CheckLeave(ctx context.Context, req *v1.CpLeaveCheckReq) (*utils.Response[any], error)
GetLeaveTypes(ctx context.Context, req *v1.CpLeaveTypesReq) (*utils.Response[any], error)
GetLeaveTypeCfg(ctx context.Context, req *v1.CpLeaveTypeCfgReq) (*utils.Response[any], error)
GetApproveFlow(ctx context.Context, req *v1.CpLeaveApproveFlowReq) (*utils.Response[any], error)
Apply(ctx context.Context, req *v1.CpLeaveApplyReq) (*utils.Response[any], error)
}
+2 -1
View File
@@ -126,6 +126,7 @@ type Type string
// Type values.
const (
TypeReport Type = "report"
TypeApply Type = "apply"
)
func (_type Type) String() string {
@@ -135,7 +136,7 @@ func (_type Type) String() string {
// TypeValidator is a validator for the "type" field enum values. It is called by the builders before save.
func TypeValidator(_type Type) error {
switch _type {
case TypeReport:
case TypeReport, TypeApply:
return nil
default:
return fmt.Errorf("cpleave: invalid enum value for type field: %q", _type)
+1 -1
View File
@@ -77,7 +77,7 @@ var (
{Name: "leave_id", Type: field.TypeString},
{Name: "title", Type: field.TypeString},
{Name: "status", Type: field.TypeEnum, Enums: []string{"done", "fail"}},
{Name: "type", Type: field.TypeEnum, Enums: []string{"report"}},
{Name: "type", Type: field.TypeEnum, Enums: []string{"report", "apply"}},
{Name: "desc", Type: field.TypeString, Nullable: true, Size: 2147483647},
{Name: "cp_user_id", Type: field.TypeInt64},
{Name: "user_id", Type: field.TypeInt64},
+1 -1
View File
@@ -21,7 +21,7 @@ func (CpLeave) Fields() []ent.Field {
field.String("leave_id").NotEmpty(),
field.String("title").NotEmpty(),
field.Enum("status").Values("done", "fail"),
field.Enum("type").Values("report"),
field.Enum("type").Values("report", "apply"),
field.Text("desc").Optional(),
}
}
+172
View File
@@ -208,3 +208,175 @@ func (v CpLeaveServiceV1) Upload(ctx context.Context, req *v1.CpLeaveUploadReq)
func NewCpLeaveServiceV1() *CpLeaveServiceV1 {
return &CpLeaveServiceV1{}
}
// CheckLeave 检查是否允许请假
func (v CpLeaveServiceV1) CheckLeave(ctx context.Context, req *v1.CpLeaveCheckReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
cpUser := db2.Global.CpUser.Query().
Where(cpuser.ID(req.CpUserId), cpuser.HasSysuserWith(sysuser.ID(user.ID))).
WithSysuser().WithSchool().OnlyX(ctx)
if cpUser.Edges.Sysuser == nil {
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
}
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
LoginType: cpUser.LoginType, Username: cpUser.Username,
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
}, &schoolDetail)
if refreshResult.Cookie != cpUser.Cookie {
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
}
reportApp := leave.NewCpdailyReportApp(refreshResult.Client, schoolDetail.WecloudUrl)
err := reportApp.CheckOnceLeave()
if err != nil {
utils.OkOrPanic(err)
}
return utils.SuccessWithMsg("允许请假")
}
// GetLeaveTypes 获取请假类型列表
func (v CpLeaveServiceV1) GetLeaveTypes(ctx context.Context, req *v1.CpLeaveTypesReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
cpUser := db2.Global.CpUser.Query().
Where(cpuser.ID(req.CpUserId), cpuser.HasSysuserWith(sysuser.ID(user.ID))).
WithSysuser().WithSchool().OnlyX(ctx)
if cpUser.Edges.Sysuser == nil {
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
}
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
LoginType: cpUser.LoginType, Username: cpUser.Username,
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
}, &schoolDetail)
if refreshResult.Cookie != cpUser.Cookie {
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
}
reportApp := leave.NewCpdailyReportApp(refreshResult.Client, schoolDetail.WecloudUrl)
leaveTypes := reportApp.QueryLeaveTypes()
return utils.SuccessWithData[any](leaveTypes.Value())
}
// GetLeaveTypeCfg 查询请假类型配置
func (v CpLeaveServiceV1) GetLeaveTypeCfg(ctx context.Context, req *v1.CpLeaveTypeCfgReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
cpUser := db2.Global.CpUser.Query().
Where(cpuser.ID(req.CpUserId), cpuser.HasSysuserWith(sysuser.ID(user.ID))).
WithSysuser().WithSchool().OnlyX(ctx)
if cpUser.Edges.Sysuser == nil {
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
}
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
LoginType: cpUser.LoginType, Username: cpUser.Username,
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
}, &schoolDetail)
if refreshResult.Cookie != cpUser.Cookie {
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
}
reportApp := leave.NewCpdailyReportApp(refreshResult.Client, schoolDetail.WecloudUrl)
cfg := reportApp.QueryLeaveTypeCfg(req.Code)
return utils.SuccessWithData[any](cfg.Value())
}
// GetApproveFlow 获取审批人列表
func (v CpLeaveServiceV1) GetApproveFlow(ctx context.Context, req *v1.CpLeaveApproveFlowReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
cpUser := db2.Global.CpUser.Query().
Where(cpuser.ID(req.CpUserId), cpuser.HasSysuserWith(sysuser.ID(user.ID))).
WithSysuser().WithSchool().OnlyX(ctx)
if cpUser.Edges.Sysuser == nil {
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
}
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
LoginType: cpUser.LoginType, Username: cpUser.Username,
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
}, &schoolDetail)
if refreshResult.Cookie != cpUser.Cookie {
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
}
reportApp := leave.NewCpdailyReportApp(refreshResult.Client, schoolDetail.WecloudUrl)
flow := reportApp.ObtainApproveFlow(req.LeaveType, req.StartTime, req.EndTime)
return utils.SuccessWithData[any](flow.Value())
}
// Apply 提交请假申请
func (v CpLeaveServiceV1) Apply(ctx context.Context, req *v1.CpLeaveApplyReq) *utils.Response[any] {
user := utils.GetUserInfoFromCtx(ctx)
cpUser := db2.Global.CpUser.Query().
Where(cpuser.ID(req.CpUserId), cpuser.HasSysuserWith(sysuser.ID(user.ID))).
WithSysuser().WithSchool().OnlyX(ctx)
if cpUser.Edges.Sysuser == nil {
utils.OkOrPanic(errors2.NewBusinessError(400, "非法的今日校园账号"))
}
// 计算扣款金额
basePoint, err := decimal.NewFromString(db2.Global.SysConfig.Query().Where(sysconfig.KeyEQ(cst2.ConfigRedisCpdailyLeave)).OnlyX(ctx).Value)
utils.OkOrPanic(err)
if user.Edges.Agent != nil {
basePoint = basePoint.Mul(user.Edges.Agent.Rate)
}
// 先扣款
balance.BalanceService.ChangeUserBalance(ctx, user.ID, basePoint.Neg(), "今日校园请假")
// 创建请假记录
leaveRecord := db2.Global.CpLeave.Create().
SetCpUserID(req.CpUserId).
SetUserID(user.ID).
SetLeaveID(fmt.Sprintf("apply_%d", req.CpUserId)).
SetTitle(fmt.Sprintf("请假申请: %s ~ %s", req.StartTime, req.EndTime)).
SetStatus(cpleave.StatusFail).
SetType(cpleave.TypeApply).
SetDesc("正在处理中...").
SaveX(ctx)
defer func() {
if r := recover(); r != nil {
balance.BalanceService.ChangeUserBalance(ctx, user.ID, basePoint, "请假失败退款")
desc := fmt.Sprintf("请假失败: %v", r)
db2.Global.CpLeave.UpdateOne(leaveRecord).
SetStatus(cpleave.StatusFail).
SetDesc(desc).
ExecX(ctx)
utils.OkOrPanic(errors2.NewBusinessError(500, desc))
}
}()
schoolDetail := school.GetSchoolDetail(ctx, cpUser.SchoolID)
refreshResult := login.EnsureCookieValid(ctx, &login.RefreshCookieParams{
ID: cpUser.ID, Cookie: cpUser.Cookie, Tgc: cpUser.Tgc,
LoginType: cpUser.LoginType, Username: cpUser.Username,
Password: cpUser.Password, SchoolID: cpUser.SchoolID,
}, &schoolDetail)
if refreshResult.Cookie != cpUser.Cookie {
db2.Global.CpUser.UpdateOneID(cpUser.ID).SetCookie(refreshResult.Cookie).SetTgc(refreshResult.Tgc).ExecX(ctx)
}
reportApp := leave.NewCpdailyReportApp(refreshResult.Client, schoolDetail.WecloudUrl)
reportApp.SubmitLeaveApply(ctx, req, cpUser.Username, cpUser.MobileCode)
db2.Global.CpLeave.UpdateOne(leaveRecord).
SetStatus(cpleave.StatusDone).
SetDesc("已提交").
ExecX(ctx)
return utils.SuccessWithMsg("请假申请提交成功")
}