药房下线(禁用/删除)医生分流迁移

This commit is contained in:
haomingming
2026-09-17 08:51:56 +08:00
parent b349acb4b4
commit cfdac70e94
6 changed files with 405 additions and 0 deletions
+51
View File
@@ -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)
}
+38
View File
@@ -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{}).
+40
View File
@@ -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"`
}
+15
View File
@@ -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"`
}
+6
View File
@@ -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)
}
// 科普分类管理
+255
View File
@@ -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
}