| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- package repository
- import (
- "encoding/json"
- "time"
- "wails-app/internal/dao"
- "wails-app/internal/global"
- "gorm.io/gorm"
- )
- // appendEventLog 向事件日志 JSON 数组追加一条事件(沿用数字票 event_log 约定)。
- func appendEventLog(log, eventJSON string) string {
- var events []map[string]interface{}
- json.Unmarshal([]byte(log), &events)
- var event map[string]interface{}
- json.Unmarshal([]byte(eventJSON), &event)
- events = append(events, event)
- data, _ := json.Marshal(events)
- return string(data)
- }
- type IncidentRepository struct{}
- // Create 创建异常记录。
- func (r *IncidentRepository) Create(record *dao.IncidentRecord) error {
- return global.GVA_DB.Create(record).Error
- }
- // GetByID 查询异常详情(含软删除过滤)。
- func (r *IncidentRepository) GetByID(id uint) (*dao.IncidentRecord, error) {
- var record dao.IncidentRecord
- err := global.GVA_DB.First(&record, id).Error
- if err != nil {
- return nil, err
- }
- return &record, nil
- }
- // UpdateStateIfCurrent CAS 状态更新:仅当记录仍处于调用方观察到的状态时生效。
- func (r *IncidentRepository) UpdateStateIfCurrent(id uint, fromStatus, toStatus string, updates map[string]interface{}) (bool, error) {
- if updates == nil {
- updates = make(map[string]interface{})
- }
- updates["status"] = toStatus
- result := global.GVA_DB.Model(&dao.IncidentRecord{}).
- Where("id = ? AND status = ?", id, fromStatus).
- Updates(updates)
- return result.RowsAffected == 1, result.Error
- }
- // UpdateIncidentID 建立异常与指令流水的双向关联(开闸失败场景)。
- func (r *IncidentRepository) UpdateIncidentID(logID, incidentID uint) error {
- if logID == 0 || incidentID == 0 {
- return nil
- }
- return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
- if err := tx.Model(&dao.DeviceCommandLog{}).
- Where("id = ?", logID).Update("incident_id", incidentID).Error; err != nil {
- return err
- }
- return tx.Model(&dao.IncidentRecord{}).
- Where("id = ?", incidentID).Update("gate_command_id", logID).Error
- })
- }
- // HasOpenDeviceOffline 同设备是否已有未关闭(待处理/处理中)的离线异常。
- func (r *IncidentRepository) HasOpenDeviceOffline(deviceCode string) (bool, error) {
- if deviceCode == "" {
- return false, nil
- }
- var count int64
- err := global.GVA_DB.Model(&dao.IncidentRecord{}).
- Where("device_code = ? AND category = ? AND status IN (?, ?)",
- deviceCode, "device_offline", "pending", "processing").
- Count(&count).Error
- return count > 0, err
- }
- // ExistsRecent 通行类异常去重:同设备 + 同分类 + 同车辆标识,在时间窗口内不重复生成。
- // 无车辆标识(车牌和 RFID 均为空)时不去重。
- func (r *IncidentRepository) ExistsRecent(deviceCode, category, plateNumber, rfidTag string, minutes int) (bool, error) {
- if plateNumber == "" && rfidTag == "" {
- return false, nil
- }
- db := global.GVA_DB.Model(&dao.IncidentRecord{}).
- Where("device_code = ? AND category = ? AND status IN (?, ?) AND created_at >= ?",
- deviceCode, category, "pending", "processing", time.Now().Add(-time.Duration(minutes)*time.Minute))
- if plateNumber != "" {
- db = db.Where("plate_number = ?", plateNumber)
- } else {
- db = db.Where("rfid_tag = ?", rfidTag)
- }
- var count int64
- err := db.Count(&count).Error
- return count > 0, err
- }
- // CreateCommandLog 写入指令流水。
- func (r *IncidentRepository) CreateCommandLog(log *dao.DeviceCommandLog) error {
- return global.GVA_DB.Create(log).Error
- }
- // ResolveDeviceOfflineByDevice 设备恢复在线时批量关闭该设备未处理的离线异常。
- func (r *IncidentRepository) ResolveDeviceOfflineByDevice(deviceCode, remark string) error {
- if deviceCode == "" {
- return nil
- }
- var open []dao.IncidentRecord
- if err := global.GVA_DB.Where("device_code = ? AND category = ? AND status IN (?, ?)",
- deviceCode, "device_offline", "pending", "processing").Find(&open).Error; err != nil {
- return err
- }
- now := time.Now()
- for i := range open {
- event, _ := json.Marshal(map[string]interface{}{
- "event": "resolved", "handle_type": "device_repaired", "remark": remark, "at": now.Format(time.RFC3339),
- })
- if err := global.GVA_DB.Model(&open[i]).Updates(map[string]interface{}{
- "status": "resolved", "handle_type": "device_repaired",
- "handled_at": now, "handle_remark": remark, "handler_id": 0,
- "event_log": appendEventLog(open[i].EventLog, string(event)),
- }).Error; err != nil {
- return err
- }
- }
- return nil
- }
- type IncidentListQuery struct {
- Status string `form:"status"`
- Category string `form:"category"`
- Level string `form:"level"`
- Source string `form:"source"`
- PlateNumber string `form:"plate_number"`
- TicketNo string `form:"ticket_no"`
- ParkingLotID uint `form:"parking_lot_id"`
- DeviceCode string `form:"device_code"`
- StartDate string `form:"start_date"`
- EndDate string `form:"end_date"`
- Page int `form:"page"`
- PageSize int `form:"page_size"`
- }
- type IncidentListItem struct {
- dao.IncidentRecord
- OperatorName string `json:"operator_name"`
- HandlerName string `json:"handler_name"`
- LotName string `json:"lot_name"`
- }
- // List 分页筛选异常记录(上报人/处理人昵称、停车场名称联查)。
- func (r *IncidentRepository) List(q IncidentListQuery) ([]IncidentListItem, int64, error) {
- db := global.GVA_DB.Table("incident_record ir").
- Select("ir.*, op.nick_name as operator_name, hd.nick_name as handler_name, COALESCE(ir.parking_lot_name, pl.lot_name) as lot_name").
- Joins("LEFT JOIN sys_users op ON op.id = ir.operator_id").
- Joins("LEFT JOIN sys_users hd ON hd.id = ir.handler_id").
- Joins("LEFT JOIN parking_lot pl ON pl.id = ir.parking_lot_id")
- if q.Status != "" {
- db = db.Where("ir.status = ?", q.Status)
- }
- if q.Category != "" {
- db = db.Where("ir.category = ?", q.Category)
- }
- if q.Level != "" {
- db = db.Where("ir.level = ?", q.Level)
- }
- if q.Source != "" {
- db = db.Where("ir.source = ?", q.Source)
- }
- if q.PlateNumber != "" {
- db = db.Where("ir.plate_number LIKE ?", "%"+q.PlateNumber+"%")
- }
- if q.TicketNo != "" {
- db = db.Where("ir.ticket_no LIKE ?", "%"+q.TicketNo+"%")
- }
- if q.ParkingLotID > 0 {
- db = db.Where("ir.parking_lot_id = ?", q.ParkingLotID)
- }
- if q.DeviceCode != "" {
- db = db.Where("ir.device_code LIKE ?", "%"+q.DeviceCode+"%")
- }
- if q.StartDate != "" {
- db = db.Where("ir.created_at >= ?", q.StartDate+" 00:00:00")
- }
- if q.EndDate != "" {
- db = db.Where("ir.created_at < ?", q.EndDate+" 23:59:59")
- }
- var total int64
- db.Count(&total)
- if q.Page <= 0 {
- q.Page = 1
- }
- if q.PageSize <= 0 {
- q.PageSize = 10
- }
- var list []IncidentListItem
- err := db.Order("ir.id DESC").Offset((q.Page - 1) * q.PageSize).Limit(q.PageSize).Scan(&list).Error
- return list, total, err
- }
- type IncidentStats struct {
- Pending int64 `json:"pending"`
- Processing int64 `json:"processing"`
- Resolved int64 `json:"resolved"`
- Closed int64 `json:"closed"`
- TodayCreated int64 `json:"today_created"`
- OpenFundRelated int64 `json:"open_fund_related"`
- TodayForceFreeAmount float64 `json:"today_force_free_amount"`
- }
- // Stats 异常统计:状态分布、今日新增、未关闭资金类数量、今日强制免费金额。
- func (r *IncidentRepository) Stats() (IncidentStats, error) {
- var stats IncidentStats
- // 每次 Count 使用全新的查询构建器,避免 GORM Where 条件在复用实例上累积
- base := func() *gorm.DB {
- return global.GVA_DB.Model(&dao.IncidentRecord{})
- }
- if err := base().Where("status = ?", "pending").Count(&stats.Pending).Error; err != nil {
- return stats, err
- }
- if err := base().Where("status = ?", "processing").Count(&stats.Processing).Error; err != nil {
- return stats, err
- }
- if err := base().Where("status = ?", "resolved").Count(&stats.Resolved).Error; err != nil {
- return stats, err
- }
- if err := base().Where("status = ?", "closed").Count(&stats.Closed).Error; err != nil {
- return stats, err
- }
- if err := base().Where("created_at >= ?", startOfDay()).Count(&stats.TodayCreated).Error; err != nil {
- return stats, err
- }
- // 资金相关分类(支付失败/不确定、开闸失败、无入场记录出场)中未关闭的数量
- if err := base().Where("status IN (?, ?) AND category IN (?, ?, ?, ?)",
- "pending", "processing",
- "payment_failed", "payment_uncertain", "gate_failed", "no_entry_exit").
- Count(&stats.OpenFundRelated).Error; err != nil {
- return stats, err
- }
- // 今日强制免费金额合计
- if err := global.GVA_DB.Model(&dao.IncidentRecord{}).
- Where("handle_type = ? AND handled_at >= ?", "force_free", startOfDay()).
- Select("COALESCE(SUM(force_free_amount), 0)").Scan(&stats.TodayForceFreeAmount).Error; err != nil {
- return stats, err
- }
- return stats, nil
- }
- func startOfDay() time.Time {
- now := time.Now()
- return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- }
|