diff --git a/src/apps/material/material.dev.tsx b/src/apps/material/material.dev.tsx index b3d933e242..8fcba4ea9a 100644 --- a/src/apps/material/material.dev.tsx +++ b/src/apps/material/material.dev.tsx @@ -811,6 +811,101 @@ export default { defaultValue: '[\n {\n "value":"30",\n "label":"1 month"\n },\n {\n "value":"60",\n "label":"2 months"\n },\n {\n "value":"90",\n "label":"3 months"\n },\n {\n "value":"180",\n "label":"6 months"\n },\n {\n "value":"360",\n "label":"1 year"\n }\n]', control: { type: "text" } + }, + openOrderResponseTitleText: { + name: "Reservation Success title", + defaultValue: "Order from another library:", + control: { type: "text" } + }, + openOrderResponseIsReservedForYouText: { + name: "Reservation Success Title", + defaultValue: "is ordered to your library", + control: { type: "text" } + }, + openOrderAuthenticationErrorText: { + name: "Open order authentication error text", + defaultValue: "Authentication error occurred", + control: { type: "text" } + }, + openOrderUserBlockedByAgencyText: { + name: "Open order user blocked by agency text", + defaultValue: "You are blocked by the agency", + control: { type: "text" } + }, + openOrderUserNotVerifiedText: { + name: "Open order user not verified text", + defaultValue: "User could not be verified", + control: { type: "text" } + }, + openOrderUserNoLongerExistOnAgencyText: { + name: "Open order user no longer exists on agency text", + defaultValue: "User no longer exists at the specified agency", + control: { type: "text" } + }, + openOrderInvalidOrderText: { + name: "Open order invalid order text", + defaultValue: "Your order is invalid", + control: { type: "text" } + }, + openOrderNotOwnedIllLocText: { + name: "Open order item localized for ILL text", + defaultValue: "Item not available at pickup agency but localized for ILL", + control: { type: "text" } + }, + openOrderNotOwnedNoIllLocText: { + name: "Open order item not localized for ILL text", + defaultValue: "Item not available and not localized for ILL", + control: { type: "text" } + }, + openOrderNotOwnedWrongIllMediumtypeText: { + name: "Open order wrong ILL medium type text", + defaultValue: "Item not available, ILL of this medium type not accepted", + control: { type: "text" } + }, + openOrderNoServicerequesterText: { + name: "Open order no service requester text", + defaultValue: "Service requester is obligatory", + control: { type: "text" } + }, + openOrderOrsErrorText: { + name: "Open order ORS error text", + defaultValue: "Error occurred while sending order to ORS", + control: { type: "text" } + }, + openOrderStatusOwnedAcceptedText: { + name: "Open order status owned accepted text", + defaultValue: "Your order is accepted", + control: { type: "text" } + }, + openOrderOwnedOwnCatalogueText: { + name: "Open order available in own catalogue text", + defaultValue: "Item available, order through the library's catalogue", + control: { type: "text" } + }, + openOrderOwnedWrongMediumtypeText: { + name: "Open order wrong medium type for available item text", + defaultValue: "Item available but medium type not accepted", + control: { type: "text" } + }, + openOrderServiceUnavailableText: { + name: "Open order service unavailable text", + defaultValue: "Service is currently unavailable", + control: { type: "text" } + }, + openOrderUnknownErrorText: { + name: "Open order unknown error text", + defaultValue: "An unknown error occurred", + control: { type: "text" } + }, + openOrderUnknownPickupagencyText: { + name: "Open order unknown pickup agency text", + defaultValue: "Specified pickup agency not found", + control: { type: "text" } + }, + openOrderUnknownUserText: { + name: "Open order unknown user text", + defaultValue: "User not found", + control: { type: "text" } } } } as ComponentMeta; @@ -877,3 +972,8 @@ export const Underverden = Template.bind({}); Underverden.args = { wid: "work-of:870970-basis:52886619" }; + +export const overbygningsMatriale = Template.bind({}); +overbygningsMatriale.args = { + wid: "work-of:870970-basis:135721719" +}; diff --git a/src/apps/material/material.entry.tsx b/src/apps/material/material.entry.tsx index dcb99bed37..383c916696 100644 --- a/src/apps/material/material.entry.tsx +++ b/src/apps/material/material.entry.tsx @@ -109,6 +109,25 @@ interface MaterialEntryTextProps { okButtonText: string; onlineLimitMonthAudiobookInfoText: string; onlineLimitMonthEbookInfoText: string; + openOrderAuthenticationErrorText: string; + openOrderInvalidOrderText: string; + openOrderNoServicerequesterText: string; + openOrderNotOwnedIllLocText: string; + openOrderNotOwnedNoIllLocText: string; + openOrderNotOwnedWrongIllMediumtypeText: string; + openOrderOrsErrorText: string; + openOrderOwnedOwnCatalogueText: string; + openOrderOwnedWrongMediumtypeText: string; + openOrderResponseIsReservedForYouText: string; + openOrderResponseTitleText: string; + openOrderServiceUnavailableText: string; + openOrderStatusOwnedAcceptedText: string; + openOrderUnknownErrorText: string; + openOrderUnknownPickupagencyText: string; + openOrderUnknownUserText: string; + openOrderUserBlockedByAgencyText: string; + openOrderUserNoLongerExistOnAgencyText: string; + openOrderUserNotVerifiedText: string; orderDigitalCopyButtonLoadingText: string; orderDigitalCopyButtonText: string; orderDigitalCopyDescriptionText: string; diff --git a/src/components/material/material-buttons/physical/MaterialButtonsPhysical.tsx b/src/components/material/material-buttons/physical/MaterialButtonsPhysical.tsx index 14923cda20..ba29dba559 100644 --- a/src/components/material/material-buttons/physical/MaterialButtonsPhysical.tsx +++ b/src/components/material/material-buttons/physical/MaterialButtonsPhysical.tsx @@ -2,7 +2,7 @@ import React from "react"; import { getAllFaustIds, getManifestationType, - isReservableOnAnotherLibrary + getReservableOnAnotherLibrary } from "../../../../core/utils/helpers/general"; import { isBlocked } from "../../../../core/utils/helpers/user"; import { ButtonSize } from "../../../../core/utils/types/button"; @@ -27,8 +27,8 @@ const MaterialButtonsPhysical: React.FC = ({ size, dataCy = "material-buttons-physical" }) => { - const reservableOnAnotherLibrary = - isReservableOnAnotherLibrary(manifestations); + const { isReservable: isReservableOnAnotherLibrar } = + getReservableOnAnotherLibrary(manifestations); const t = useText(); const faustIds = getAllFaustIds(manifestations); const { reservableManifestations } = UseReservableManifestations({ @@ -49,12 +49,12 @@ const MaterialButtonsPhysical: React.FC = ({ return ; } - if (reservableOnAnotherLibrary) { + if (isReservableOnAnotherLibrar) { return ( ); } diff --git a/src/components/reservation/OpenOrderResponse.tsx b/src/components/reservation/OpenOrderResponse.tsx new file mode 100644 index 0000000000..0b65c4b1af --- /dev/null +++ b/src/components/reservation/OpenOrderResponse.tsx @@ -0,0 +1,66 @@ +import FocusTrap from "focus-trap-react"; +import React from "react"; +import { useDispatch } from "react-redux"; +import { closeModal } from "../../core/modal.slice"; +import { useText } from "../../core/utils/text"; +import { Button } from "../Buttons/Button"; +import { OpenOrderMutation } from "../../core/dbc-gateway/generated/graphql"; +import { translateOpenOrderStatus } from "./helper"; + +type OpenOrderResponseProps = { + title: string; + modalId: string; + openOrderResponse: OpenOrderMutation; +}; + +const OpenOrderResponse: React.FC = ({ + modalId, + title, + openOrderResponse +}) => { + const dispatch = useDispatch(); + const t = useText(); + + return ( + +
+

+ {t("openOrderResponseTitleText")} +

+ +

+ {title} {t("openOrderResponseIsReservedForYouText")} +

+ + {openOrderResponse.submitOrder?.status && ( +

+ {translateOpenOrderStatus(openOrderResponse.submitOrder?.status, t)} +

+ )} + +
+
+ ); +}; + +export default OpenOrderResponse; diff --git a/src/components/reservation/ReservationModalBody.tsx b/src/components/reservation/ReservationModalBody.tsx index 496ab4f0f3..c6e144583f 100644 --- a/src/components/reservation/ReservationModalBody.tsx +++ b/src/components/reservation/ReservationModalBody.tsx @@ -6,7 +6,8 @@ import { getAllPids, getMaterialTypes, getManifestationType, - materialIsFiction + materialIsFiction, + getReservableOnAnotherLibrary } from "../../core/utils/helpers/general"; import { useText } from "../../core/utils/text"; import { Button } from "../Buttons/Button"; @@ -39,7 +40,9 @@ import { getAuthorLine, getManifestationsToReserve, getInstantLoanBranchHoldings, - getInstantLoanBranchHoldingsAboveThreshold + getInstantLoanBranchHoldingsAboveThreshold, + removeDKFromBranchId, + getPatronAddress } from "./helper"; import UseReservableManifestations from "../../core/utils/UseReservableManifestations"; import { PeriodicalEdition } from "../material/periodical/helper"; @@ -54,6 +57,11 @@ import InstantLoan from "../instant-loan/InstantLoan"; import { excludeBlacklistedBranches } from "../../core/utils/branches"; import { InstantLoanConfigType } from "../../core/utils/types/instant-loan"; import { usePatronData } from "../material/helper"; +import { + OpenOrderMutation, + useOpenOrderMutation +} from "../../core/dbc-gateway/generated/graphql"; +import OpenOrderResponse from "./OpenOrderResponse"; type ReservationModalProps = { selectedManifestations: Manifestation[]; @@ -95,11 +103,14 @@ export const ReservationModalBody = ({ const queryClient = useQueryClient(); const [reservationResponse, setReservationResponse] = useState(null); + const [openOrderResponse, setOpenOrderResponse] = + useState(null); const [selectedBranch, setSelectedBranch] = useState(null); const [selectedInterest, setSelectedInterest] = useState(null); const allPids = getAllPids(selectedManifestations); const faustIds = convertPostIdsToFaustIds(allPids); - const { mutate } = useAddReservationsV2(); + const { mutate: mutateAddReservations } = useAddReservationsV2(); + const { mutate: mutateOpenOrder } = useOpenOrderMutation(); const userResponse = usePatronData(); const holdingsResponse = useGetHoldings({ faustIds, config }); const { track } = useStatistics(); @@ -128,12 +139,45 @@ export const ReservationModalBody = ({ ? getFutureDateString(selectedInterest) : null; + const { + isReservable: isReservableOnAnotherLibrar, + pids: pidsOnAnotherLibrar + } = getReservableOnAnotherLibrary(manifestationsToReserve); + const saveReservation = () => { if (!manifestationsToReserve || manifestationsToReserve.length < 1) { return; } + + if (isReservableOnAnotherLibrar && patron) { + const { preferredPickupBranch, patronId, address, name, emailAddress } = + patron; + + mutateOpenOrder( + { + input: { + pids: [...pidsOnAnotherLibrar], + pickUpBranch: removeDKFromBranchId(preferredPickupBranch), + userParameters: { + userId: patronId.toString(), + userAddress: address ? getPatronAddress(address) : "", + userName: name, + userMail: emailAddress + } + } + }, + { + onSuccess: (res) => { + setOpenOrderResponse(res); + } + } + ); + + return; + } + // Save reservation to FBS. - mutate( + mutateAddReservations( { data: constructReservationData({ manifestations: manifestationsToReserve, @@ -184,7 +228,7 @@ export const ReservationModalBody = ({ return ( <> - {!reservationResults && ( + {!reservationResults && !openOrderResponse && (
@@ -266,6 +310,13 @@ export const ReservationModalBody = ({
)} + {openOrderResponse && ( + + )} {reservationSuccess && reservationDetails && ( configValue === "1"; @@ -251,4 +253,55 @@ export const getInstantLoanBranchHoldingsAboveThreshold = ( ({ materials }) => materials.length >= Number(instantLoanThresholdConfig) ); +export const removeDKFromBranchId = (branchId: string) => { + return branchId.slice(3); +}; + +export const getPatronAddress = (address: AddressV2) => + `${address.street}, ${address.postalCode} ${address.city}`; + +export const translateOpenOrderStatus = ( + status: SubmitOrderStatus, + t: UseTextFunction +) => { + switch (status) { + case SubmitOrderStatus.OwnedAccepted: + return t("openOrderStatusOwnedAcceptedText"); + case SubmitOrderStatus.AuthenticationError: + return t("openOrderAuthenticationErrorText"); + case SubmitOrderStatus.BorchkUserBlockedByAgency: + return t("openOrderUserBlockedByAgencyText"); + case SubmitOrderStatus.BorchkUserNotVerified: + return t("openOrderUserNotVerifiedText"); + case SubmitOrderStatus.BorchkUserNoLongerExistOnAgency: + return t("openOrderUserNoLongerExistOnAgencyText"); + case SubmitOrderStatus.InvalidOrder: + return t("openOrderInvalidOrderText"); + case SubmitOrderStatus.NotOwnedIllLoc: + return t("openOrderNotOwnedIllLocText"); + case SubmitOrderStatus.NotOwnedNoIllLoc: + return t("openOrderNotOwnedNoIllLocText"); + case SubmitOrderStatus.NotOwnedWrongIllMediumtype: + return t("openOrderNotOwnedWrongIllMediumtypeText"); + case SubmitOrderStatus.NoServicerequester: + return t("openOrderNoServicerequesterText"); + case SubmitOrderStatus.OrsError: + return t("openOrderOrsErrorText"); + case SubmitOrderStatus.OwnedOwnCatalogue: + return t("openOrderOwnedOwnCatalogueText"); + case SubmitOrderStatus.OwnedWrongMediumtype: + return t("openOrderOwnedWrongMediumtypeText"); + case SubmitOrderStatus.ServiceUnavailable: + return t("openOrderServiceUnavailableText"); + case SubmitOrderStatus.UnknownError: + return t("openOrderUnknownErrorText"); + case SubmitOrderStatus.UnknownPickupagency: + return t("openOrderUnknownPickupagencyText"); + case SubmitOrderStatus.UnknownUser: + return t("openOrderUnknownUserText"); + default: + return ""; + } +}; + export default {}; diff --git a/src/core/utils/helpers/general.ts b/src/core/utils/helpers/general.ts index 88b7b24b95..d03fa0a1c2 100644 --- a/src/core/utils/helpers/general.ts +++ b/src/core/utils/helpers/general.ts @@ -562,8 +562,18 @@ export const getContributors = (short: boolean, creators: string[]) => { return creators[0]; }; -export const isReservableOnAnotherLibrary = (manifestation: Manifestation[]) => - true; +export const getReservableOnAnotherLibrary = ( + manifestations: Manifestation[] +) => { + const matchingManifestations = manifestations.filter(({ catalogueCodes }) => + catalogueCodes?.otherCatalogues.some((code) => code.startsWith("OVE")) + ); + + return { + isReservable: matchingManifestations.length > 0, + pids: matchingManifestations.map(({ pid }) => pid) + }; +}; export default {}; @@ -571,6 +581,70 @@ export default {}; if (import.meta.vitest) { const { describe, expect, it } = import.meta.vitest; + describe("getReservableOnAnotherLibrary", () => { + it("should return true for isReservable and correct pids if manifestations are reservable on another library (catalogueCodes starts with 'OVE')", () => { + const result = getReservableOnAnotherLibrary([ + { + pid: "870970-basis:135721719", + catalogueCodes: { + otherCatalogues: ["OVE123"], + nationalBibliography: ["ABE123"] + } + }, + { + pid: "870970-basis:135721719", + catalogueCodes: { + otherCatalogues: ["OVE124"], + nationalBibliography: ["ABE456"] + } + } + ] as unknown as Manifestation[]); + + expect(result.isReservable).toBe(true); + expect(result.pids).toEqual([ + "870970-basis:135721719", + "870970-basis:135721719" + ]); + }); + + it("should return false for isReservable and empty pids array if no manifestations are reservable on another library", () => { + const result = getReservableOnAnotherLibrary([ + { + pid: "pid1", + catalogueCodes: { + otherCatalogues: ["ABE789"], + nationalBibliography: ["ABE123"] + } + } + ] as unknown as Manifestation[]); + + expect(result.isReservable).toBe(false); + expect(result.pids).toEqual([]); + }); + + it("should filter out non-reservable manifestations and return only reservable pids", () => { + const result = getReservableOnAnotherLibrary([ + { + pid: "870970-basis:135721719", + catalogueCodes: { + otherCatalogues: ["OVE123"], + nationalBibliography: ["ABE123"] + } + }, + { + pid: "870970-basis:111111111", + catalogueCodes: { + otherCatalogues: ["ABE789"], + nationalBibliography: ["ABE456"] + } + } + ] as unknown as Manifestation[]); + + expect(result.isReservable).toBe(true); + expect(result.pids).toEqual(["870970-basis:135721719"]); + }); + }); + describe("getMaterialTypes", () => { const manifestations = [ {