feat(t245): use ERP images as procurement references
This commit is contained in:
@@ -7,16 +7,29 @@ import (
|
||||
"cmroubao/backend-api/internal/domain"
|
||||
)
|
||||
|
||||
type AutoProcurementReference struct {
|
||||
Asset domain.Asset
|
||||
ProductThumbRef string
|
||||
SourceImageSHA256 string
|
||||
SourceImageStorageKey string
|
||||
}
|
||||
|
||||
type ProcurementRepository interface {
|
||||
GetProcurementSourceItem(
|
||||
context.Context,
|
||||
string,
|
||||
string,
|
||||
) (domain.ProcurementSourceItem, error)
|
||||
GetReadyFreightItemImage(
|
||||
context.Context,
|
||||
string,
|
||||
string,
|
||||
) (domain.FreightItemImage, error)
|
||||
CreateProcurementRequest(
|
||||
context.Context,
|
||||
domain.ProcurementRequest,
|
||||
) (domain.ProcurementRequest, bool, error)
|
||||
*AutoProcurementReference,
|
||||
) (domain.ProcurementRequest, bool, bool, error)
|
||||
ListProcurementRequestsForOrder(
|
||||
context.Context,
|
||||
string,
|
||||
@@ -33,7 +46,7 @@ type ProcurementRepository interface {
|
||||
string,
|
||||
string,
|
||||
time.Time,
|
||||
) (domain.ProcurementRequest, error)
|
||||
) (domain.ProcurementRequest, *string, error)
|
||||
CreateProcurementTask(
|
||||
context.Context,
|
||||
domain.ProcurementRequest,
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"cmroubao/backend-api/internal/domain"
|
||||
@@ -21,10 +22,25 @@ var (
|
||||
|
||||
type ProcurementService struct {
|
||||
repository ProcurementRepository
|
||||
store ReferenceImageStore
|
||||
clock Clock
|
||||
ids IDGenerator
|
||||
}
|
||||
|
||||
type ProcurementServiceOption func(*ProcurementService) error
|
||||
|
||||
func WithProcurementReferenceStore(
|
||||
store ReferenceImageStore,
|
||||
) ProcurementServiceOption {
|
||||
return func(service *ProcurementService) error {
|
||||
if store == nil {
|
||||
return errors.New("procurement reference store is required")
|
||||
}
|
||||
service.store = store
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type CreateProcurementRequestCommand struct {
|
||||
CreatorSubject string
|
||||
ActorUserID string
|
||||
@@ -60,15 +76,25 @@ func NewProcurementService(
|
||||
repository ProcurementRepository,
|
||||
clock Clock,
|
||||
ids IDGenerator,
|
||||
options ...ProcurementServiceOption,
|
||||
) (*ProcurementService, error) {
|
||||
if repository == nil || clock == nil || ids == nil {
|
||||
return nil, errors.New("procurement service dependencies are required")
|
||||
}
|
||||
return &ProcurementService{
|
||||
service := &ProcurementService{
|
||||
repository: repository,
|
||||
clock: clock,
|
||||
ids: ids,
|
||||
}, nil
|
||||
}
|
||||
for _, option := range options {
|
||||
if option == nil {
|
||||
return nil, errors.New("procurement service option is required")
|
||||
}
|
||||
if err := option(service); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (service *ProcurementService) CreateRequest(
|
||||
@@ -136,13 +162,27 @@ func (service *ProcurementService) CreateRequest(
|
||||
request.Status = domain.ProcurementBlocked
|
||||
request.BlockingCode = &code
|
||||
}
|
||||
stored, created, err := service.repository.CreateProcurementRequest(
|
||||
ctx,
|
||||
request,
|
||||
)
|
||||
var auto *AutoProcurementReference
|
||||
if request.Status == domain.ProcurementNeedsImage &&
|
||||
service.store != nil {
|
||||
auto, err = service.copyFreightImageReference(ctx, request, now)
|
||||
if err != nil {
|
||||
return CreateProcurementRequestResult{}, err
|
||||
}
|
||||
}
|
||||
stored, created, referenceUsed, err :=
|
||||
service.repository.CreateProcurementRequest(
|
||||
ctx,
|
||||
request,
|
||||
auto,
|
||||
)
|
||||
if err != nil {
|
||||
service.cleanupAutomaticReference(auto)
|
||||
return CreateProcurementRequestResult{}, wrapProcurementError(err)
|
||||
}
|
||||
if !referenceUsed {
|
||||
service.cleanupAutomaticReference(auto)
|
||||
}
|
||||
return CreateProcurementRequestResult{
|
||||
Request: stored,
|
||||
Replayed: !created,
|
||||
@@ -207,19 +247,95 @@ func (service *ProcurementService) BindReference(
|
||||
fields,
|
||||
)
|
||||
}
|
||||
request, err := service.repository.BindProcurementReference(
|
||||
ctx,
|
||||
command.CreatorSubject,
|
||||
command.RequestID,
|
||||
command.ImageAssetID,
|
||||
service.clock.Now().UTC(),
|
||||
)
|
||||
request, replacedStorageKey, err :=
|
||||
service.repository.BindProcurementReference(
|
||||
ctx,
|
||||
command.CreatorSubject,
|
||||
command.RequestID,
|
||||
command.ImageAssetID,
|
||||
service.clock.Now().UTC(),
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, wrapProcurementError(err)
|
||||
}
|
||||
if replacedStorageKey != nil {
|
||||
service.deleteStoredReference(*replacedStorageKey)
|
||||
}
|
||||
return request, nil
|
||||
}
|
||||
|
||||
func (service *ProcurementService) copyFreightImageReference(
|
||||
ctx context.Context,
|
||||
request domain.ProcurementRequest,
|
||||
now time.Time,
|
||||
) (*AutoProcurementReference, error) {
|
||||
image, err := service.repository.GetReadyFreightItemImage(
|
||||
ctx,
|
||||
request.CreatorSubject,
|
||||
request.FreightOrderItemID,
|
||||
)
|
||||
if errors.Is(err, ErrRepositoryNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, wrapRepositoryError(err)
|
||||
}
|
||||
assetID, err := service.ids.NewID()
|
||||
if err != nil {
|
||||
return nil, procurementInternal(err)
|
||||
}
|
||||
content, err := service.store.Open(ctx, image.StorageKey)
|
||||
if err != nil {
|
||||
return nil, mapImageStoreError(err)
|
||||
}
|
||||
normalized, putErr := service.store.Put(
|
||||
ctx,
|
||||
assetID,
|
||||
image.MediaType,
|
||||
content,
|
||||
)
|
||||
closeErr := content.Close()
|
||||
if putErr != nil {
|
||||
return nil, mapImageStoreError(putErr)
|
||||
}
|
||||
if closeErr != nil {
|
||||
service.deleteStoredReference(normalized.StorageKey)
|
||||
return nil, procurementInternal(closeErr)
|
||||
}
|
||||
return &AutoProcurementReference{
|
||||
Asset: domain.Asset{
|
||||
ID: assetID,
|
||||
CreatorSubject: request.CreatorSubject,
|
||||
Purpose: domain.AssetPurposeTaskReference,
|
||||
MediaType: normalized.MediaType,
|
||||
SizeBytes: normalized.SizeBytes,
|
||||
SHA256: normalized.SHA256,
|
||||
StorageKey: normalized.StorageKey,
|
||||
CreatedAt: now,
|
||||
},
|
||||
ProductThumbRef: image.ProductThumbRef,
|
||||
SourceImageSHA256: image.SHA256,
|
||||
SourceImageStorageKey: image.StorageKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (service *ProcurementService) cleanupAutomaticReference(
|
||||
auto *AutoProcurementReference,
|
||||
) {
|
||||
if auto != nil {
|
||||
service.deleteStoredReference(auto.Asset.StorageKey)
|
||||
}
|
||||
}
|
||||
|
||||
func (service *ProcurementService) deleteStoredReference(storageKey string) {
|
||||
if service.store == nil || storageKey == "" {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = service.store.Delete(ctx, storageKey)
|
||||
}
|
||||
|
||||
func (service *ProcurementService) CreateTask(
|
||||
ctx context.Context,
|
||||
command CreateProcurementTaskCommand,
|
||||
|
||||
Reference in New Issue
Block a user