Files
platforms/backend/api/internal/logic/platform/user/relation.go
2026-08-13 22:50:37 +08:00

277 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 功能描述:实现平台总后台的用户地址与服务关系管理。
// 版本v1.1
package user
import (
"errors"
"strings"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"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"
)
type addressRequest struct {
UserAccountIdentity string `json:"user_account_identity" binding:"required"`
Address string `json:"address" binding:"required,max=255"`
Longitude string `json:"longitude" binding:"max=32"`
Latitude string `json:"latitude" binding:"max=32"`
IsDefault bool `json:"is_default"`
}
// ListUserAddress 查询用户地址分页列表,并为已授权的平台页面恢复地址与坐标。
func ListUserAddress(ctx *gin.Context) {
page, size := common.PageSize(ctx)
var list []models.UserAddress
var total int64
query := common.ApplyKeywordFilter(ctx, common.ActiveRecords(impl.DBService.Model(&models.UserAddress{})), &models.UserAddress{})
query = filterUserAddressByContract(query, ctx.Query("gasorder_contract_identity"))
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
if err := query.Order("created_at desc").Offset((page - 1) * size).Limit(size).Find(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
response, err := common.PublicResourceResponse(list)
if err != nil {
infra.Response.Error(ctx, err)
return
}
protected := common.ProtectPreciseLocation(ctx, &models.UserAddress{}, response)
infra.Response.Success(ctx, gin.H{"total": total, "list": restoreUserAddressLocations(protected, list)})
}
// filterUserAddressByContract 仅返回指定配送合同签约用户的地址,避免后台下单时跨用户选择。
func filterUserAddressByContract(query *gorm.DB, contractIdentity string) *gorm.DB {
contractIdentity = strings.TrimSpace(contractIdentity)
if contractIdentity == "" {
return query
}
contractUser := common.ActiveRecords(impl.DBService.Model(&models.GasorderContract{})).
Select("user_account_id").Where("identity = ?", contractIdentity)
return query.Where("user_account_id = (?)", contractUser)
}
// GetUserAddress 查询用户地址详情,并返回详情与编辑页需要的地址和坐标。
func GetUserAddress(ctx *gin.Context) {
var address models.UserAddress
if err := common.ActiveRecords(impl.DBService).Where("identity = ?", ctx.Param("identity")).First(&address).Error; err != nil {
common.RespondRecordError(ctx, err)
return
}
response, err := common.PublicResourceResponse(address)
if err != nil {
infra.Response.Error(ctx, err)
return
}
protected := common.ProtectPreciseLocation(ctx, &models.UserAddress{}, response)
infra.Response.Success(ctx, restoreUserAddressLocations(protected, []models.UserAddress{address}))
}
// restoreUserAddressLocations 仅在用户地址受控接口中恢复完整地址和精确坐标。
func restoreUserAddressLocations(response any, addresses []models.UserAddress) any {
restore := func(record map[string]any, address models.UserAddress) {
record["address"] = address.Address
record["longitude"] = address.Longitude
record["latitude"] = address.Latitude
}
switch data := response.(type) {
case []any:
for index, item := range data {
if index >= len(addresses) {
break
}
if record, ok := item.(map[string]any); ok {
restore(record, addresses[index])
}
}
case map[string]any:
if len(addresses) > 0 {
restore(data, addresses[0])
}
}
return response
}
func CreateUserAddress(ctx *gin.Context) {
var request addressRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
userAccountID, err := common.ResolveIdentityID(&models.UserAccount{}, request.UserAccountIdentity, true)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
address := models.UserAddress{Entity: common.NewEntity(common.StatusEnable), UserAccountID: userAccountID, Address: request.Address, Longitude: request.Longitude, Latitude: request.Latitude, IsDefault: request.IsDefault}
if err := impl.DBService.Transaction(func(tx *gorm.DB) error {
if request.IsDefault {
if err := tx.Model(&models.UserAddress{}).Where("user_account_id = ?", userAccountID).Update("is_default", false).Error; err != nil {
return err
}
}
return tx.Create(&address).Error
}); err != nil {
infra.Response.Error(ctx, err)
return
}
common.RespondCreatedResource(ctx, address)
}
func UpdateUserAddress(ctx *gin.Context) {
var request addressRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
userAccountID, err := common.ResolveIdentityID(&models.UserAccount{}, request.UserAccountIdentity, true)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
err = impl.DBService.Transaction(func(tx *gorm.DB) error {
var current models.UserAddress
if err := tx.Where("identity = ?", ctx.Param("identity")).First(&current).Error; err != nil {
return err
}
if current.UserAccountID != userAccountID {
return errors.New("address owner cannot be changed")
}
if request.IsDefault {
if err := tx.Model(&models.UserAddress{}).
Where("user_account_id = ? AND id <> ?", userAccountID, current.ID).
Update("is_default", false).Error; err != nil {
return err
}
}
return tx.Model(&current).Updates(userAddressUpdateValues(request, userAccountID)).Error
})
if err != nil {
common.RespondRecordError(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"updated": true})
}
// userAddressUpdateValues 构造 GORM 支持的标准更新映射,并保留 false 等零值字段。
func userAddressUpdateValues(request addressRequest, userAccountID uint64) map[string]any {
return map[string]any{
"user_account_id": userAccountID,
"address": request.Address,
"longitude": request.Longitude,
"latitude": request.Latitude,
"is_default": request.IsDefault,
}
}
type serviceRelationRequest struct {
UserAccountIdentity string `json:"user_account_identity" binding:"required"`
GasBasicIdentity string `json:"gas_basic_identity"`
DeliveryBasicIdentity string `json:"delivery_basic_identity"`
StaffAccountIdentity string `json:"staff_account_identity"`
}
func ListUserServiceRelation(ctx *gin.Context) { common.ListPage[models.UserServiceRelation](ctx) }
func GetUserServiceRelation(ctx *gin.Context) { common.GetByIdentity[models.UserServiceRelation](ctx) }
func CreateUserServiceRelation(ctx *gin.Context) {
var request serviceRelationRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
userAccountID, gasBasicID, deliveryBasicID, staffAccountID, ok := resolveServiceRelation(ctx, request)
if !ok {
return
}
relation := models.UserServiceRelation{Entity: common.NewEntity(common.StatusEnable), UserAccountID: userAccountID, GasBasicID: gasBasicID, DeliveryBasicID: deliveryBasicID, StaffAccountID: staffAccountID}
if err := impl.DBService.Create(&relation).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
common.RespondCreatedResource(ctx, relation)
}
func UpdateUserServiceRelation(ctx *gin.Context) {
var request serviceRelationRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
userAccountID, gasBasicID, deliveryBasicID, staffAccountID, ok := resolveServiceRelation(ctx, request)
if !ok {
return
}
var current models.UserServiceRelation
if err := common.ActiveRecords(impl.DBService).Select("user_account_id").
Where("identity = ?", ctx.Param("identity")).First(&current).Error; err != nil {
common.RespondRecordError(ctx, err)
return
}
if current.UserAccountID != userAccountID {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
common.UpdateAllowedByIdentity(ctx, &models.UserServiceRelation{}, gin.H{"user_account_id": userAccountID, "gas_basic_id": gasBasicID, "delivery_basic_id": deliveryBasicID, "staff_account_id": staffAccountID}, []string{"user_account_id", "gas_basic_id", "delivery_basic_id", "staff_account_id"})
}
func resolveServiceRelation(ctx *gin.Context, request serviceRelationRequest) (uint64, uint64, uint64, uint64, bool) {
userAccountID, err := common.ResolveIdentityID(&models.UserAccount{}, request.UserAccountIdentity, true)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return 0, 0, 0, 0, false
}
gasBasicID, err := common.ResolveIdentityID(&models.GasBasic{}, request.GasBasicIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return 0, 0, 0, 0, false
}
deliveryBasicID, err := common.ResolveIdentityID(&models.DeliveryBasic{}, request.DeliveryBasicIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return 0, 0, 0, 0, false
}
staffAccountID, err := common.ResolveIdentityID(&models.StaffAccount{}, request.StaffAccountIdentity, false)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return 0, 0, 0, 0, false
}
if !validateServiceRelationOrganization(gasBasicID, deliveryBasicID, staffAccountID) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return 0, 0, 0, 0, false
}
return userAccountID, gasBasicID, deliveryBasicID, staffAccountID, true
}
// validateServiceRelationOrganization 读取组织记录并执行服务关系专用的严格归属校验。
func validateServiceRelationOrganization(gasBasicID, deliveryBasicID, staffAccountID uint64) bool {
var delivery models.DeliveryBasic
if deliveryBasicID != 0 {
if err := common.ActiveRecords(impl.DBService).First(&delivery, deliveryBasicID).Error; err != nil {
return false
}
}
var staff models.StaffAccount
if staffAccountID != 0 {
if err := common.ActiveRecords(impl.DBService).First(&staff, staffAccountID).Error; err != nil {
return false
}
}
return serviceRelationOrganizationMatches(gasBasicID, deliveryBasicID, staffAccountID, delivery, staff)
}
// serviceRelationOrganizationMatches 要求配送点、服务人员与当前选择完全一致;全空表示暂未分配。
func serviceRelationOrganizationMatches(gasBasicID, deliveryBasicID, staffAccountID uint64, delivery models.DeliveryBasic, staff models.StaffAccount) bool {
if deliveryBasicID != 0 && delivery.GasBasicID != gasBasicID {
return false
}
if staffAccountID == 0 {
return true
}
validRole := staff.RoleCode == "installer" || staff.RoleCode == "delivery" || staff.RoleCode == "operations"
return validRole && staff.Status == common.StatusEnable && staff.GasBasicID == gasBasicID && staff.DeliveryBasicID == deliveryBasicID
}