Files
platforms/backend/api/internal/logic/platform/staff/staff.go
2026-08-15 19:19:15 +08:00

294 lines
11 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.
// 功能描述:提供平台工作人员资源管理与受控候选筛选。
// 版本v1.1.0
package staff
import (
"strconv"
"strings"
"time"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
)
type staffListFilters struct {
RoleCodes []string
Status *int
WorkStatus string
ValidCredentialOnly bool
}
type staffOrganizationFilters struct {
GasIdentity string
DeliveryIdentity string
GasUnassigned bool
DeliveryUnassigned bool
}
// parseStaffListFilters 将工作人员列表查询参数收敛为闭集条件,拒绝含糊或冲突值。
func parseStaffListFilters(roleCode, roleCodes, status, workStatus, validCredentialOnly string) (staffListFilters, bool) {
filters := staffListFilters{}
roleCode = strings.TrimSpace(roleCode)
roleCodes = strings.TrimSpace(roleCodes)
if roleCode != "" && roleCodes != "" {
return filters, false
}
if roleCode != "" {
filters.RoleCodes = []string{roleCode}
} else if roleCodes != "" {
seen := map[string]bool{}
for _, value := range strings.Split(roleCodes, ",") {
value = strings.TrimSpace(value)
if value == "" || seen[value] {
return staffListFilters{}, false
}
seen[value] = true
filters.RoleCodes = append(filters.RoleCodes, value)
}
}
for _, value := range filters.RoleCodes {
if !validStaffRole(value) {
return staffListFilters{}, false
}
}
status = strings.TrimSpace(status)
if status != "" {
value, err := strconv.Atoi(status)
if err != nil || !common.IsGenericRecordStatus(value) || value == common.StatusArchived {
return staffListFilters{}, false
}
filters.Status = &value
}
workStatus = strings.TrimSpace(workStatus)
if workStatus != "" && !validWorkStatus(workStatus) {
return staffListFilters{}, false
}
filters.WorkStatus = workStatus
validCredentialOnly = strings.TrimSpace(validCredentialOnly)
if validCredentialOnly != "" && validCredentialOnly != "1" {
return staffListFilters{}, false
}
filters.ValidCredentialOnly = validCredentialOnly == "1"
return filters, true
}
// parseStaffOrganizationFilters 解析服务关系下拉使用的精确组织范围,并拒绝相互冲突的条件。
func parseStaffOrganizationFilters(gasIdentity, deliveryIdentity, gasUnassigned, deliveryUnassigned string) (staffOrganizationFilters, bool) {
filters := staffOrganizationFilters{
GasIdentity: strings.TrimSpace(gasIdentity),
DeliveryIdentity: strings.TrimSpace(deliveryIdentity),
}
if strings.Contains(filters.GasIdentity, ",") || strings.Contains(filters.DeliveryIdentity, ",") {
return staffOrganizationFilters{}, false
}
parseUnassigned := func(value string) (bool, bool) {
value = strings.TrimSpace(value)
if value == "" {
return false, true
}
return value == "1", value == "1"
}
var ok bool
filters.GasUnassigned, ok = parseUnassigned(gasUnassigned)
if !ok {
return staffOrganizationFilters{}, false
}
filters.DeliveryUnassigned, ok = parseUnassigned(deliveryUnassigned)
if !ok || filters.GasIdentity != "" && filters.GasUnassigned || filters.DeliveryIdentity != "" && filters.DeliveryUnassigned {
return staffOrganizationFilters{}, false
}
return filters, true
}
// ListStaff 查询服务人员分页列表,并应用调用方显式声明的角色与在岗状态条件。
func ListStaff(ctx *gin.Context) {
filters, ok := parseStaffListFilters(
ctx.Query("role_code"),
ctx.Query("role_codes"),
ctx.Query("status"),
ctx.Query("work_status"),
ctx.Query("valid_credential_only"),
)
if !ok {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
organizationFilters, ok := parseStaffOrganizationFilters(
ctx.Query("gas_basic_identities"),
ctx.Query("delivery_basic_identities"),
ctx.Query("gas_basic_unassigned"),
ctx.Query("delivery_basic_unassigned"),
)
if !ok {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
page, size := common.PageSize(ctx)
var list []models.StaffAccount
var total int64
query := common.ActiveRecords(impl.DBService.Model(&models.StaffAccount{}))
if len(filters.RoleCodes) > 0 {
query = query.Where("role_code IN ?", filters.RoleCodes)
}
if filters.Status != nil {
query = query.Where("status = ?", *filters.Status)
}
if filters.WorkStatus != "" {
query = query.Where("work_status = ?", filters.WorkStatus)
}
if filters.ValidCredentialOnly {
now := time.Now()
query = query.Where(
"EXISTS (SELECT 1 FROM staff_credential WHERE staff_credential.staff_account_id = staff_account.id AND staff_credential.status = ? AND (staff_credential.expired_at IS NULL OR staff_credential.expired_at > ?))",
common.StatusEnable,
now,
)
}
if organizationFilters.GasIdentity != "" {
query = query.Where(
"gas_basic_id IN (SELECT id FROM gas_basic WHERE identity = ? AND status <> ?)",
organizationFilters.GasIdentity,
common.StatusArchived,
)
} else if organizationFilters.GasUnassigned {
query = query.Where("gas_basic_id = 0")
}
if organizationFilters.DeliveryIdentity != "" {
query = query.Where(
"delivery_basic_id IN (SELECT id FROM delivery_basic WHERE identity = ? AND status <> ?)",
organizationFilters.DeliveryIdentity,
common.StatusArchived,
)
} else if organizationFilters.DeliveryUnassigned {
query = query.Where("delivery_basic_id = 0")
}
query = common.ApplyKeywordFilter(ctx, query, &models.StaffAccount{})
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
response, err := common.PublicResourceResponse(list)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"total": total, "list": common.ProtectPreciseLocation(ctx, &models.StaffAccount{}, response)})
}
// GetStaff 查询一个服务人员档案。
func GetStaff(ctx *gin.Context) { common.GetByIdentity[models.StaffAccount](ctx) }
// GetStaffAvatar 返回已鉴权工作人员资料页使用的头像二进制内容。
func GetStaffAvatar(ctx *gin.Context) {
var account models.StaffAccount
if err := common.ActiveRecords(impl.DBService).Select("avatar").Where("identity = ?", ctx.Param("identity")).First(&account).Error; err != nil {
common.RespondRecordError(ctx, err)
return
}
upload.ServeAvatar(ctx, account.Avatar)
}
// CreateStaff 创建服务人员档案。
func CreateStaff(ctx *gin.Context) {
var request struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required"`
Name string `json:"name" binding:"required,max=64"`
Phone string `json:"phone" binding:"max=32"`
Avatar string `json:"avatar" binding:"max=512"`
RoleCode string `json:"role_code" binding:"max=64"`
GasBasicIdentity string `json:"gas_basic_identity"`
DeliveryBasicIdentity string `json:"delivery_basic_identity"`
WorkStatus string `json:"work_status" binding:"max=32"`
}
if err := ctx.ShouldBindJSON(&request); err != nil ||
!common.IsValidAccountPassword(request.Password) ||
!validStaffRole(request.RoleCode) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
hash, err := common.PasswordHash(request.Password)
if err != nil {
infra.Response.Error(ctx, err)
return
}
gasBasicID, err := common.ResolveIdentityID(&models.GasBasic{}, request.GasBasicIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
deliveryBasicID, err := common.ResolveIdentityID(&models.DeliveryBasic{}, request.DeliveryBasicIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.ValidateOrganizationIDs(gasBasicID, deliveryBasicID, 0) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
staff := models.StaffAccount{Entity: common.NewEntity(common.StatusDraft), Username: request.Username, PasswordHash: hash, Name: request.Name, Phone: request.Phone, Avatar: request.Avatar, RoleCode: request.RoleCode, GasBasicID: gasBasicID, DeliveryBasicID: deliveryBasicID, WorkStatus: request.WorkStatus}
if staff.WorkStatus == "" {
staff.WorkStatus = "off_duty"
}
if !validWorkStatus(staff.WorkStatus) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if err := common.CreateStaffRecord(&staff); err != nil {
infra.Response.Error(ctx, err)
return
}
common.RespondCreatedResource(ctx, staff)
}
// UpdateStaff 更新服务人员档案。
func UpdateStaff(ctx *gin.Context) {
var request struct {
Name string `json:"name" binding:"required,max=64"`
Phone string `json:"phone" binding:"max=32"`
Avatar *string `json:"avatar" binding:"omitempty,max=512"`
RoleCode string `json:"role_code" binding:"max=64"`
GasBasicIdentity string `json:"gas_basic_identity"`
DeliveryBasicIdentity string `json:"delivery_basic_identity"`
WorkStatus string `json:"work_status" binding:"max=32"`
}
if err := ctx.ShouldBindJSON(&request); err != nil || !validWorkStatus(request.WorkStatus) || !validStaffRole(request.RoleCode) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
gasBasicID, err := common.ResolveIdentityID(&models.GasBasic{}, request.GasBasicIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
deliveryBasicID, err := common.ResolveIdentityID(&models.DeliveryBasic{}, request.DeliveryBasicIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.ValidateOrganizationIDs(gasBasicID, deliveryBasicID, 0) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
values := gin.H{"name": request.Name, "phone": request.Phone, "role_code": request.RoleCode, "gas_basic_id": gasBasicID, "delivery_basic_id": deliveryBasicID, "work_status": request.WorkStatus}
// 未选择新头像时不提交 avatar避免普通资料编辑误清空现有头像。
if request.Avatar != nil {
values["avatar"] = *request.Avatar
}
common.UpdateAllowedByIdentityWithError(ctx, &models.StaffAccount{}, values, []string{"name", "phone", "avatar", "role_code", "gas_basic_id", "delivery_basic_id", "work_status"}, common.StaffWriteError)
}
func validWorkStatus(status string) bool { return status == "on_duty" || status == "off_duty" }
func validStaffRole(role string) bool {
return role == "installer" || role == "delivery" || role == "operations"
}