242 lines
8.1 KiB
Go
242 lines
8.1 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"
|
|
clientcommon "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/client/common"
|
|
base "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 := clientcommon.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: base.NewEntity(base.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 := clientcommon.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, base.StatusArchived).
|
|
Order("created_at desc").Find(&list).Error; err != nil {
|
|
infra.Response.Error(ctx, err)
|
|
return
|
|
}
|
|
infra.Response.Success(ctx, base.ResourceResponse(list))
|
|
}
|
|
|
|
// GetTicket 按公开 identity 返回当前工作人员被分派的单一工单。
|
|
func GetTicket(ctx *gin.Context) {
|
|
account, ok := clientcommon.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, base.StatusArchived).
|
|
First(&ticket).Error != nil {
|
|
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
|
|
return
|
|
}
|
|
infra.Response.Success(ctx, base.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 := clientcommon.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: base.NewEntity(base.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 := clientcommon.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})
|
|
}
|