feat(t245): use ERP images as procurement references
This commit is contained in:
@@ -66,10 +66,12 @@ func (store *Store) GetProcurementSourceItem(
|
||||
func (store *Store) CreateProcurementRequest(
|
||||
ctx context.Context,
|
||||
candidate domain.ProcurementRequest,
|
||||
) (domain.ProcurementRequest, bool, error) {
|
||||
auto *usecase.AutoProcurementReference,
|
||||
) (domain.ProcurementRequest, bool, bool, error) {
|
||||
tx, err := store.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, false, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, false, false,
|
||||
repositoryFailure(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
existing, err := getProcurementRequestBySource(
|
||||
@@ -80,36 +82,60 @@ func (store *Store) CreateProcurementRequest(
|
||||
candidate.SourceRevision,
|
||||
)
|
||||
if err == nil {
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, false, repositoryFailure(err)
|
||||
if auto != nil &&
|
||||
existing.Status == domain.ProcurementNeedsImage {
|
||||
updated, bindErr := bindAutomaticProcurementReference(
|
||||
ctx,
|
||||
tx,
|
||||
existing,
|
||||
*auto,
|
||||
candidate.UpdatedAt,
|
||||
)
|
||||
if bindErr != nil {
|
||||
return domain.ProcurementRequest{}, false, false, bindErr
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, false, false,
|
||||
repositoryFailure(err)
|
||||
}
|
||||
return updated, false, true, nil
|
||||
}
|
||||
return existing, false, nil
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, false, false,
|
||||
repositoryFailure(err)
|
||||
}
|
||||
return existing, false, false, nil
|
||||
}
|
||||
if !errors.Is(err, usecase.ErrRepositoryNotFound) {
|
||||
return domain.ProcurementRequest{}, false, err
|
||||
return domain.ProcurementRequest{}, false, false, err
|
||||
}
|
||||
var currentRevision int
|
||||
var currentSHA string
|
||||
var present bool
|
||||
if err := tx.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT item.revision, item.canonical_sha256, item.is_present
|
||||
FROM freight_order_items AS item
|
||||
JOIN freight_orders AS freight
|
||||
ON freight.id = item.freight_order_id
|
||||
WHERE freight.creator_subject = ? AND item.id = ?`,
|
||||
candidate.CreatorSubject,
|
||||
candidate.FreightOrderItemID,
|
||||
).Scan(¤tRevision, ¤tSHA, &present); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return domain.ProcurementRequest{}, false, usecase.ErrRepositoryNotFound
|
||||
if err := validateProcurementSource(ctx, tx, candidate); err != nil {
|
||||
return domain.ProcurementRequest{}, false, false, err
|
||||
}
|
||||
referenceUsed := false
|
||||
if auto != nil {
|
||||
if candidate.Status != domain.ProcurementNeedsImage {
|
||||
return domain.ProcurementRequest{}, false, false,
|
||||
repositoryFailure(usecase.ErrRepositoryInvariant)
|
||||
}
|
||||
return domain.ProcurementRequest{}, false, repositoryFailure(err)
|
||||
}
|
||||
if !present || currentRevision != candidate.SourceRevision ||
|
||||
currentSHA != candidate.SourceSHA256 {
|
||||
return domain.ProcurementRequest{}, false,
|
||||
usecase.ErrProcurementSourceChanged
|
||||
if err := validateAutomaticProcurementReference(
|
||||
ctx,
|
||||
tx,
|
||||
candidate,
|
||||
*auto,
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, false, false, err
|
||||
}
|
||||
if err := insertProcurementReferenceAsset(
|
||||
ctx,
|
||||
tx,
|
||||
auto.Asset,
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, false, false, err
|
||||
}
|
||||
candidate.ReferenceAssetID = &auto.Asset.ID
|
||||
candidate.Status = domain.ProcurementReady
|
||||
referenceUsed = true
|
||||
}
|
||||
_, err = tx.ExecContext(
|
||||
ctx,
|
||||
@@ -120,7 +146,7 @@ func (store *Store) CreateProcurementRequest(
|
||||
procurement_confirmed_by_user_id, procurement_confirmed_at,
|
||||
status, blocking_code, reference_asset_id, purchase_task_id,
|
||||
created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, ?, ?)`,
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?)`,
|
||||
candidate.ID,
|
||||
candidate.CreatorSubject,
|
||||
candidate.FreightOrderItemID,
|
||||
@@ -136,16 +162,43 @@ func (store *Store) CreateProcurementRequest(
|
||||
formatTimestamp(candidate.ProcurementConfirmedAt),
|
||||
candidate.Status,
|
||||
nullableString(candidate.BlockingCode),
|
||||
nullableString(candidate.ReferenceAssetID),
|
||||
formatTimestamp(candidate.CreatedAt),
|
||||
formatTimestamp(candidate.UpdatedAt),
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, false, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, false, false,
|
||||
repositoryFailure(err)
|
||||
}
|
||||
if auto != nil {
|
||||
if err := saveProcurementReferenceSource(
|
||||
ctx,
|
||||
tx,
|
||||
candidate.ID,
|
||||
domain.ProcurementReferenceSource{
|
||||
Origin: domain.ProcurementReferenceERP,
|
||||
ProductThumbRef: &auto.ProductThumbRef,
|
||||
SourceImageSHA256: &auto.SourceImageSHA256,
|
||||
BoundAt: candidate.UpdatedAt,
|
||||
},
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, false, false, err
|
||||
}
|
||||
}
|
||||
stored, err := getProcurementRequest(
|
||||
ctx,
|
||||
tx,
|
||||
candidate.CreatorSubject,
|
||||
candidate.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, false, false, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, false, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, false, false,
|
||||
repositoryFailure(err)
|
||||
}
|
||||
return candidate, true, nil
|
||||
return stored, true, referenceUsed, nil
|
||||
}
|
||||
|
||||
func (store *Store) ListProcurementRequestsForOrder(
|
||||
@@ -190,10 +243,10 @@ func (store *Store) BindProcurementReference(
|
||||
ctx context.Context,
|
||||
creatorSubject, requestID, assetID string,
|
||||
now time.Time,
|
||||
) (domain.ProcurementRequest, error) {
|
||||
) (domain.ProcurementRequest, *string, error) {
|
||||
tx, err := store.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
request, err := getProcurementRequest(
|
||||
@@ -203,30 +256,33 @@ func (store *Store) BindProcurementReference(
|
||||
requestID,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
return domain.ProcurementRequest{}, nil, err
|
||||
}
|
||||
if request.SourceChanged {
|
||||
if request.PurchaseTaskID == nil {
|
||||
if err := markProcurementSourceChanged(ctx, tx, request.ID, now); err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
return domain.ProcurementRequest{}, nil, err
|
||||
}
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
return domain.ProcurementRequest{}, usecase.ErrProcurementSourceChanged
|
||||
return domain.ProcurementRequest{}, nil,
|
||||
usecase.ErrProcurementSourceChanged
|
||||
}
|
||||
if request.ReferenceAssetID != nil &&
|
||||
*request.ReferenceAssetID == assetID &&
|
||||
(request.Status == domain.ProcurementReady ||
|
||||
request.Status == domain.ProcurementTaskCreated) {
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
return request, nil
|
||||
return request, nil, nil
|
||||
}
|
||||
if request.Status != domain.ProcurementNeedsImage {
|
||||
return domain.ProcurementRequest{}, usecase.ErrProcurementStateConflict
|
||||
if request.Status != domain.ProcurementNeedsImage &&
|
||||
request.Status != domain.ProcurementReady {
|
||||
return domain.ProcurementRequest{}, nil,
|
||||
usecase.ErrProcurementStateConflict
|
||||
}
|
||||
var available int
|
||||
if err := tx.QueryRowContext(
|
||||
@@ -250,30 +306,68 @@ func (store *Store) BindProcurementReference(
|
||||
creatorSubject,
|
||||
requestID,
|
||||
).Scan(&available); err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
if available != 1 {
|
||||
return domain.ProcurementRequest{}, usecase.ErrAssetUnavailable
|
||||
return domain.ProcurementRequest{}, nil, usecase.ErrAssetUnavailable
|
||||
}
|
||||
var replacedStorageKey *string
|
||||
var replacedAssetID *string
|
||||
if request.ReferenceAssetID != nil &&
|
||||
request.ReferenceOrigin != nil &&
|
||||
*request.ReferenceOrigin == domain.ProcurementReferenceERP {
|
||||
replaced, getErr := getAssetByID(
|
||||
ctx,
|
||||
tx,
|
||||
creatorSubject,
|
||||
*request.ReferenceAssetID,
|
||||
)
|
||||
if getErr != nil {
|
||||
return domain.ProcurementRequest{}, nil, getErr
|
||||
}
|
||||
replacedStorageKey = &replaced.StorageKey
|
||||
replacedAssetID = &replaced.ID
|
||||
}
|
||||
if _, err := tx.ExecContext(
|
||||
ctx,
|
||||
`UPDATE procurement_requests
|
||||
SET reference_asset_id = ?, status = 'READY', updated_at = ?
|
||||
WHERE id = ? AND status = 'NEEDS_IMAGE'`,
|
||||
SET reference_asset_id = ?, status = 'READY', blocking_code = NULL,
|
||||
updated_at = ?
|
||||
WHERE id = ? AND status IN ('NEEDS_IMAGE', 'READY')`,
|
||||
assetID,
|
||||
formatTimestamp(now),
|
||||
requestID,
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
if err := saveProcurementReferenceSource(
|
||||
ctx,
|
||||
tx,
|
||||
requestID,
|
||||
domain.ProcurementReferenceSource{
|
||||
Origin: domain.ProcurementReferenceManual,
|
||||
BoundAt: now,
|
||||
},
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, nil, err
|
||||
}
|
||||
if replacedAssetID != nil {
|
||||
if _, err := tx.ExecContext(
|
||||
ctx,
|
||||
`DELETE FROM assets WHERE id = ?`,
|
||||
*replacedAssetID,
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
}
|
||||
updated, err := getProcurementRequest(ctx, tx, creatorSubject, requestID)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
return domain.ProcurementRequest{}, nil, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
return domain.ProcurementRequest{}, nil, repositoryFailure(err)
|
||||
}
|
||||
return updated, nil
|
||||
return updated, replacedStorageKey, nil
|
||||
}
|
||||
|
||||
func (store *Store) CreateProcurementTask(
|
||||
@@ -488,6 +582,8 @@ const procurementRequestSelect = `SELECT
|
||||
request.procurement_confirmed_by_user_id,
|
||||
request.procurement_confirmed_at, request.status,
|
||||
request.blocking_code, request.reference_asset_id,
|
||||
reference_source.origin, reference_source.product_thumb_ref,
|
||||
reference_source.source_image_sha256, reference_source.bound_at,
|
||||
request.purchase_task_id, request.created_at, request.updated_at,
|
||||
CASE
|
||||
WHEN item.revision != request.source_revision
|
||||
@@ -502,6 +598,8 @@ const procurementRequestSelect = `SELECT
|
||||
ON item.id = request.freight_order_item_id
|
||||
JOIN freight_orders AS freight
|
||||
ON freight.id = item.freight_order_id
|
||||
LEFT JOIN procurement_reference_sources AS reference_source
|
||||
ON reference_source.procurement_request_id = request.id
|
||||
`
|
||||
|
||||
func getProcurementRequest(
|
||||
@@ -557,7 +655,9 @@ func scanProcurementRequest(
|
||||
var quantity sql.NullInt64
|
||||
var purchaseStatus sql.NullString
|
||||
var isCanceled sql.NullBool
|
||||
var blockingCode, assetID, taskID sql.NullString
|
||||
var blockingCode, assetID, referenceOrigin sql.NullString
|
||||
var referenceThumb, referenceSHA, referenceBoundAt sql.NullString
|
||||
var taskID sql.NullString
|
||||
var confirmedAt, createdAt, updatedAt string
|
||||
var sourceChanged bool
|
||||
err := scanner.Scan(
|
||||
@@ -577,6 +677,10 @@ func scanProcurementRequest(
|
||||
&request.Status,
|
||||
&blockingCode,
|
||||
&assetID,
|
||||
&referenceOrigin,
|
||||
&referenceThumb,
|
||||
&referenceSHA,
|
||||
&referenceBoundAt,
|
||||
&taskID,
|
||||
&createdAt,
|
||||
&updatedAt,
|
||||
@@ -595,6 +699,19 @@ func scanProcurementRequest(
|
||||
}
|
||||
request.BlockingCode = optionalString(blockingCode)
|
||||
request.ReferenceAssetID = optionalString(assetID)
|
||||
if referenceOrigin.Valid {
|
||||
origin := domain.ProcurementReferenceOrigin(referenceOrigin.String)
|
||||
request.ReferenceOrigin = &origin
|
||||
}
|
||||
request.ReferenceProductThumbRef = optionalString(referenceThumb)
|
||||
request.ReferenceSourceImageSHA256 = optionalString(referenceSHA)
|
||||
if referenceBoundAt.Valid {
|
||||
boundAt, parseErr := parseTimestamp(referenceBoundAt.String)
|
||||
if parseErr != nil {
|
||||
return domain.ProcurementRequest{}, parseErr
|
||||
}
|
||||
request.ReferenceBoundAt = &boundAt
|
||||
}
|
||||
request.PurchaseTaskID = optionalString(taskID)
|
||||
request.SourceChanged = sourceChanged
|
||||
request.ProcurementConfirmedAt, err = parseTimestamp(confirmedAt)
|
||||
@@ -609,6 +726,204 @@ func scanProcurementRequest(
|
||||
return request, err
|
||||
}
|
||||
|
||||
func validateProcurementSource(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
candidate domain.ProcurementRequest,
|
||||
) error {
|
||||
var revision int
|
||||
var sourceSHA string
|
||||
var present bool
|
||||
err := tx.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT item.revision, item.canonical_sha256, item.is_present
|
||||
FROM freight_order_items AS item
|
||||
JOIN freight_orders AS freight
|
||||
ON freight.id = item.freight_order_id
|
||||
WHERE freight.creator_subject = ? AND item.id = ?`,
|
||||
candidate.CreatorSubject,
|
||||
candidate.FreightOrderItemID,
|
||||
).Scan(&revision, &sourceSHA, &present)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return usecase.ErrRepositoryNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return repositoryFailure(err)
|
||||
}
|
||||
if !present || revision != candidate.SourceRevision ||
|
||||
sourceSHA != candidate.SourceSHA256 {
|
||||
return usecase.ErrProcurementSourceChanged
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateAutomaticProcurementReference(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
candidate domain.ProcurementRequest,
|
||||
auto usecase.AutoProcurementReference,
|
||||
) error {
|
||||
if auto.Asset.CreatorSubject != candidate.CreatorSubject ||
|
||||
auto.Asset.Purpose != domain.AssetPurposeTaskReference ||
|
||||
auto.Asset.MediaType != domain.NormalizedImageMediaType ||
|
||||
auto.Asset.ID == "" || auto.Asset.StorageKey == "" ||
|
||||
auto.Asset.SizeBytes <= 0 || len(auto.Asset.SHA256) != 64 {
|
||||
return usecase.ErrRepositoryInvariant
|
||||
}
|
||||
var thumbRef, sourceSHA, storageKey string
|
||||
err := tx.QueryRowContext(
|
||||
ctx,
|
||||
`SELECT image.product_thumb_ref, image.sha256, image.storage_key
|
||||
FROM freight_item_images AS image
|
||||
JOIN freight_order_items AS item
|
||||
ON item.id = image.freight_order_item_id
|
||||
JOIN freight_orders AS freight
|
||||
ON freight.id = item.freight_order_id
|
||||
WHERE freight.creator_subject = ?
|
||||
AND item.id = ?
|
||||
AND item.is_present = 1
|
||||
AND item.product_thumb_ref = image.product_thumb_ref
|
||||
AND image.status = 'READY'`,
|
||||
candidate.CreatorSubject,
|
||||
candidate.FreightOrderItemID,
|
||||
).Scan(&thumbRef, &sourceSHA, &storageKey)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return usecase.ErrProcurementSourceChanged
|
||||
}
|
||||
if err != nil {
|
||||
return repositoryFailure(err)
|
||||
}
|
||||
if thumbRef != auto.ProductThumbRef ||
|
||||
sourceSHA != auto.SourceImageSHA256 ||
|
||||
storageKey != auto.SourceImageStorageKey {
|
||||
return usecase.ErrProcurementSourceChanged
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertProcurementReferenceAsset(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
asset domain.Asset,
|
||||
) error {
|
||||
_, err := tx.ExecContext(
|
||||
ctx,
|
||||
`INSERT INTO assets (
|
||||
id, creator_subject, purpose, media_type, size_bytes,
|
||||
sha256, storage_key, created_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
asset.ID,
|
||||
asset.CreatorSubject,
|
||||
asset.Purpose,
|
||||
asset.MediaType,
|
||||
asset.SizeBytes,
|
||||
asset.SHA256,
|
||||
asset.StorageKey,
|
||||
formatTimestamp(asset.CreatedAt),
|
||||
)
|
||||
if err != nil {
|
||||
return repositoryFailure(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveProcurementReferenceSource(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
requestID string,
|
||||
source domain.ProcurementReferenceSource,
|
||||
) error {
|
||||
_, err := tx.ExecContext(
|
||||
ctx,
|
||||
`INSERT INTO procurement_reference_sources (
|
||||
procurement_request_id, origin, product_thumb_ref,
|
||||
source_image_sha256, bound_at
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(procurement_request_id) DO UPDATE SET
|
||||
origin = excluded.origin,
|
||||
product_thumb_ref = excluded.product_thumb_ref,
|
||||
source_image_sha256 = excluded.source_image_sha256,
|
||||
bound_at = excluded.bound_at`,
|
||||
requestID,
|
||||
source.Origin,
|
||||
nullableString(source.ProductThumbRef),
|
||||
nullableString(source.SourceImageSHA256),
|
||||
formatTimestamp(source.BoundAt),
|
||||
)
|
||||
if err != nil {
|
||||
return repositoryFailure(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindAutomaticProcurementReference(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
request domain.ProcurementRequest,
|
||||
auto usecase.AutoProcurementReference,
|
||||
now time.Time,
|
||||
) (domain.ProcurementRequest, error) {
|
||||
if request.SourceChanged {
|
||||
return domain.ProcurementRequest{},
|
||||
usecase.ErrProcurementSourceChanged
|
||||
}
|
||||
if err := validateProcurementSource(ctx, tx, request); err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
}
|
||||
if err := validateAutomaticProcurementReference(
|
||||
ctx,
|
||||
tx,
|
||||
request,
|
||||
auto,
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
}
|
||||
if err := insertProcurementReferenceAsset(ctx, tx, auto.Asset); err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
}
|
||||
result, err := tx.ExecContext(
|
||||
ctx,
|
||||
`UPDATE procurement_requests
|
||||
SET reference_asset_id = ?, status = 'READY',
|
||||
blocking_code = NULL, updated_at = ?
|
||||
WHERE id = ? AND status = 'NEEDS_IMAGE'
|
||||
AND reference_asset_id IS NULL`,
|
||||
auto.Asset.ID,
|
||||
formatTimestamp(now),
|
||||
request.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
}
|
||||
affected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return domain.ProcurementRequest{}, repositoryFailure(err)
|
||||
}
|
||||
if affected != 1 {
|
||||
return domain.ProcurementRequest{},
|
||||
usecase.ErrProcurementStateConflict
|
||||
}
|
||||
if err := saveProcurementReferenceSource(
|
||||
ctx,
|
||||
tx,
|
||||
request.ID,
|
||||
domain.ProcurementReferenceSource{
|
||||
Origin: domain.ProcurementReferenceERP,
|
||||
ProductThumbRef: &auto.ProductThumbRef,
|
||||
SourceImageSHA256: &auto.SourceImageSHA256,
|
||||
BoundAt: now,
|
||||
},
|
||||
); err != nil {
|
||||
return domain.ProcurementRequest{}, err
|
||||
}
|
||||
return getProcurementRequest(
|
||||
ctx,
|
||||
tx,
|
||||
request.CreatorSubject,
|
||||
request.ID,
|
||||
)
|
||||
}
|
||||
|
||||
func markProcurementSourceChanged(
|
||||
ctx context.Context,
|
||||
tx *sql.Tx,
|
||||
|
||||
Reference in New Issue
Block a user