2026-07-30 14:16:58 +08:00
package gas
import (
"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"
2026-08-18 20:28:01 +08:00
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/upload"
2026-07-30 14:16:58 +08:00
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
2026-08-18 20:52:15 +08:00
// gasUserListItem 在用户主档上附加当前气站服务关系中的配送点归属。
type gasUserListItem struct {
models . UserAccount
DeliveryBasicID uint64 ` gorm:"column:delivery_basic_id" json:"delivery_basic_id" `
}
2026-08-18 22:49:14 +08:00
// gasUserDetail 标识用户是否仅因当前气站合同而可见,前端据此进入只读模式。
type gasUserDetail struct {
models . UserAccount
ContractReadonly bool ` json:"contract_readonly" `
}
2026-08-18 20:52:15 +08:00
// scopedUserList 将用户列表与当前气站唯一有效服务关系绑定,避免返回跨站归属。
func scopedUserList ( databaseService * gorm . DB , gasBasicID uint64 ) * gorm . DB {
return common . ActiveRecords ( databaseService . Model ( & models . UserAccount { } ) ) .
Select ( "user_account.*, user_service_relation.delivery_basic_id AS delivery_basic_id" ) .
Joins ( "JOIN user_service_relation ON user_service_relation.user_account_id = user_account.id AND user_service_relation.status <> ?" , common . StatusArchived ) .
Where ( "user_service_relation.gas_basic_id = ?" , gasBasicID )
}
2026-07-30 14:16:58 +08:00
func ListUser ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
page , size := common . PageSize ( ctx )
2026-08-18 20:52:15 +08:00
var list [ ] gasUserListItem
2026-07-30 14:16:58 +08:00
var total int64
query := common . ApplyKeywordFilter ( ctx ,
2026-08-18 20:52:15 +08:00
scopedUserList ( impl . DBService , station . ID ) ,
2026-07-30 14:16:58 +08:00
& models . UserAccount { } )
if err := query . Count ( & total ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
if err := query . Order ( "user_account.created_at desc" ) . Offset ( ( page - 1 ) * size ) . Limit ( size ) . Find ( & list ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
respondList ( ctx , list , total )
}
func GetUser ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
2026-08-18 22:49:14 +08:00
user , relation , err := findCurrentGasUser ( impl . DBService , ctx . Param ( "identity" ) , station . ID )
contractReadonly := false
var contractDeliveryBasicID uint64
if err != nil {
contractUser , contractErr := findContractScopedUser (
impl . DBService , ctx . Param ( "identity" ) , station . ID , ctx . Query ( "contract_identity" ) ,
)
err = contractErr
if err != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
user = contractUser . UserAccount
contractReadonly = true
contractDeliveryBasicID = contractUser . ContractDeliveryBasicID
}
if contractReadonly {
// 合同范围只返回用户主档,不泄露地址、钱包或其他服务关系上下文。
detail := gasUserDetail { UserAccount : user , ContractReadonly : true }
response , responseErr := common . PublicResourceResponse ( gin . H {
"user" : detail ,
"relation" : gin . H { "delivery_basic_id" : contractDeliveryBasicID } ,
"addresses" : [ ] models . UserAddress { } ,
} )
if responseErr != nil {
infra . Response . Error ( ctx , responseErr )
return
}
infra . Response . Success ( ctx , response )
2026-07-30 14:16:58 +08:00
return
}
var addresses [ ] models . UserAddress
if err := common . ActiveRecords ( impl . DBService ) . Where ( "user_account_id = ?" , user . ID ) . Order ( "is_default desc, created_at desc" ) . Find ( & addresses ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
response , err := common . PublicResourceResponse ( gin . H { "user" : user , "relation" : relation , "addresses" : addresses } )
if err != nil {
infra . Response . Error ( ctx , err )
return
}
infra . Response . Success ( ctx , response )
}
2026-08-18 22:49:14 +08:00
// GetUserAvatar 返回与当前气站存在服务关系或合同引用关系的用户受保护头像。
2026-08-18 20:28:01 +08:00
func GetUserAvatar ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
2026-08-18 22:49:14 +08:00
user , _ , err := findCurrentGasUser ( impl . DBService , ctx . Param ( "identity" ) , station . ID )
if err != nil {
contractUser , contractErr := findContractScopedUser ( impl . DBService , ctx . Param ( "identity" ) , station . ID , "" )
err = contractErr
if err != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
user = contractUser . UserAccount
2026-08-18 20:28:01 +08:00
}
upload . ServeAvatar ( ctx , user . Avatar )
}
2026-07-30 14:16:58 +08:00
type userRequest struct {
2026-08-18 20:28:01 +08:00
Username string ` json:"username" `
Password string ` json:"password" `
Name string ` json:"name" binding:"required,max=64" `
Phone string ` json:"phone" binding:"max=32" `
Avatar * string ` json:"avatar" binding:"omitempty,max=512" `
RealName string ` json:"real_name" binding:"max=64" `
DeliveryBasicIdentity string ` json:"delivery_basic_identity" binding:"required" `
2026-07-30 14:16:58 +08:00
}
func CreateUser ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
var request userRequest
if err := ctx . ShouldBindJSON ( & request ) ; err != nil || request . Username == "" || ! common . IsValidAccountPassword ( request . Password ) {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
delivery , ok := requireDelivery ( ctx , request . DeliveryBasicIdentity , station . ID )
if ! ok {
return
}
hash , err := common . PasswordHash ( request . Password )
if err != nil {
infra . Response . Error ( ctx , err )
return
}
2026-08-18 20:28:01 +08:00
avatar := ""
if request . Avatar != nil {
avatar = * request . Avatar
}
2026-07-30 14:16:58 +08:00
user := models . UserAccount {
Entity : common . NewEntity ( common . StatusEnable ) , Username : request . Username , PasswordHash : hash ,
2026-08-18 20:28:01 +08:00
Name : request . Name , Phone : request . Phone , Avatar : avatar , RealName : request . RealName ,
2026-07-30 14:16:58 +08:00
}
relation := models . UserServiceRelation { Entity : common . NewEntity ( common . StatusEnable ) , GasBasicID : station . ID , DeliveryBasicID : delivery . ID }
if err := impl . DBService . Transaction ( func ( tx * gorm . DB ) error {
if err := tx . Create ( & user ) . Error ; err != nil {
return err
}
relation . UserAccountID = user . ID
return tx . Create ( & relation ) . Error
} ) ; err != nil {
infra . Response . Error ( ctx , err )
return
}
common . RespondCreatedResource ( ctx , user )
}
func UpdateUser ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
user , relation , ok := requireUser ( ctx , ctx . Param ( "identity" ) , station . ID )
if ! ok {
return
}
var request userRequest
if err := ctx . ShouldBindJSON ( & request ) ; err != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
delivery , ok := requireDelivery ( ctx , request . DeliveryBasicIdentity , station . ID )
if ! ok {
return
}
if err := impl . DBService . Transaction ( func ( tx * gorm . DB ) error {
2026-08-18 20:28:01 +08:00
values := map [ string ] any {
"name" : request . Name , "phone" : request . Phone , "real_name" : request . RealName ,
}
// 未选择新头像时不提交 avatar, 避免编辑基础资料误清空现有头像。
if request . Avatar != nil {
values [ "avatar" ] = * request . Avatar
}
if err := tx . Model ( & user ) . Updates ( values ) . Error ; err != nil {
2026-07-30 14:16:58 +08:00
return err
}
return tx . Model ( & relation ) . Update ( "delivery_basic_id" , delivery . ID ) . Error
} ) ; err != nil {
infra . Response . Error ( ctx , err )
return
}
infra . Response . Success ( ctx , gin . H { "updated" : true } )
}
func UpdateUserStatus ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
if _ , _ , ok := requireUser ( ctx , ctx . Param ( "identity" ) , station . ID ) ; ! ok {
return
}
common . UpdateRecordStatus ( ctx , & models . UserAccount { } )
}
func ArchiveUser ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
user , relation , ok := requireUser ( ctx , ctx . Param ( "identity" ) , station . ID )
if ! ok {
return
}
var blocking int64
if err := impl . DBService . Model ( & models . GasorderBasic { } ) .
Where ( "user_account_id = ? AND order_status NOT IN ?" , user . ID , [ ] int { common . StatusCompleted , common . StatusCancelled } ) .
Count ( & blocking ) . Error ; err != nil || blocking > 0 {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
if err := impl . DBService . Model ( & models . CsTicket { } ) .
Where ( "user_account_id = ? AND status <> ? AND ticket_status = ?" , user . ID , common . StatusArchived , common . StatusOpen ) .
Count ( & blocking ) . Error ; err != nil || blocking > 0 {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
var wallet models . WalletBasic
err := impl . DBService . Where ( "owner_type = ? AND owner_id = ? AND status <> ?" , "user" , user . ID , common . StatusArchived ) . First ( & wallet ) . Error
if err == nil && ( wallet . Balance > 0 || wallet . WithdrawalBalance > 0 ) {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
if err == nil {
if cashErr := impl . DBService . Model ( & models . WalletApplyCash { } ) .
Where ( "wallet_basic_id = ? AND status <> ? AND apply_status = ?" , wallet . ID , common . StatusArchived , common . StatusPending ) .
Count ( & blocking ) . Error ; cashErr != nil || blocking > 0 {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
}
if err != nil && err != gorm . ErrRecordNotFound {
infra . Response . Error ( ctx , err )
return
}
if err := impl . DBService . Transaction ( func ( tx * gorm . DB ) error {
if err := tx . Model ( & relation ) . Update ( "status" , common . StatusArchived ) . Error ; err != nil {
return err
}
if err := tx . Model ( & models . UserAddress { } ) . Where ( "user_account_id = ? AND status <> ?" , user . ID , common . StatusArchived ) .
Update ( "status" , common . StatusArchived ) . Error ; err != nil {
return err
}
return tx . Model ( & user ) . Update ( "status" , common . StatusArchived ) . Error
} ) ; err != nil {
infra . Response . Error ( ctx , err )
return
}
infra . Response . Success ( ctx , gin . H { "archived" : true } )
}
func ListUserAddress ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
page , size := common . PageSize ( ctx )
var list [ ] models . UserAddress
var total int64
query := common . ActiveRecords ( impl . DBService . Model ( & models . UserAddress { } ) ) .
Joins ( "JOIN user_service_relation ON user_service_relation.user_account_id = user_address.user_account_id AND user_service_relation.status <> ?" ,
common . StatusArchived ) .
Where ( "user_service_relation.gas_basic_id = ?" , station . ID )
if err := query . Count ( & total ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
if err := query . Order ( "user_address.created_at desc" ) . Offset ( ( page - 1 ) * size ) . Limit ( size ) . Find ( & list ) . Error ; err != nil {
infra . Response . Error ( ctx , err )
return
}
respondList ( ctx , list , total )
}
func GetUserAddress ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
var address models . UserAddress
query := common . ActiveRecords ( impl . DBService . Model ( & models . UserAddress { } ) ) .
Select ( "user_address.*" ) .
Joins ( "JOIN user_service_relation ON user_service_relation.user_account_id = user_address.user_account_id AND user_service_relation.status <> ?" ,
common . StatusArchived ) .
Where ( "user_address.identity = ? AND user_service_relation.gas_basic_id = ?" , ctx . Param ( "identity" ) , station . ID )
respondScopedRecord ( ctx , query , & address )
}
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" `
}
func CreateUserAddress ( ctx * gin . Context ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
var request addressRequest
if err := ctx . ShouldBindJSON ( & request ) ; err != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
user , _ , ok := requireUser ( ctx , request . UserAccountIdentity , station . ID )
if ! ok {
return
}
address := models . UserAddress {
Entity : common . NewEntity ( common . StatusEnable ) , UserAccountID : user . ID , 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 = ?" , user . ID ) . 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 ) {
station , ok := currentGas ( ctx )
if ! ok {
return
}
var existing models . UserAddress
if err := common . ActiveRecords ( impl . DBService . Model ( & models . UserAddress { } ) ) .
Select ( "user_address.*" ) .
Joins ( "JOIN user_service_relation ON user_service_relation.user_account_id = user_address.user_account_id AND user_service_relation.status <> ?" ,
common . StatusArchived ) .
Where ( "user_address.identity = ? AND user_service_relation.gas_basic_id = ?" , ctx . Param ( "identity" ) , station . ID ) .
First ( & existing ) . Error ; err != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
var request addressRequest
if err := ctx . ShouldBindJSON ( & request ) ; err != nil {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
user , _ , ok := requireUser ( ctx , request . UserAccountIdentity , station . ID )
if ! ok || user . ID != existing . UserAccountID {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return
}
if err := impl . DBService . Transaction ( func ( tx * gorm . DB ) error {
if request . IsDefault {
if err := tx . Model ( & models . UserAddress { } ) . Where ( "user_account_id = ? AND id <> ?" , user . ID , existing . ID ) .
Update ( "is_default" , false ) . Error ; err != nil {
return err
}
}
return tx . Model ( & existing ) . Updates ( map [ string ] any {
"address" : request . Address , "longitude" : request . Longitude , "latitude" : request . Latitude , "is_default" : request . IsDefault ,
} ) . Error
} ) ; err != nil {
infra . Response . Error ( ctx , err )
return
}
infra . Response . Success ( ctx , gin . H { "updated" : true } )
}
func UpdateUserAddressStatus ( ctx * gin . Context ) {
if ! requireScopedAddress ( ctx ) {
return
}
common . UpdateRecordStatus ( ctx , & models . UserAddress { } )
}
func ArchiveUserAddress ( ctx * gin . Context ) {
if ! requireScopedAddress ( ctx ) {
return
}
common . ArchiveRecord ( ctx , & models . UserAddress { } )
}
func requireScopedAddress ( ctx * gin . Context ) bool {
station , ok := currentGas ( ctx )
if ! ok {
return false
}
var count int64
err := impl . DBService . Model ( & models . UserAddress { } ) .
Joins ( "JOIN user_service_relation ON user_service_relation.user_account_id = user_address.user_account_id AND user_service_relation.status <> ?" ,
common . StatusArchived ) .
Where ( "user_address.identity = ? AND user_service_relation.gas_basic_id = ?" , ctx . Param ( "identity" ) , station . ID ) .
Count ( & count ) . Error
if err != nil || count != 1 {
infra . Response . Error ( ctx , errcode . ErrInvalidArgument )
return false
}
return true
}