Files
platforms/backend/api/internal/logic/client/staff/work.go

241 lines
8.0 KiB
Go

package staff
import (
"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"
common "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// Attendance 上下班打卡;存在进行中任务时禁止下班。
func Attendance(ctx *gin.Context) {
account, ok := common.StaffAccount(ctx)
if !ok {
return
}
var request struct {
Action string `json:"action" binding:"required,oneof=clock_in clock_out"`
OccurredAt time.Time `json:"occurred_at" binding:"required"`
Longitude string `json:"longitude" binding:"required"`
Latitude string `json:"latitude" binding:"required"`
DeviceIdentity string `json:"device_identity" binding:"required"`
RequestNo string `json:"request_no" binding:"required"`
}
if ctx.ShouldBindJSON(&request) != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if request.Action == "clock_out" {
var count int64
impl.DBService.Model(&models.GasorderBasic{}).Where("staff_account_id = ? AND order_status IN ?", account.ID, []int{18, 11, 21, 34}).Count(&count)
if count == 0 {
impl.DBService.Model(&models.CsTicket{}).Where("staff_account_id = ? AND ticket_status IN ?", account.ID, []int{18, 11, 21, 34}).Count(&count)
}
if count > 0 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
}
record := models.StaffAttendance{
Entity: common.NewEntity(common.StatusEnable), StaffAccountID: account.ID, RoleCode: account.RoleCode,
Action: request.Action, OccurredAt: request.OccurredAt, Longitude: request.Longitude,
Latitude: request.Latitude, DeviceIdentity: request.DeviceIdentity, RequestNo: request.RequestNo,
}
workStatus := "on_duty"
if request.Action == "clock_out" {
workStatus = "off_duty"
}
err := impl.DBService.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&record).Error; err != nil {
return err
}
return tx.Model(&account).Update("work_status", workStatus).Error
})
if err != nil {
var existing models.StaffAttendance
if impl.DBService.Where("request_no = ? AND staff_account_id = ?", request.RequestNo, account.ID).First(&existing).Error != nil {
infra.Response.Error(ctx, err)
return
}
record = existing
}
infra.Response.Success(ctx, gin.H{"identity": record.Identity, "work_status": workStatus})
}
// ListTickets 仅返回分派给当前人员且与岗位匹配的工单。
func ListTickets(ctx *gin.Context) {
account, ok := common.StaffAccount(ctx)
if !ok {
return
}
categories := []string{"installation", "repair"}
if account.RoleCode == "operations" {
categories = []string{"inspection", "reinspection"}
}
if account.RoleCode == "delivery" {
infra.Response.Success(ctx, []models.CsTicket{})
return
}
var list []models.CsTicket
if err := impl.DBService.Where("staff_account_id = ? AND category IN ? AND status <> ?", account.ID, categories, common.StatusArchived).
Order("created_at desc").Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, common.ResourceResponse(list))
}
// GetTicket 按公开 identity 返回当前工作人员被分派的单一工单。
func GetTicket(ctx *gin.Context) {
account, ok := common.StaffAccount(ctx)
if !ok {
return
}
var ticket models.CsTicket
if impl.DBService.Where("identity = ? AND staff_account_id = ? AND status <> ?", ctx.Param("identity"), account.ID, common.StatusArchived).
First(&ticket).Error != nil {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
infra.Response.Success(ctx, common.ResourceResponse(ticket))
}
// StartTicket 将本人已分派工单置为处理中。
func StartTicket(ctx *gin.Context) {
updateTicketStatus(ctx, 18, 11, nil)
}
// ExceptionTicket 标记本人处理中工单异常。
func ExceptionTicket(ctx *gin.Context) {
updateTicketStatus(ctx, 11, 21, nil)
}
// RecoverTicket 恢复本人异常工单。
func RecoverTicket(ctx *gin.Context) {
updateTicketStatus(ctx, 21, 11, nil)
}
// SubmitTicketResult 追加现场证据并提交用户确认;不合格或高风险结果必须进入异常。
func SubmitTicketResult(ctx *gin.Context) {
account, ok := common.StaffAccount(ctx)
if !ok {
return
}
var request struct {
Result string `json:"result" binding:"required,max=2000"`
Conclusion string `json:"conclusion" binding:"required,oneof=qualified noncompliant high_risk"`
Evidences []struct {
EvidenceType string `json:"evidence_type" binding:"required"`
MediaType string `json:"media_type" binding:"required,oneof=image video signature"`
FileURI string `json:"file_uri" binding:"required"`
CapturedAt time.Time `json:"captured_at" binding:"required"`
Longitude string `json:"longitude" binding:"required"`
Latitude string `json:"latitude" binding:"required"`
RequestNo string `json:"request_no" binding:"required"`
} `json:"evidences" binding:"required,min=2"`
}
if ctx.ShouldBindJSON(&request) != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var ticket models.CsTicket
if impl.DBService.Where("identity = ? AND staff_account_id = ? AND ticket_status = ?", ctx.Param("identity"), account.ID, 11).First(&ticket).Error != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
images, videos, signatures := 0, 0, 0
stages := map[string]bool{}
for _, evidence := range request.Evidences {
if !strings.HasPrefix(evidence.FileURI, "/uploads/") {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
switch evidence.MediaType {
case "image":
images++
case "video":
videos++
case "signature":
signatures++
}
stages[evidence.EvidenceType] = true
}
valid := images <= 6 && videos <= 3 && signatures >= 1
if ticket.Category == "installation" || ticket.Category == "repair" {
valid = valid && stages["before"] && stages["during"] && stages["after"]
} else {
valid = valid && images >= 1
}
if !valid {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
nextStatus := 34
if request.Conclusion != "qualified" {
nextStatus = 21
}
err := impl.DBService.Transaction(func(tx *gorm.DB) error {
now := time.Now()
for _, item := range request.Evidences {
record := models.CsTicketEvidence{
Entity: common.NewEntity(common.StatusEnable), CsTicketID: ticket.ID, EvidenceType: item.EvidenceType,
MediaType: item.MediaType, FileURI: item.FileURI, CapturedAt: item.CapturedAt, ReceivedAt: now,
Longitude: item.Longitude, Latitude: item.Latitude, Source: "app",
IntegrityStatus: "unverified", OperatorIdentity: account.Identity, RequestNo: item.RequestNo,
}
if err := tx.Create(&record).Error; err != nil {
return err
}
}
result := tx.Model(&models.CsTicket{}).Where("id = ? AND ticket_status = ?", ticket.ID, 11).
Updates(map[string]any{"ticket_status": nextStatus, "result": request.Result, "operator_identity": account.Identity})
if result.Error != nil {
return result.Error
}
if result.RowsAffected != 1 {
return gorm.ErrInvalidData
}
return nil
})
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"ticket_status": nextStatus})
}
func updateTicketStatus(ctx *gin.Context, from, to int, extra map[string]any) {
account, ok := common.StaffAccount(ctx)
if !ok {
return
}
values := map[string]any{"ticket_status": to, "operator_identity": account.Identity}
if extra != nil {
for key, value := range extra {
values[key] = value
}
}
if to == 11 {
now := time.Now()
values["started_at"] = &now
}
result := impl.DBService.Model(&models.CsTicket{}).
Where("identity = ? AND staff_account_id = ? AND ticket_status = ?", ctx.Param("identity"), account.ID, from).
Updates(values)
if result.Error != nil {
infra.Response.Error(ctx, result.Error)
return
}
if result.RowsAffected != 1 {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
infra.Response.Success(ctx, gin.H{"ticket_status": to})
}