feat(t245): use ERP images as procurement references
This commit is contained in:
@@ -766,7 +766,11 @@ func TestAdminProcurementAPIProducesImmutablePendingTask(t *testing.T) {
|
||||
"",
|
||||
)
|
||||
if createRequest.Code != http.StatusCreated ||
|
||||
!strings.Contains(createRequest.Body.String(), `"status":"NEEDS_IMAGE"`) {
|
||||
!strings.Contains(createRequest.Body.String(), `"status":"READY"`) ||
|
||||
!strings.Contains(
|
||||
createRequest.Body.String(),
|
||||
`"reference_origin":"ERP_FREIGHT_IMAGE"`,
|
||||
) {
|
||||
t.Fatalf(
|
||||
"create request status/body = %d / %s",
|
||||
createRequest.Code,
|
||||
@@ -806,7 +810,11 @@ func TestAdminProcurementAPIProducesImmutablePendingTask(t *testing.T) {
|
||||
"",
|
||||
)
|
||||
if bind.Code != http.StatusOK ||
|
||||
!strings.Contains(bind.Body.String(), `"status":"READY"`) {
|
||||
!strings.Contains(bind.Body.String(), `"status":"READY"`) ||
|
||||
!strings.Contains(
|
||||
bind.Body.String(),
|
||||
`"reference_origin":"MANUAL_UPLOAD"`,
|
||||
) {
|
||||
t.Fatalf("bind status/body = %d / %s", bind.Code, bind.Body)
|
||||
}
|
||||
deleteWithReference := performAdminRequest(
|
||||
@@ -920,7 +928,7 @@ func TestAdminProcurementAPIProducesImmutablePendingTask(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAdminFreightDeleteRemovesNeedsImageDraft(t *testing.T) {
|
||||
fixture := newAdminIntegrationFixture(t)
|
||||
fixture := newAdminIntegrationFixtureWithReferences(t, false)
|
||||
createSync := performAdminRequest(
|
||||
t,
|
||||
fixture.router,
|
||||
@@ -1123,6 +1131,9 @@ func TestAdminOrderAuthorizationIsIdempotentAndRevisioned(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("migration.New() error = %v", err)
|
||||
}
|
||||
if err := runner.Down(context.Background()); err != nil {
|
||||
t.Fatalf("reference source migration down: %v", err)
|
||||
}
|
||||
if err := runner.Down(context.Background()); err != nil {
|
||||
t.Fatalf("image migration down: %v", err)
|
||||
}
|
||||
@@ -1406,6 +1417,13 @@ type adminIntegrationFixture struct {
|
||||
}
|
||||
|
||||
func newAdminIntegrationFixture(t *testing.T) *adminIntegrationFixture {
|
||||
return newAdminIntegrationFixtureWithReferences(t, true)
|
||||
}
|
||||
|
||||
func newAdminIntegrationFixtureWithReferences(
|
||||
t *testing.T,
|
||||
autoReferences bool,
|
||||
) *adminIntegrationFixture {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
db, err := database.Open(ctx, filepath.Join(t.TempDir(), "admin.db"))
|
||||
@@ -1482,10 +1500,18 @@ func newAdminIntegrationFixture(t *testing.T) *adminIntegrationFixture {
|
||||
if err != nil {
|
||||
t.Fatalf("usecase.NewFreightService() error = %v", err)
|
||||
}
|
||||
procurementOptions := make([]usecase.ProcurementServiceOption, 0, 1)
|
||||
if autoReferences {
|
||||
procurementOptions = append(
|
||||
procurementOptions,
|
||||
usecase.WithProcurementReferenceStore(files),
|
||||
)
|
||||
}
|
||||
procurement, err := usecase.NewProcurementService(
|
||||
repositories,
|
||||
clock,
|
||||
ids,
|
||||
procurementOptions...,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("usecase.NewProcurementService() error = %v", err)
|
||||
|
||||
@@ -713,6 +713,9 @@ func TestDeviceExecutionResultsAreIdempotentAndAuditable(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("migration.New() after review error = %v", err)
|
||||
}
|
||||
if err := runner.Down(context.Background()); err != nil {
|
||||
t.Fatalf("reference source migration down: %v", err)
|
||||
}
|
||||
if err := runner.Down(context.Background()); err != nil {
|
||||
t.Fatalf("image migration down: %v", err)
|
||||
}
|
||||
@@ -742,8 +745,8 @@ func TestDeviceExecutionResultsAreIdempotentAndAuditable(t *testing.T) {
|
||||
}
|
||||
if applied, err := runner.Up(context.Background()); err != nil {
|
||||
t.Fatalf("restore device command migration: %v", err)
|
||||
} else if applied != 8 {
|
||||
t.Fatalf("restored migrations = %d, want 8", applied)
|
||||
} else if applied != 9 {
|
||||
t.Fatalf("restored migrations = %d, want 9", applied)
|
||||
}
|
||||
|
||||
completePayload := fmt.Sprintf(
|
||||
@@ -1432,6 +1435,9 @@ func TestDeviceOrderCommandDeliveryAndAcknowledgementAreRecoverable(
|
||||
if err != nil {
|
||||
t.Fatalf("migration.New() error = %v", err)
|
||||
}
|
||||
if err := runner.Down(context.Background()); err != nil {
|
||||
t.Fatalf("reference source migration down: %v", err)
|
||||
}
|
||||
if err := runner.Down(context.Background()); err != nil {
|
||||
t.Fatalf("image migration down: %v", err)
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ func procurementRequestResponse(
|
||||
"status": request.Status,
|
||||
"blocking_code": request.BlockingCode,
|
||||
"reference_asset_id": request.ReferenceAssetID,
|
||||
"reference_origin": request.ReferenceOrigin,
|
||||
"purchase_task_id": request.PurchaseTaskID,
|
||||
"source_changed": request.SourceChanged,
|
||||
"created_at": formatTime(request.CreatedAt),
|
||||
|
||||
@@ -1239,10 +1239,12 @@ func TestFreightDetailCreatesProcurementTaskWithCSRF(t *testing.T) {
|
||||
ImageURL: "/api/v1/freight-items/" + itemID + "/image",
|
||||
},
|
||||
Request: &ProcurementRequest{
|
||||
ID: itemID,
|
||||
FreightOrderItemID: itemID,
|
||||
Status: "READY",
|
||||
StatusLabel: "可以生成任务",
|
||||
ID: itemID,
|
||||
FreightOrderItemID: itemID,
|
||||
Status: "READY",
|
||||
StatusLabel: "可以生成任务",
|
||||
ReferenceOrigin: "ERP_FREIGHT_IMAGE",
|
||||
ReferenceOriginLabel: "参考图来自 ERP 商品图",
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -1263,6 +1265,8 @@ func TestFreightDetailCreatesProcurementTaskWithCSRF(t *testing.T) {
|
||||
)
|
||||
if detail.Code != http.StatusOK ||
|
||||
!strings.Contains(detail.Body.String(), "生成采购任务") ||
|
||||
!strings.Contains(detail.Body.String(), "更换参考图") ||
|
||||
!strings.Contains(detail.Body.String(), "参考图来自 ERP 商品图") ||
|
||||
!strings.Contains(detail.Body.String(), "可以生成任务") ||
|
||||
!strings.Contains(detail.Body.String(), "TWD 129.50") ||
|
||||
!strings.Contains(detail.Body.String(), `width="84" height="84"`) ||
|
||||
@@ -1273,6 +1277,13 @@ func TestFreightDetailCreatesProcurementTaskWithCSRF(t *testing.T) {
|
||||
}
|
||||
cookie := csrfCookie(t, detail)
|
||||
taskKey := hiddenValue(t, detail.Body.String(), "task_key")
|
||||
if uploadKey := hiddenValue(
|
||||
t,
|
||||
detail.Body.String(),
|
||||
"upload_key",
|
||||
); !validToken(uploadKey) {
|
||||
t.Fatalf("upload key = %q", uploadKey)
|
||||
}
|
||||
values := url.Values{
|
||||
"csrf_token": {cookie.Value},
|
||||
"order_id": {testTaskID},
|
||||
|
||||
@@ -103,6 +103,7 @@
|
||||
<button class="button" type="submit">上传参考图</button>
|
||||
</form>
|
||||
{{else if eq .Request.Status "READY"}}
|
||||
{{if .Request.ReferenceOriginLabel}}<span class="secondary">{{.Request.ReferenceOriginLabel}}</span>{{end}}
|
||||
<form method="post"
|
||||
action="/freight/procurement-requests/{{pathPart .Request.ID}}/purchase-task">
|
||||
<input type="hidden" name="csrf_token" value="{{$.Page.CSRFToken}}">
|
||||
@@ -110,6 +111,17 @@
|
||||
<input type="hidden" name="task_key" value="{{.Request.TaskKey}}">
|
||||
<button class="button primary" type="submit">生成采购任务</button>
|
||||
</form>
|
||||
<form method="post" enctype="multipart/form-data"
|
||||
action="/freight/procurement-requests/{{pathPart .Request.ID}}/reference">
|
||||
<input type="hidden" name="csrf_token" value="{{$.Page.CSRFToken}}">
|
||||
<input type="hidden" name="order_id" value="{{$.Detail.Order.ID}}">
|
||||
<input type="hidden" name="upload_key" value="{{.Request.UploadKey}}">
|
||||
<label>
|
||||
<span class="visually-hidden">更换参考图片</span>
|
||||
<input name="image" type="file" accept="image/jpeg,image/png,image/webp" required>
|
||||
</label>
|
||||
<button class="button" type="submit">更换参考图</button>
|
||||
</form>
|
||||
{{else if .Request.PurchaseTaskID}}
|
||||
<a class="detail-link" href="/tasks/{{pathPart .Request.PurchaseTaskID}}">查看采购任务</a>
|
||||
{{end}}
|
||||
|
||||
@@ -192,18 +192,20 @@ type FreightItemReview struct {
|
||||
}
|
||||
|
||||
type ProcurementRequest struct {
|
||||
ID string
|
||||
FreightOrderItemID string
|
||||
SourceRevision int
|
||||
Status string
|
||||
StatusLabel string
|
||||
BlockingCode string
|
||||
BlockingLabel string
|
||||
ReferenceAssetID string
|
||||
PurchaseTaskID string
|
||||
SourceChanged bool
|
||||
UploadKey string
|
||||
TaskKey string
|
||||
ID string
|
||||
FreightOrderItemID string
|
||||
SourceRevision int
|
||||
Status string
|
||||
StatusLabel string
|
||||
BlockingCode string
|
||||
BlockingLabel string
|
||||
ReferenceAssetID string
|
||||
ReferenceOrigin string
|
||||
ReferenceOriginLabel string
|
||||
PurchaseTaskID string
|
||||
SourceChanged bool
|
||||
UploadKey string
|
||||
TaskKey string
|
||||
}
|
||||
|
||||
type ListTasksInput struct {
|
||||
|
||||
@@ -292,9 +292,11 @@ func procurementRequestFrom(
|
||||
BlockingLabel: procurementBlockingLabel(
|
||||
stringValue(request.BlockingCode),
|
||||
),
|
||||
ReferenceAssetID: stringValue(request.ReferenceAssetID),
|
||||
PurchaseTaskID: stringValue(request.PurchaseTaskID),
|
||||
SourceChanged: request.SourceChanged,
|
||||
ReferenceAssetID: stringValue(request.ReferenceAssetID),
|
||||
ReferenceOrigin: procurementReferenceOrigin(request),
|
||||
ReferenceOriginLabel: procurementReferenceOriginLabel(request),
|
||||
PurchaseTaskID: stringValue(request.PurchaseTaskID),
|
||||
SourceChanged: request.SourceChanged,
|
||||
}
|
||||
if request.SourceChanged {
|
||||
result.StatusLabel = "来源已变化"
|
||||
@@ -302,6 +304,28 @@ func procurementRequestFrom(
|
||||
return result
|
||||
}
|
||||
|
||||
func procurementReferenceOrigin(
|
||||
request domain.ProcurementRequest,
|
||||
) string {
|
||||
if request.ReferenceOrigin == nil {
|
||||
return ""
|
||||
}
|
||||
return string(*request.ReferenceOrigin)
|
||||
}
|
||||
|
||||
func procurementReferenceOriginLabel(
|
||||
request domain.ProcurementRequest,
|
||||
) string {
|
||||
switch procurementReferenceOrigin(request) {
|
||||
case string(domain.ProcurementReferenceERP):
|
||||
return "参考图来自 ERP 商品图"
|
||||
case string(domain.ProcurementReferenceManual):
|
||||
return "参考图由人工上传"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func procurementStatusLabel(
|
||||
status domain.ProcurementRequestStatus,
|
||||
) string {
|
||||
|
||||
Reference in New Issue
Block a user