Files
platforms/backend/api/internal/logic/gas/gasorder.go
2026-08-31 22:04:22 +08:00

427 lines
15 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.3.0
package gas
import (
"bytes"
"encoding/json"
"io"
"reflect"
"strings"
"time"
"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"
platformgasorder "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/platform/gasorder"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
func rewriteJSON(ctx *gin.Context, mutate func(map[string]any) bool) bool {
body, err := io.ReadAll(ctx.Request.Body)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return false
}
var values map[string]any
if err := json.Unmarshal(body, &values); err != nil || !mutate(values) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return false
}
body, err = json.Marshal(values)
if err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return false
}
ctx.Request.Body = io.NopCloser(bytes.NewReader(body))
return true
}
func scopedContract(ctx *gin.Context, identity string, gasID uint64) (models.GasorderContract, bool) {
var contract models.GasorderContract
if err := common.ActiveRecords(impl.DBService).Where("identity = ? AND gas_basic_id = ?", identity, gasID).First(&contract).Error; err != nil {
common.RespondRecordError(ctx, err)
return contract, false
}
return contract, true
}
func scopedOrder(ctx *gin.Context, identity string, gasID uint64) (models.GasorderBasic, bool) {
var order models.GasorderBasic
if err := common.ActiveRecords(impl.DBService).Where("identity = ? AND gas_basic_id = ?", identity, gasID).First(&order).Error; err != nil {
common.RespondRecordError(ctx, err)
return order, false
}
return order, true
}
func ListGasorderContract(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
page, size := common.PageSize(ctx)
query := common.ApplyKeywordFilter(ctx,
platformgasorder.ContractPartyDisplayQuery(impl.DBService).
Where("gasorder_contract.gas_basic_id = ?", station.ID),
&models.GasorderContract{})
candidate := strings.TrimSpace(ctx.Query("candidate"))
if candidate == "order" || candidate == "binding" {
query = platformgasorder.FilterGasorderContractCandidates(query, candidate, time.Now())
}
var total int64
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
var list []platformgasorder.ContractPartyDisplay
if err := query.Order("gasorder_contract.created_at desc").Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondList(ctx, list, total)
}
func GetGasorderContract(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, ok := scopedContract(ctx, ctx.Param("identity"), station.ID); ok {
platformgasorder.GetGasorderContract(ctx)
}
}
// UploadGasorderContractAttachment 为当前气站账号提供受控 PDF 临时上传。
func UploadGasorderContractAttachment(ctx *gin.Context) {
if _, ok := currentGas(ctx); !ok {
return
}
platformgasorder.UploadGasorderContractAttachment(ctx)
}
// CleanupGasorderContractAttachment 清理当前气站账号尚未绑定的临时附件。
func CleanupGasorderContractAttachment(ctx *gin.Context) {
if _, ok := currentGas(ctx); !ok {
return
}
platformgasorder.CleanupGasorderContractAttachment(ctx)
}
// ServeGasorderContractAttachment 仅预览当前气站合同范围内的正式附件。
func ServeGasorderContractAttachment(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, ok := scopedContract(ctx, ctx.Param("identity"), station.ID); ok {
platformgasorder.ServeGasorderContractAttachment(ctx)
}
}
func CreateGasorderContract(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
userIdentity, _ := values["user_account_identity"].(string)
if _, _, valid := requireUser(ctx, userIdentity, station.ID); !valid {
return false
}
if deliveryIdentity, _ := values["delivery_basic_identity"].(string); deliveryIdentity != "" {
if _, valid := requireDelivery(ctx, deliveryIdentity, station.ID); !valid {
return false
}
}
values["gas_basic_identity"] = station.Identity
return true
}) {
return
}
platformgasorder.CreateGasorderContract(ctx)
}
func withContract(ctx *gin.Context, handler gin.HandlerFunc) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, ok := scopedContract(ctx, ctx.Param("identity"), station.ID); ok {
handler(ctx)
}
}
func UpdateGasorderContract(ctx *gin.Context) {
withContract(ctx, platformgasorder.UpdateGasorderContract)
}
func ActivateGasorderContract(ctx *gin.Context) {
withContract(ctx, platformgasorder.ActivateGasorderContract)
}
func RenewGasorderContract(ctx *gin.Context) {
withContract(ctx, platformgasorder.RenewGasorderContract)
}
func TerminateGasorderContract(ctx *gin.Context) {
withContract(ctx, platformgasorder.TerminateGasorderContract)
}
func BindGasorderContractProduct(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
identity, _ := values["gasorder_contract_identity"].(string)
_, valid := scopedContract(ctx, identity, station.ID)
return valid
}) {
return
}
platformgasorder.BindGasorderContractProduct(ctx)
}
func UnbindGasorderContractProduct(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
var binding models.GasorderContractProduct
err := common.ActiveRecords(impl.DBService.Model(&models.GasorderContractProduct{})).
Select("gasorder_contract_product.*").
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_product.gasorder_contract_id").
Where("gasorder_contract_product.identity = ? AND gasorder_contract.gas_basic_id = ?", ctx.Param("identity"), station.ID).
First(&binding).Error
if err != nil {
common.RespondRecordError(ctx, err)
return
}
platformgasorder.UnbindGasorderContractProduct(ctx)
}
func ListGasorderContractProduct(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
// 订单候选场景先校验合同属于当前气站,再复用可读名称和有效绑定过滤。
if contractIdentity := strings.TrimSpace(ctx.Query("contract_identity")); contractIdentity != "" {
if _, ok := scopedContract(ctx, contractIdentity, station.ID); !ok {
return
}
platformgasorder.ListGasorderContractProduct(ctx)
return
}
query := common.ActiveRecords(impl.DBService.Model(&models.GasorderContractProduct{})).
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_product.gasorder_contract_id").
Where("gasorder_contract.gas_basic_id = ?", station.ID)
listScoped(ctx, &models.GasorderContractProduct{}, query, "gasorder_contract_product.created_at desc")
}
// GetGasorderContractProduct 返回当前气站合同范围内的单条合同气瓶。
func GetGasorderContractProduct(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
query := common.ActiveRecords(impl.DBService.Model(&models.GasorderContractProduct{})).
Select("gasorder_contract_product.*").
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_product.gasorder_contract_id").
Where("gasorder_contract_product.identity = ? AND gasorder_contract.gas_basic_id = ?", ctx.Param("identity"), station.ID)
respondScopedRecord(ctx, query, &models.GasorderContractProduct{})
}
// gasContractProductCandidateQuery 按合同用户和当前气站服务关系限定可绑定智能气阀。
func gasContractProductCandidateQuery(databaseService *gorm.DB, contract models.GasorderContract, gasBasicID uint64) *gorm.DB {
query := common.ActiveRecords(databaseService.Model(&models.ProductInfo{}))
return platformgasorder.ContractProductCandidateQuery(query, contract).
Where(`EXISTS (
SELECT 1 FROM user_service_relation AS candidate_relation
WHERE candidate_relation.user_account_id = product_info.user_account_id
AND candidate_relation.gas_basic_id = ?
AND candidate_relation.status <> ?
AND candidate_relation.deleted_at IS NULL
)`, gasBasicID, common.StatusArchived)
}
// ListContractProductCandidate 仅返回属于指定合同用户且仍在当前气站服务范围内的可绑定气阀。
func ListContractProductCandidate(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
query := common.ActiveRecords(impl.DBService.Model(&models.ProductInfo{})).Where("1 = 0")
if contractIdentity := strings.TrimSpace(ctx.Query("contract_identity")); contractIdentity != "" {
contract, valid := scopedContract(ctx, contractIdentity, station.ID)
if !valid {
return
}
query = gasContractProductCandidateQuery(impl.DBService, contract, station.ID)
}
listScoped(ctx, &models.ProductInfo{}, query, "product_info.created_at desc")
}
// GetContractProductCandidate 返回指定合同用户范围内的单条候选气阀。
func GetContractProductCandidate(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
contractIdentity := strings.TrimSpace(ctx.Query("contract_identity"))
query := common.ActiveRecords(impl.DBService.Model(&models.ProductInfo{})).Where("1 = 0")
if contractIdentity != "" {
contract, valid := scopedContract(ctx, contractIdentity, station.ID)
if !valid {
return
}
query = gasContractProductCandidateQuery(impl.DBService, contract, station.ID)
}
query = query.Select("product_info.*").Where("product_info.identity = ?", ctx.Param("identity"))
respondScopedRecord(ctx, query, &models.ProductInfo{})
}
func ListGasorderContractRevision(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
query := common.ActiveRecords(impl.DBService.Model(&models.GasorderContractRevision{})).
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_revision.gasorder_contract_id").
Where("gasorder_contract.gas_basic_id = ?", station.ID)
listScoped(ctx, &models.GasorderContractRevision{}, query, "gasorder_contract_revision.created_at desc")
}
// GetGasorderContractRevision 返回当前气站合同范围内的单条修订记录。
func GetGasorderContractRevision(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
query := common.ActiveRecords(impl.DBService.Model(&models.GasorderContractRevision{})).
Select("gasorder_contract_revision.*").
Joins("JOIN gasorder_contract ON gasorder_contract.id = gasorder_contract_revision.gasorder_contract_id").
Where("gasorder_contract_revision.identity = ? AND gasorder_contract.gas_basic_id = ?", ctx.Param("identity"), station.ID)
respondScopedRecord(ctx, query, &models.GasorderContractRevision{})
}
func ListGasorderBasic(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
page, size := common.PageSize(ctx)
query := common.ApplyKeywordFilter(ctx, scopedOrderDisplayQuery(impl.DBService, station.ID), &models.GasorderBasic{})
var total int64
if err := query.Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
var list []gasOrderListDisplay
if err := query.Order("gasorder_basic.created_at desc").Offset((page - 1) * size).Limit(size).Scan(&list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondList(ctx, list, total)
}
// gasOrderListDisplay 在订单主档上附加订单权限范围内的用户可读名称。
type gasOrderListDisplay struct {
models.GasorderBasic
UserAccountDisplayName string `gorm:"column:user_account_display_name" json:"user_account_display_name"`
}
// scopedOrderDisplayQuery 通过订单保存的用户主键读取名称,并始终限制在当前气站订单范围。
func scopedOrderDisplayQuery(databaseService *gorm.DB, gasBasicID uint64) *gorm.DB {
return common.ActiveRecords(databaseService.Model(&models.GasorderBasic{})).
Select(`gasorder_basic.*,
COALESCE(NULLIF(order_user.name, ''), NULLIF(order_user.real_name, ''), NULLIF(order_user.username, ''), '用户记录已失效') AS user_account_display_name`).
Joins("LEFT JOIN user_account AS order_user ON order_user.id = gasorder_basic.user_account_id").
Where("gasorder_basic.gas_basic_id = ?", gasBasicID)
}
func GetGasorderBasic(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, ok := scopedOrder(ctx, ctx.Param("identity"), station.ID); ok {
platformgasorder.GetGasorderBasic(ctx)
}
}
func CreateGasorderBasic(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
contractIdentity, _ := values["gasorder_contract_identity"].(string)
if _, valid := scopedContract(ctx, contractIdentity, station.ID); !valid {
return false
}
values["creator_type"] = "gas"
values["creator_identity"] = station.Identity
return true
}) {
return
}
platformgasorder.CreateGasorderBasic(ctx)
}
func withOrder(ctx *gin.Context, handler gin.HandlerFunc) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, ok := scopedOrder(ctx, ctx.Param("identity"), station.ID); ok {
handler(ctx)
}
}
func AssignGasorderBasic(ctx *gin.Context) {
station, ok := currentGas(ctx)
if !ok {
return
}
if _, ok := scopedOrder(ctx, ctx.Param("identity"), station.ID); !ok {
return
}
if !rewriteJSON(ctx, func(values map[string]any) bool {
deliveryIdentity, _ := values["delivery_basic_identity"].(string)
staffIdentity, _ := values["staff_account_identity"].(string)
_, deliveryOK := requireDelivery(ctx, deliveryIdentity, station.ID)
_, staffOK := requireStaff(ctx, staffIdentity, station.ID)
return deliveryOK && staffOK
}) {
return
}
platformgasorder.AssignGasorderBasic(ctx)
}
func GasorderStartFilling(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderStartFilling) }
func GasorderReady(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderReady) }
func GasorderException(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderException) }
func GasorderRecover(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderRecover) }
func GasorderCancel(ctx *gin.Context) { withOrder(ctx, platformgasorder.GasorderCancel) }
func listScoped(ctx *gin.Context, model any, query *gorm.DB, order string) {
page, size := common.PageSize(ctx)
var total int64
if err := common.ApplyKeywordFilter(ctx, query, model).Count(&total).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
list := sliceForModel(model)
if err := common.ApplyKeywordFilter(ctx, query, model).Order(order).Offset((page - 1) * size).Limit(size).Find(list).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
respondList(ctx, list, total)
}
func sliceForModel(model any) any {
return reflect.New(reflect.SliceOf(reflect.TypeOf(model).Elem())).Interface()
}