diff --git a/api/controller/pharmacy.go b/api/controller/pharmacy.go index 3d725ce..700a765 100644 --- a/api/controller/pharmacy.go +++ b/api/controller/pharmacy.go @@ -282,3 +282,54 @@ func (r *Pharmacy) GetPharmacyDoctorList(c *gin.Context) { responses.OkWithData(list, c) } + +// GetAffectedDoctors 获取药房下线受影响医生列表(预检接口) +func (r *Pharmacy) GetAffectedDoctors(c *gin.Context) { + id := c.Param("pharmacy_id") + if id == "" { + id = c.Param("id") + } + if id == "" { + responses.FailWithMessage("缺少药房ID参数", c) + return + } + + pharmacyId, err := strconv.ParseInt(id, 10, 64) + if err != nil { + responses.Fail(c) + return + } + + pharmacyService := service.PharmacyService{} + result, err := pharmacyService.GetAffectedDoctors(pharmacyId) + if err != nil { + responses.FailWithMessage(err.Error(), c) + return + } + + responses.OkWithData(result, c) +} + +// BatchTransferAndAction 批量迁移医生默认药房并执行药房操作(禁用/删除) +func (r *Pharmacy) BatchTransferAndAction(c *gin.Context) { + pharmacyRequest := requests.PharmacyRequest{} + req := pharmacyRequest.BatchTransferAndAction + if err := c.ShouldBindJSON(&req); err != nil { + responses.FailWithMessage(err.Error(), c) + return + } + + if err := global.Validate.Struct(req); err != nil { + responses.FailWithMessage(utils.Translate(err), c) + return + } + + pharmacyService := service.PharmacyService{} + _, err := pharmacyService.BatchTransferAndAction(req) + if err != nil { + responses.FailWithMessage(err.Error(), c) + return + } + + responses.Ok(c) +} diff --git a/api/dao/doctorPharmacy.go b/api/dao/doctorPharmacy.go index 46a62ad..2b639c3 100644 --- a/api/dao/doctorPharmacy.go +++ b/api/dao/doctorPharmacy.go @@ -192,6 +192,44 @@ func (r *DoctorPharmacyDao) GetDoctorPharmacyCountsByPharmacyId(pharmacyId int64 return totalCount, defaultCount, nil } +// GetOtherPharmaciesByDoctorIds 批量获取一组医生绑定的其他有效药房 +func (r *DoctorPharmacyDao) GetOtherPharmaciesByDoctorIds(doctorIds []int64, excludePharmacyId int64) (m []*model.DoctorPharmacy, err error) { + if len(doctorIds) == 0 { + return nil, nil + } + + err = global.Db.Preload("Pharmacy"). + Where("doctor_id IN (?) AND status = 1 AND pharmacy_id != ?", doctorIds, excludePharmacyId). + Order("is_default desc, created_at desc"). + Find(&m).Error + if err != nil { + return nil, err + } + + // GORM 在外键为 0 时会自动跳过 Preload,因此针对未能预加载的记录进行兜底补全 + var zeroPharmacy *model.Pharmacy + for _, dp := range m { + if dp.Pharmacy == nil { + if dp.PharmacyId == 0 { + if zeroPharmacy == nil { + var ph model.Pharmacy + if err := global.Db.Where("pharmacy_id = 0").First(&ph).Error; err == nil { + zeroPharmacy = &ph + } + } + dp.Pharmacy = zeroPharmacy + } else { + var ph model.Pharmacy + if err := global.Db.Where("pharmacy_id = ?", dp.PharmacyId).First(&ph).Error; err == nil { + dp.Pharmacy = &ph + } + } + } + } + + return m, nil +} + // GetPharmacyDoctorExportListSearch 获取药房关联医生列表-导出 func (r *DoctorPharmacyDao) GetPharmacyDoctorExportListSearch(req requests.PharmacyDoctorExportList) (m []*model.DoctorPharmacy, err error) { query := global.Db.Model(&model.DoctorPharmacy{}). diff --git a/api/dto/PharmacyDoctor.go b/api/dto/PharmacyDoctor.go index 9f4f59e..f6d5201 100644 --- a/api/dto/PharmacyDoctor.go +++ b/api/dto/PharmacyDoctor.go @@ -76,3 +76,43 @@ func GetPharmacyDoctorListDto(list []*model.DoctorPharmacy) []PharmacyDoctorDto } return res } + +// GetDoctorTitleName 获取医生职称名称 +func GetDoctorTitleName(title int) string { + return doctorTitleMap[title] +} + +// BoundPharmacySimpleDto 医生绑定的药房简要信息 +type BoundPharmacySimpleDto struct { + PharmacyId string `json:"pharmacy_id"` + PharmacyName string `json:"pharmacy_name"` + PharmacyCode string `json:"pharmacy_code"` + IsDefault int `json:"is_default"` +} + +// AffectedDoctorItemDto 受影响的医生明细 +type AffectedDoctorItemDto struct { + DoctorId string `json:"doctor_id"` + DoctorPharmacyId string `json:"doctor_pharmacy_id"` + UserId string `json:"user_id"` + UserName string `json:"user_name"` + Mobile string `json:"mobile"` + DoctorTitleName string `json:"doctor_title_name"` + HospitalId string `json:"hospital_id"` + HospitalName string `json:"hospital_name"` + DepartmentCustomId string `json:"department_custom_id"` + DepartmentCustomName string `json:"department_custom_name"` + IsDefault int `json:"is_default"` // 在当前待下线药房是否为默认(1:是 0:否) + OtherBoundPharmacies []BoundPharmacySimpleDto `json:"other_bound_pharmacies"` // 该医生名下绑定的其他可用药房 +} + +// PharmacyAffectedDoctorsDto 下线预检受影响医生列表响应 +type PharmacyAffectedDoctorsDto struct { + PharmacyId string `json:"pharmacy_id"` + PharmacyName string `json:"pharmacy_name"` + PharmacyCode string `json:"pharmacy_code"` + Status int `json:"status"` + DefaultDoctorCount int64 `json:"default_doctor_count"` + TotalDoctorCount int64 `json:"total_doctor_count"` + DoctorList []AffectedDoctorItemDto `json:"doctor_list"` +} diff --git a/api/requests/pharmacy.go b/api/requests/pharmacy.go index 0713e03..ecacda0 100644 --- a/api/requests/pharmacy.go +++ b/api/requests/pharmacy.go @@ -9,6 +9,7 @@ type PharmacyRequest struct { GetPharmacyDoctorPage // 药房绑定的医生列表-分页 GetPharmacyDoctorList // 药房绑定的医生列表 PharmacyDoctorExportList // 药房关联医生-导出 + BatchTransferAndAction // 批量迁移医生默认药房并执行药房操作 } // PharmacyDoctorExportList 药房关联医生-导出 @@ -89,3 +90,17 @@ type PutPharmacy struct { type PutPharmacyStatus struct { Status *int `json:"status" form:"status" label:"状态" validate:"required,oneof=0 1"` } + +// BatchTransferDoctorItem 单个医生的迁移配置 +type BatchTransferDoctorItem struct { + DoctorId string `json:"doctor_id" form:"doctor_id" label:"医生id" validate:"required"` + TargetPharmacyId string `json:"target_pharmacy_id" form:"target_pharmacy_id" label:"目标药房id" validate:"required"` +} + +// BatchTransferAndAction 批量迁移医生默认药房并执行药房操作 +type BatchTransferAndAction struct { + SourcePharmacyId string `json:"source_pharmacy_id" form:"source_pharmacy_id" label:"原药房id" validate:"required"` + Action string `json:"action" form:"action" label:"操作类型" validate:"required,oneof=disable delete"` + UnbindSource *int `json:"unbind_source" form:"unbind_source" label:"是否解绑原药房"` // 1:是 0:否,若action为delete则强制解绑 + Transfers []BatchTransferDoctorItem `json:"transfers" form:"transfers" label:"迁移列表" validate:"required,dive"` +} diff --git a/api/router/router.go b/api/router/router.go index 206cfe3..7b59038 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -956,6 +956,12 @@ func privateRouter(r *gin.Engine, api controller.Api) { // 药房关联医生-导出 pharmacyGroup.POST("/doctor/export", api.Export.PharmacyDoctor) + + // 获取药房下线受影响医生列表(预检接口) + pharmacyGroup.GET("/affected-doctors/:pharmacy_id", api.Pharmacy.GetAffectedDoctors) + + // 批量迁移医生默认药房并执行药房操作(禁用/删除) + pharmacyGroup.POST("/batch-transfer-and-action", api.Pharmacy.BatchTransferAndAction) } // 科普分类管理 diff --git a/api/service/pharmacy.go b/api/service/pharmacy.go index 21a9334..c2cad4d 100644 --- a/api/service/pharmacy.go +++ b/api/service/pharmacy.go @@ -350,3 +350,258 @@ func (r *PharmacyService) GetPharmacyDoctorList(pharmacyId int64) ([]dto.Pharmac return dto.GetPharmacyDoctorListDto(list), nil } + +// GetAffectedDoctors 获取药房下线受影响医生列表(预检接口) +func (r *PharmacyService) GetAffectedDoctors(pharmacyId int64) (*dto.PharmacyAffectedDoctorsDto, error) { + pharmacyDao := dao.PharmacyDao{} + pharmacy, err := pharmacyDao.GetPharmacyById(pharmacyId) + if err != nil || pharmacy == nil { + return nil, errors.New("药房不存在或已删除") + } + + doctorPharmacyDao := dao.DoctorPharmacyDao{} + totalCount, defaultCount, err := doctorPharmacyDao.GetDoctorPharmacyCountsByPharmacyId(pharmacyId) + if err != nil { + return nil, errors.New("查询药房关联医生数量失败") + } + + // 获取当前药房绑定的所有医生 + list, err := doctorPharmacyDao.GetPharmacyDoctorListByPharmacyId(pharmacyId) + if err != nil { + return nil, errors.New("查询药房关联医生列表失败") + } + + // 收集所有医生ID + doctorIds := make([]int64, 0, len(list)) + for _, dp := range list { + doctorIds = append(doctorIds, dp.DoctorId) + } + + // 批量查询这些医生绑定的其他有效药房 + otherMap := make(map[int64][]dto.BoundPharmacySimpleDto) + if len(doctorIds) > 0 { + otherList, err := doctorPharmacyDao.GetOtherPharmaciesByDoctorIds(doctorIds, pharmacyId) + if err == nil { + for _, dp := range otherList { + if dp.Pharmacy != nil { + otherMap[dp.DoctorId] = append(otherMap[dp.DoctorId], dto.BoundPharmacySimpleDto{ + PharmacyId: strconv.FormatInt(dp.Pharmacy.PharmacyId, 10), + PharmacyName: dp.Pharmacy.PharmacyName, + PharmacyCode: dp.Pharmacy.PharmacyCode, + IsDefault: dp.IsDefault, + }) + } + } + } + } + + doctorItems := make([]dto.AffectedDoctorItemDto, 0, len(list)) + for _, dp := range list { + item := dto.AffectedDoctorItemDto{ + DoctorId: strconv.FormatInt(dp.DoctorId, 10), + DoctorPharmacyId: strconv.FormatInt(dp.DoctorPharmacyId, 10), + IsDefault: dp.IsDefault, + } + if dp.UserDoctor != nil { + item.UserId = strconv.FormatInt(dp.UserDoctor.UserId, 10) + item.UserName = dp.UserDoctor.UserName + item.DoctorTitleName = dto.GetDoctorTitleName(dp.UserDoctor.DoctorTitle) + item.HospitalId = strconv.FormatInt(dp.UserDoctor.HospitalID, 10) + if dp.UserDoctor.Hospital != nil { + item.HospitalName = dp.UserDoctor.Hospital.HospitalName + } + item.DepartmentCustomId = strconv.FormatInt(dp.UserDoctor.DepartmentCustomId, 10) + item.DepartmentCustomName = dp.UserDoctor.DepartmentCustomName + if dp.UserDoctor.User != nil { + item.Mobile = dp.UserDoctor.User.Mobile + } + } + if others, ok := otherMap[dp.DoctorId]; ok { + item.OtherBoundPharmacies = others + } else { + item.OtherBoundPharmacies = []dto.BoundPharmacySimpleDto{} + } + doctorItems = append(doctorItems, item) + } + + res := &dto.PharmacyAffectedDoctorsDto{ + PharmacyId: strconv.FormatInt(pharmacy.PharmacyId, 10), + PharmacyName: pharmacy.PharmacyName, + PharmacyCode: pharmacy.PharmacyCode, + Status: pharmacy.Status, + DefaultDoctorCount: defaultCount, + TotalDoctorCount: totalCount, + DoctorList: doctorItems, + } + return res, nil +} + +// BatchTransferAndAction 批量迁移医生默认药房并执行药房操作(禁用/删除) +func (r *PharmacyService) BatchTransferAndAction(req requests.BatchTransferAndAction) (bool, error) { + sourcePharmacyId, err := strconv.ParseInt(req.SourcePharmacyId, 10, 64) + if err != nil || sourcePharmacyId < 0 { + return false, errors.New("原药房id无效") + } + + if req.Action != "disable" && req.Action != "delete" { + return false, errors.New("操作类型仅支持 disable(禁用) 或 delete(删除)") + } + + pharmacyDao := dao.PharmacyDao{} + sourcePharmacy, err := pharmacyDao.GetPharmacyById(sourcePharmacyId) + if err != nil || sourcePharmacy == nil { + return false, errors.New("原药房不存在或已删除") + } + + // 查询当前以原药房为默认药房的所有医生 + doctorPharmacyDao := dao.DoctorPharmacyDao{} + var defaultDoctorPharmacies []*model.DoctorPharmacy + err = global.Db.Where("pharmacy_id = ? AND status = 1 AND is_default = 1", sourcePharmacyId).Find(&defaultDoctorPharmacies).Error + if err != nil { + return false, errors.New("查询受影响医生失败") + } + + defaultDoctorMap := make(map[int64]bool) + for _, dp := range defaultDoctorPharmacies { + defaultDoctorMap[dp.DoctorId] = true + } + + // 校验 transfers + transferDoctorMap := make(map[int64]int64) // doctorId -> targetPharmacyId + targetPharmacyIdSet := make(map[int64]bool) + + for _, item := range req.Transfers { + docId, err := strconv.ParseInt(item.DoctorId, 10, 64) + if err != nil || docId <= 0 { + return false, errors.New("迁移配置中包含无效的医生id: " + item.DoctorId) + } + targetPharId, err := strconv.ParseInt(item.TargetPharmacyId, 10, 64) + if err != nil || targetPharId < 0 { + return false, errors.New("迁移配置中包含无效的目标药房id: " + item.TargetPharmacyId) + } + if targetPharId == sourcePharmacyId { + return false, errors.New("目标药房不能是当前待下线的药房自身") + } + if _, exists := transferDoctorMap[docId]; exists { + return false, fmt.Errorf("医生(ID: %d)存在重复迁移配置,每位医生只能指定一个默认药房", docId) + } + transferDoctorMap[docId] = targetPharId + targetPharmacyIdSet[targetPharId] = true + } + + // 强校验:原药房名下所有默认医生必须全部指定了替代药房 + for docId := range defaultDoctorMap { + if _, ok := transferDoctorMap[docId]; !ok { + return false, fmt.Errorf("医生(ID: %d)当前以此药房为默认药房,必须为其指定替代药房", docId) + } + } + + // 校验所有目标药房必须存在且处于正常状态 (status = 1) + if len(targetPharmacyIdSet) > 0 { + var targetPharIds []int64 + for id := range targetPharmacyIdSet { + targetPharIds = append(targetPharIds, id) + } + + var targetPharmacies []*model.Pharmacy + err = global.Db.Where("pharmacy_id IN (?)", targetPharIds).Find(&targetPharmacies).Error + if err != nil { + return false, errors.New("查询目标药房信息失败") + } + + targetMap := make(map[int64]*model.Pharmacy) + for _, ph := range targetPharmacies { + targetMap[ph.PharmacyId] = ph + } + + for _, targetId := range targetPharIds { + ph, exists := targetMap[targetId] + if !exists || ph == nil { + return false, fmt.Errorf("目标药房(ID: %d)不存在或已被删除", targetId) + } + if ph.Status != 1 { + return false, fmt.Errorf("目标药房【%s】处于禁用状态,无法作为替代药房", ph.PharmacyName) + } + } + } + + // 开启事务执行迁移与药房操作 + tx := global.Db.Begin() + defer func() { + if rec := recover(); rec != nil { + tx.Rollback() + } + }() + + // 逐个医生处理目标药房绑定与默认设置 + for docId, targetPharId := range transferDoctorMap { + // 1. 重置该医生名下现有的所有药房为非默认 + if err := doctorPharmacyDao.ResetDoctorDefaultPharmacy(tx, docId); err != nil { + tx.Rollback() + return false, errors.New("重置医生默认药房失败") + } + + // 2. 检查该医生是否已绑定目标药房 + existTarget, _ := doctorPharmacyDao.GetDoctorPharmacyByDoctorAndPharmacy(docId, targetPharId) + if existTarget != nil { + // 更新为 status = 1, is_default = 1 + err = tx.Model(&model.DoctorPharmacy{}). + Where("doctor_pharmacy_id = ?", existTarget.DoctorPharmacyId). + Updates(map[string]interface{}{ + "is_default": 1, + "status": 1, + }).Error + if err != nil { + tx.Rollback() + return false, errors.New("更新目标药房绑定关系失败") + } + } else { + // 新增绑定记录 + newBinding := &model.DoctorPharmacy{ + DoctorId: docId, + PharmacyId: targetPharId, + IsDefault: 1, + Status: 1, + } + _, err = doctorPharmacyDao.AddDoctorPharmacy(tx, newBinding) + if err != nil { + tx.Rollback() + return false, errors.New("新增目标药房绑定关系失败: " + err.Error()) + } + } + + // 3. 处理医生与原药房关系 + if req.Action == "delete" || (req.UnbindSource != nil && *req.UnbindSource == 1) { + _ = doctorPharmacyDao.DeleteDoctorPharmacyByDoctorAndPharmacy(tx, docId, sourcePharmacyId) + } + } + + // 执行原药房下线操作 + if req.Action == "disable" { + // 若选择了解绑原药房,清空原药房所有绑定记录 + if req.UnbindSource != nil && *req.UnbindSource == 1 { + _ = doctorPharmacyDao.DeleteDoctorPharmacy(tx, map[string]interface{}{"pharmacy_id": sourcePharmacyId}) + } + + data := map[string]interface{}{ + "status": 0, + } + err = pharmacyDao.EditPharmacyById(tx, sourcePharmacyId, data) + if err != nil { + tx.Rollback() + return false, errors.New("修改药房状态失败: " + err.Error()) + } + } else if req.Action == "delete" { + // 删除药房必须清空所有关联记录,防止外键孤儿数据 + _ = doctorPharmacyDao.DeleteDoctorPharmacy(tx, map[string]interface{}{"pharmacy_id": sourcePharmacyId}) + + err = pharmacyDao.DeletePharmacyById(tx, sourcePharmacyId) + if err != nil { + tx.Rollback() + return false, errors.New("删除药房失败: " + err.Error()) + } + } + + tx.Commit() + return true, nil +}