diff --git a/broker/common/common.go b/broker/common/common.go index cd60018c..f8c6ba7e 100644 --- a/broker/common/common.go +++ b/broker/common/common.go @@ -40,6 +40,14 @@ func StructToMap(obj any) (map[string]any, error) { return result, nil } +func MapToStruct(obj map[string]any, v any) error { + b, err := json.Marshal(obj) + if err != nil { + return err + } + return json.Unmarshal(b, v) +} + func UnpackItemsNote(note string) ([][]string, int, int) { startIdx := strings.Index(note, MULTIPLE_ITEMS) endIdx := strings.Index(note, MULTIPLE_ITEMS_END) diff --git a/broker/common/common_test.go b/broker/common/common_test.go index cfaab9db..d8021778 100644 --- a/broker/common/common_test.go +++ b/broker/common/common_test.go @@ -85,6 +85,46 @@ func TestStructToMap(t *testing.T) { } } +func TestMapToStruct(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + want User + wantErr bool + }{ + { + name: "Basic map conversion", + input: map[string]interface{}{ + "id": float64(1), + "name": "Alice", + "Active": true, + }, + want: User{ID: 1, Name: &alice, Active: true}, + wantErr: false, + }, + { + name: "42 as string instead of int", + input: map[string]interface{}{"id": "42"}, + want: User{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got User + err := MapToStruct(tt.input, &got) + if (err != nil) != tt.wantErr { + t.Errorf("MapToStruct() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("MapToStruct() got = %v, want %v", got, tt.want) + } + }) + } +} + func TestUnpackItemsNote(t *testing.T) { // Just ID note := MULTIPLE_ITEMS + "\n1\n" + MULTIPLE_ITEMS_END diff --git a/broker/patron_request/service/action.go b/broker/patron_request/service/action.go index 4e31a652..e4c5c003 100644 --- a/broker/patron_request/service/action.go +++ b/broker/patron_request/service/action.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "net/http" + "strconv" "strings" "time" @@ -46,6 +47,14 @@ func (e *autoActionFailure) Error() string { return e.msg } +type ActionParams struct { + Note string `json:"note,omitempty"` + LoanCondition string `json:"loanCondition,omitempty"` + Cost *float64 `json:"cost,omitempty"` + Currency string `json:"currency,omitempty"` + ReasonUnfilled string `json:"reasonUnfilled,omitempty"` +} + func CreatePatronRequestActionService(prRepo pr_db.PrRepo, eventBus events.EventBus, iso18626Handler handler.Iso18626HandlerInterface, lmsCreator lms.LmsCreator) *PatronRequestActionService { return &PatronRequestActionService{ prRepo: prRepo, @@ -267,7 +276,7 @@ func (a *PatronRequestActionService) handleBorrowingAction(ctx common.ExtendedCo } } -func (a *PatronRequestActionService) handleLenderAction(ctx common.ExtendedContext, action pr_db.PatronRequestAction, pr pr_db.PatronRequest, illRequest iso18626.Request, actionParams map[string]interface{}, eventID *string) actionExecutionResult { +func (a *PatronRequestActionService) handleLenderAction(ctx common.ExtendedContext, action pr_db.PatronRequestAction, pr pr_db.PatronRequest, illRequest iso18626.Request, actionCustomData map[string]any, eventID *string) actionExecutionResult { if !pr.SupplierSymbol.Valid { status, result := a.logErrorAndReturnResult(ctx, "missing supplier symbol", nil) return actionExecutionResult{status: status, result: result, pr: pr} @@ -291,19 +300,26 @@ func (a *PatronRequestActionService) handleLenderAction(ctx common.ExtendedConte ctx.Logger().Error("failed to create LMS log event", "error", createErr) } }) + + var actionParams ActionParams + err = common.MapToStruct(actionCustomData, &actionParams) + if err != nil { + status, result := a.logErrorAndReturnResult(ctx, "failed to unmarshal action parameters", err) + return actionExecutionResult{status: status, result: result, pr: pr} + } switch action { case LenderActionValidate: return a.validateLenderRequest(ctx, pr, lms) case LenderActionWillSupply: - return a.willSupplyLenderRequest(ctx, pr, lms, illRequest) + return a.willSupplyLenderRequest(ctx, pr, lms, illRequest, actionParams) case LenderActionRejectCancel: return a.rejectCancelLenderRequest(ctx, pr) case LenderActionCannotSupply: - return a.cannotSupplyLenderRequest(ctx, pr) + return a.cannotSupplyLenderRequest(ctx, pr, actionParams) case LenderActionAddCondition: return a.addConditionsLenderRequest(ctx, pr, actionParams) case LenderActionShip: - return a.shipLenderRequest(ctx, pr, lms, illRequest) + return a.shipLenderRequest(ctx, pr, lms, illRequest, actionParams) case LenderActionMarkReceived: return a.markReceivedLenderRequest(ctx, pr, lms, illRequest) case LenderActionAcceptCancel: @@ -597,7 +613,7 @@ func (a *PatronRequestActionService) validateLenderRequest(ctx common.ExtendedCo return actionExecutionResult{status: events.EventStatusSuccess, pr: pr} } -func (a *PatronRequestActionService) willSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request) actionExecutionResult { +func (a *PatronRequestActionService) willSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request, actionParams ActionParams) actionExecutionResult { itemId := illRequest.BibliographicInfo.SupplierUniqueRecordId requestId := illRequest.Header.RequestingAgencyRequestId userId := lmsAdapter.InstitutionalPatron(pr.RequesterSymbol.String) @@ -625,28 +641,65 @@ func (a *PatronRequestActionService) willSupplyLenderRequest(ctx common.Extended return actionExecutionResult{status: status, result: result, pr: pr} } result := events.EventResult{} - status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, iso18626.MessageInfo{ReasonForMessage: iso18626.TypeReasonForMessageStatusChange}, iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}) + status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, + iso18626.MessageInfo{ + ReasonForMessage: iso18626.TypeReasonForMessageStatusChange, + Note: actionParams.Note, + }, + iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}, + nil) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } -func (a *PatronRequestActionService) cannotSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest) actionExecutionResult { +func (a *PatronRequestActionService) cannotSupplyLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, actionParams ActionParams) actionExecutionResult { result := events.EventResult{} - status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, iso18626.MessageInfo{ReasonForMessage: iso18626.TypeReasonForMessageStatusChange}, iso18626.StatusInfo{Status: iso18626.TypeStatusUnfilled}) + var reasonUnfilled *iso18626.TypeSchemeValuePair + if actionParams.ReasonUnfilled != "" { + reasonUnfilled = &iso18626.TypeSchemeValuePair{Text: actionParams.ReasonUnfilled} + } + status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, + iso18626.MessageInfo{ + ReasonForMessage: iso18626.TypeReasonForMessageStatusChange, + Note: actionParams.Note, + ReasonUnfilled: reasonUnfilled, + }, + iso18626.StatusInfo{Status: iso18626.TypeStatusUnfilled}, + nil) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } -func (a *PatronRequestActionService) addConditionsLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, actionParams map[string]interface{}) actionExecutionResult { +func (a *PatronRequestActionService) addConditionsLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, actionParams ActionParams) actionExecutionResult { + var offeredCosts *iso18626.TypeCosts + if actionParams.Cost != nil { + if actionParams.Currency == "" { + status, result := a.logErrorAndReturnResult(ctx, "currency is required when cost is provided", nil) + return actionExecutionResult{status: status, result: result, pr: pr} + } + _, costBase, costExp := utils.ExtractDecimal(strconv.FormatFloat(*actionParams.Cost, 'f', -1, 64), -1) + offeredCosts = &iso18626.TypeCosts{ + CurrencyCode: iso18626.TypeSchemeValuePair{Text: actionParams.Currency}, + MonetaryValue: utils.XSDDecimal{Base: costBase, Exp: costExp}, + } + } + var deliveryInfo *iso18626.DeliveryInfo + if actionParams.LoanCondition != "" { + deliveryInfo = &iso18626.DeliveryInfo{ + LoanCondition: &iso18626.TypeSchemeValuePair{Text: actionParams.LoanCondition}, + } + } result := events.EventResult{} status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, iso18626.MessageInfo{ ReasonForMessage: iso18626.TypeReasonForMessageNotification, - Note: shim.RESHARE_ADD_LOAN_CONDITION, // TODO add action params + Note: actionParams.Note + shim.RESHARE_ADD_LOAN_CONDITION, + OfferedCosts: offeredCosts, }, - iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}) + iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}, + deliveryInfo) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } -func (a *PatronRequestActionService) shipLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request) actionExecutionResult { +func (a *PatronRequestActionService) shipLenderRequest(ctx common.ExtendedContext, pr pr_db.PatronRequest, lmsAdapter lms.LmsAdapter, illRequest iso18626.Request, actionParams ActionParams) actionExecutionResult { requestId := illRequest.Header.RequestingAgencyRequestId userId := lmsAdapter.InstitutionalPatron(pr.RequesterSymbol.String) externalReferenceValue := "" @@ -680,11 +733,15 @@ func (a *PatronRequestActionService) shipLenderRequest(ctx common.ExtendedContex } } } - note := encodeItemsNote(items) + note := actionParams.Note + encodeItemsNote(items) result := events.EventResult{} status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, - iso18626.MessageInfo{ReasonForMessage: iso18626.TypeReasonForMessageStatusChange, Note: note}, - iso18626.StatusInfo{Status: iso18626.TypeStatusLoaned}) + iso18626.MessageInfo{ + ReasonForMessage: iso18626.TypeReasonForMessageStatusChange, + Note: note, + }, + iso18626.StatusInfo{Status: iso18626.TypeStatusLoaned}, + nil) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } @@ -718,7 +775,11 @@ func (a *PatronRequestActionService) markReceivedLenderRequest(ctx common.Extend } } result := events.EventResult{} - status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, iso18626.MessageInfo{ReasonForMessage: iso18626.TypeReasonForMessageStatusChange}, iso18626.StatusInfo{Status: iso18626.TypeStatusLoanCompleted}) + status, eventResult, httpStatus := a.sendSupplyingAgencyMessage(ctx, pr, &result, + iso18626.MessageInfo{ + ReasonForMessage: iso18626.TypeReasonForMessageStatusChange}, + iso18626.StatusInfo{Status: iso18626.TypeStatusLoanCompleted}, + nil) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } @@ -730,7 +791,8 @@ func (a *PatronRequestActionService) rejectCancelLenderRequest(ctx common.Extend ReasonForMessage: iso18626.TypeReasonForMessageCancelResponse, AnswerYesNo: &no, }, - iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}) + iso18626.StatusInfo{Status: iso18626.TypeStatusWillSupply}, + nil) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } @@ -742,11 +804,12 @@ func (a *PatronRequestActionService) acceptCancelLenderRequest(ctx common.Extend ReasonForMessage: iso18626.TypeReasonForMessageCancelResponse, AnswerYesNo: &yes, }, - iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled}) + iso18626.StatusInfo{Status: iso18626.TypeStatusCancelled}, + nil) return a.checkSupplyingResponse(status, eventResult, &result, httpStatus, pr) } -func (a *PatronRequestActionService) sendSupplyingAgencyMessage(ctx common.ExtendedContext, pr pr_db.PatronRequest, result *events.EventResult, messageInfo iso18626.MessageInfo, statusInfo iso18626.StatusInfo) (events.EventStatus, *events.EventResult, *int) { +func (a *PatronRequestActionService) sendSupplyingAgencyMessage(ctx common.ExtendedContext, pr pr_db.PatronRequest, result *events.EventResult, messageInfo iso18626.MessageInfo, statusInfo iso18626.StatusInfo, deliveryInfo *iso18626.DeliveryInfo) (events.EventStatus, *events.EventResult, *int) { if !pr.RequesterSymbol.Valid { status, eventResult := a.logErrorAndReturnResult(ctx, "missing requester symbol", nil) return status, eventResult, nil @@ -773,8 +836,9 @@ func (a *PatronRequestActionService) sendSupplyingAgencyMessage(ctx common.Exten RequestingAgencyRequestId: pr.RequesterReqID.String, SupplyingAgencyRequestId: pr.ID, }, - MessageInfo: messageInfo, - StatusInfo: statusInfo, + MessageInfo: messageInfo, + StatusInfo: statusInfo, + DeliveryInfo: deliveryInfo, }, } if illMessage.SupplyingAgencyMessage.StatusInfo.LastChange.IsZero() { diff --git a/broker/patron_request/service/action_test.go b/broker/patron_request/service/action_test.go index c8f8bf4d..24c361d3 100644 --- a/broker/patron_request/service/action_test.go +++ b/broker/patron_request/service/action_test.go @@ -841,6 +841,13 @@ func TestHandleInvokeLenderActionWillSupplyUseIllTitleWhenRequestItemEmptyOK(t * assert.Equal(t, "1", mockPrRepo.savedItems[0].Barcode) assert.Equal(t, "2", mockPrRepo.savedItems[0].CallNumber.String) assert.Equal(t, "title1", mockPrRepo.savedItems[0].Title.String) + + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { + assert.Equal(t, iso18626.TypeStatusWillSupply, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) + } } func TestHandleInvokeLenderActionWillSupplyUseRequestItemTitleWhenAvailableOK(t *testing.T) { @@ -854,7 +861,12 @@ func TestHandleInvokeLenderActionWillSupplyUseRequestItemTitleWhenAvailableOK(t illRequest := iso18626.Request{BibliographicInfo: iso18626.BibliographicInfo{Title: "title1"}} mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{IllRequest: illRequest, State: LenderStateValidated, Side: SideLending, SupplierSymbol: getDbText("ISIL:SUP1"), RequesterSymbol: getDbText("ISIL:REQ1")}, nil) action := LenderActionWillSupply - status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &action}}}) + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + CustomData: map[string]any{ + "note": "my note", + }, + }}) assert.Equal(t, events.EventStatusSuccess, status) assert.NotNil(t, resultData) @@ -863,6 +875,13 @@ func TestHandleInvokeLenderActionWillSupplyUseRequestItemTitleWhenAvailableOK(t assert.Equal(t, "1", mockPrRepo.savedItems[0].Barcode) assert.Equal(t, "2", mockPrRepo.savedItems[0].CallNumber.String) assert.Equal(t, "title2", mockPrRepo.savedItems[0].Title.String) + + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { + assert.Equal(t, iso18626.TypeStatusWillSupply, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "my note", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) + } } func TestHandleInvokeLenderActionRejectCancel(t *testing.T) { @@ -943,14 +962,56 @@ func TestHandleInvokeLenderActionCannotSupply(t *testing.T) { illRequest := iso18626.Request{} mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{IllRequest: illRequest, State: LenderStateValidated, Side: SideLending, SupplierSymbol: getDbText("ISIL:SUP1"), RequesterSymbol: getDbText("ISIL:REQ1")}, nil) action := LenderActionCannotSupply - status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &action}}}) + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + }}) + + assert.Equal(t, events.EventStatusSuccess, status) + assert.NotNil(t, resultData) + assert.Equal(t, LenderStateUnfilled, mockPrRepo.savedPr.State) + + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { + assert.Equal(t, iso18626.TypeStatusUnfilled, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.ReasonUnfilled) + } +} + +func TestHandleInvokeLenderActionCannotSupplyWithReason(t *testing.T) { + mockPrRepo := new(MockPrRepo) + lmsCreator := new(MockLmsCreator) + lmsCreator.On("GetAdapter", "ISIL:SUP1").Return(lms.CreateLmsAdapterMockOK(), nil) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(events.EventBus), mockIso18626Handler, lmsCreator) + illRequest := iso18626.Request{} + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{IllRequest: illRequest, State: LenderStateValidated, Side: SideLending, SupplierSymbol: getDbText("ISIL:SUP1"), RequesterSymbol: getDbText("ISIL:REQ1")}, nil) + action := LenderActionCannotSupply + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + CustomData: map[string]any{ + "note": "my note", + "reasonUnfilled": "my reason", + }, + }}) assert.Equal(t, events.EventStatusSuccess, status) assert.NotNil(t, resultData) assert.Equal(t, LenderStateUnfilled, mockPrRepo.savedPr.State) + + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { + assert.Equal(t, iso18626.TypeStatusUnfilled, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "my note", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.ReasonUnfilled) { + assert.Equal(t, "my reason", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.ReasonUnfilled.Text) + } + } } -func TestHandleInvokeLenderActionAddCondition(t *testing.T) { +func TestHandleInvokeLenderActionAddConditionOK(t *testing.T) { mockPrRepo := new(MockPrRepo) lmsCreator := new(MockLmsCreator) lmsCreator.On("GetAdapter", "ISIL:SUP1").Return(lms.CreateLmsAdapterMockOK(), nil) @@ -960,11 +1021,81 @@ func TestHandleInvokeLenderActionAddCondition(t *testing.T) { mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{IllRequest: illRequest, State: LenderStateValidated, Side: SideLending, SupplierSymbol: getDbText("ISIL:SUP1"), RequesterSymbol: getDbText("ISIL:REQ1")}, nil) action := LenderActionAddCondition - status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &action}}}) + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + }}) + assert.Equal(t, events.EventStatusSuccess, status) + assert.NotNil(t, resultData) + assert.Equal(t, LenderStateConditionPending, mockPrRepo.savedPr.State) + + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { + assert.Equal(t, iso18626.TypeStatusWillSupply, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "#ReShareAddLoanCondition#", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) + } +} + +func TestHandleInvokeLenderActionAddConditionWithCurrency(t *testing.T) { + mockPrRepo := new(MockPrRepo) + lmsCreator := new(MockLmsCreator) + lmsCreator.On("GetAdapter", "ISIL:SUP1").Return(lms.CreateLmsAdapterMockOK(), nil) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(events.EventBus), mockIso18626Handler, lmsCreator) + illRequest := iso18626.Request{} + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{IllRequest: illRequest, State: LenderStateValidated, Side: SideLending, SupplierSymbol: getDbText("ISIL:SUP1"), RequesterSymbol: getDbText("ISIL:REQ1")}, nil) + action := LenderActionAddCondition + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + CustomData: map[string]any{ + "loanCondition": "my condition", + "note": "Condition note", + "cost": 12.34, + "currency": "DKK", + }, + }}) assert.Equal(t, events.EventStatusSuccess, status) assert.NotNil(t, resultData) assert.Equal(t, LenderStateConditionPending, mockPrRepo.savedPr.State) + + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { + assert.Equal(t, iso18626.TypeStatusWillSupply, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "Condition note#ReShareAddLoanCondition#", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts) { + assert.Equal(t, 1234, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts.MonetaryValue.Base) + assert.Equal(t, 2, mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts.MonetaryValue.Exp) + assert.Equal(t, "DKK", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.OfferedCosts.CurrencyCode.Text) + } + if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) { + assert.Equal(t, "my condition", mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo.LoanCondition.Text) + } + } +} + +func TestHandleInvokeLenderActionAddConditionMissingCurrency(t *testing.T) { + mockPrRepo := new(MockPrRepo) + lmsCreator := new(MockLmsCreator) + lmsCreator.On("GetAdapter", "ISIL:SUP1").Return(lms.CreateLmsAdapterMockOK(), nil) + mockIso18626Handler := new(MockIso18626Handler) + prAction := CreatePatronRequestActionService(mockPrRepo, *new(events.EventBus), mockIso18626Handler, lmsCreator) + illRequest := iso18626.Request{} + mockPrRepo.On("GetPatronRequestById", patronRequestId).Return(pr_db.PatronRequest{IllRequest: illRequest, State: LenderStateValidated, Side: SideLending, SupplierSymbol: getDbText("ISIL:SUP1"), RequesterSymbol: getDbText("ISIL:REQ1")}, nil) + action := LenderActionAddCondition + + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + CustomData: map[string]any{ + "loanCondition": "my condition", + "note": "Condition note", + "cost": 12.34, + }, + }}) + assert.Equal(t, events.EventStatusError, status) + assert.NotNil(t, resultData) + assert.Equal(t, LenderStateValidated, mockPrRepo.savedPr.State) + assert.Equal(t, "currency is required when cost is provided", resultData.EventError.Message) + assert.Nil(t, mockIso18626Handler.lastSupplyingAgencyMessage) } func TestHandleInvokeLenderActionShipOK(t *testing.T) { @@ -994,13 +1125,19 @@ func TestHandleInvokeLenderActionShipOK(t *testing.T) { action := LenderActionShip - status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{CommonEventData: events.CommonEventData{Action: &action}}}) + status, resultData := prAction.handleInvokeAction(appCtx, events.Event{PatronRequestID: patronRequestId, EventData: events.EventData{ + CommonEventData: events.CommonEventData{Action: &action}, + CustomData: map[string]any{ + "note": "my note", + }, + }}) assert.Equal(t, events.EventStatusSuccess, status) assert.NotNil(t, resultData) assert.Equal(t, LenderStateShipped, mockPrRepo.savedPr.State) assert.Len(t, mockPrRepo.savedItems, 0) if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage) { assert.Equal(t, iso18626.TypeStatusLoaned, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.Status) + assert.Equal(t, "my note#MultipleItems#\n1234||\n5678||\n#MultipleItemsEnd#", mockIso18626Handler.lastSupplyingAgencyMessage.MessageInfo.Note) assert.False(t, mockIso18626Handler.lastSupplyingAgencyMessage.StatusInfo.LastChange.IsZero()) if assert.NotNil(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo) { assert.False(t, mockIso18626Handler.lastSupplyingAgencyMessage.DeliveryInfo.DateSent.IsZero())