Skip to end of banner
Go to start of banner

Import Orders in MARC format

Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 85 Next »

AuthorOlamide Kolawole/Serhii Nosko
JIRA task

Folijet: UXPROD-185 - Getting issue details... STATUS  

Thunderjet Support: TBD 

Business Requirements
Architects Review

IN PROGRESS

PO review

IN PROGRESS

Revision History

VersionDateOwnerDescriptionReason
v1.006.26.2022Initial version
v1.106.28.2022Add model schemas
v1.206.30.2022Add diagrams for Inventory creation
v1.306.30.2022Add diagrams for creating and opening orders
v1.407.07.2022Formatting changes
v1.508.07.2022Serhii_Nosko Add receving API

Overview

This document will illustrate the design that will integrate the Orders module area with Data Import(DI) module area. Order lines will be defined in MARC Bibliographic(MARC Bib) files by a user. The MARC Bib files will be parsed by DI and have records sent to mod-orders via Kafka eventing.

Solution Design

Architectural Requirements

  • Dependency Inversion: Unlike traditional integrations that have been implemented with Inventory and less so with Invoices, Orders will be a consumer of Data Import. Data Import will be responsible for parsing input files from a user into a form that FOLIO modules will understand to Create/Update their respective objects.
  • Single Source of Truth: This design should ensure that business logic only lives in one location within the FOLIO system. Orders and Data Import should not have copies/variants of the same functionality. This will reduce maintenance issues, clearly designate responsibilities and reduce cognitive load when developing the Orders and Data Import sub-systems.
  • Clearly Defined Input & Outputs of Components: Each component in the process flow should have clearly defined inputs and outputs. The cardinality of inputs and outputs of each component should be generally less than three. If the cardinality is more than three, then division of the component should be considered.

Process Flows

Create Inventory Instance, Holdings, Item Within Order Create/Update

The basic question here, do we need to create Instance, Holdings, Item by DI in mod-inventory using full power of MARC file and Order mapping profile to map Instance, Holdings, Item, or use standard mapping capabilities provided by mod-orders. The screenshot below is of the Orders UI showing where the "Create Inventory" action is selected.

Below is an enumeration of two approaches that could be used to support creation of Inventory objects for Orders.

Approach #1 with creating Inventory instance, holdings, item in mod-inventory by DI

 Click here to expand...

Benefits of this approach:

  • Inventory Instance, Holdings, Item can be mapped separately from mod-orders. In this case Orders field mapping profile can be adjusted with new Inventory field to map easily. Mod-orders creates Inventory from predefined set of fields from template on Create Orders UI and does not satisfy powerful mapping capabilities, that possible if mapping from the marc file
  • Instance create post processing logic invoked in the same manner as in common DI flow to connect Inventory instance with source marc record

Drawbacks of this approach:

  • More amount of work needed to create Inventory instance, holding, item, because not using approach from the box when mod-orders create these entities automatically during openning of the order
  • New story for mod-orders needed to support new parameter: inventoryFlow with allowed values[Synchronized, Independent]. In this approach this param value should be set to Independent

Creating of Inventory Instance, Holdings, Item from Order basically is optional step, it's possible the library is ordering something that they do not want represented in Inventory (like a database that they maybe want to handle through the ERM app), or other random things that they do not plan to keep in their permanent collection.

The diagram below shows scenario when user chooses to create Instance and/or Holdings and/or Item on the Orders field mapping profile. 

The diagram below shows a scenario where users chooses to NOT create Instance and/or Holdings and/or Item on the Orders field mapping profile. 

Since for order mapping we use single mapping profile - we don't need separate topics for each Inventory type. So when DI_SRS_MARC_BIB_RECORD_CREATED event comes, data-import-processing-core will chose new CreateOrdersInventoryDataHandler based on action profile Create Orders.

CreateOrdersInventoryDataHandler will invoke Inventory Storage REST APIs to create Instance, Holdings, Item one by one based on Orders mapping profile configuration

PlantUML Diagram

Sequence diagram can be edited using this tool: https://www.planttext.com/

Source code for sequence diagram
@startuml
!pragma teoz true
!theme cerulean

skinparam backgroundColor white
autonumber "<b>[0]"

actor User as user
participant "mod-data-import" as mdi
participant "mod-srm" as srm
participant "mod-srs" as srs
participant "mod-inventory" as inv
participant "mod-inventory-storage" as invs
participant "mod-orders" as ord

user -> srm: create job definition (profile, type)
activate srm
  srm -> srm: create job
  srm --> user: jobId
deactivate srm

user -> mdi: upload MARC file and jobId
activate mdi
  mdi -> srm: start DI initialization <<DI_INITIALIZATION_STARTED>>
  srm -> srm: start job progress
  mdi -> mdi: split to chunks, store in memory
  mdi -> srm: export chunks to <<DI_RAW_RECORDS_CHUNK_READ>>
  return
deactivate mdi

activate srm
  srm -> srm: persist source chunks records
  srm -> srs: parse chunks to <<DI_RAW_RECORDS_CHUNK_PARSED>>
deactivate srm

activate srs
  srs -> srs: persist to database
  srs -> srm: <<DI_PARSED_RECORDS_CHUNK_SAVED>>
deactivate srs

activate srm
  srm -> srm: create JSON payload (profile, parsed MARC, mapping parameters)
deactivate srm

alt Order Mapping Profile has INVENTORY creation
  srm -> inv: <<DI_SRS_MARC_BIB_RECORD_CREATED>>

  activate inv
    inv -> invs: OKAPI: create INSTANCE
    activate invs
      invs -> invs: persist to database
      invs --> inv: result
    deactivate invs
    inv -> srs: <<DI_INVENTORY_INSTANCE_CREATED_READY_FOR_POST_PROCESSING>>
  deactivate inv
  
  activate srs
    srs -> srs: Instance HRID is set to '001' MARC_BIB field,\nthe value from '001' is moved to '035',\nInstance ID is set to '999 ff i' field
    srs -> inv: <<DI_SRS_MARC_BIB_INSTANCE_HRID_SET>>
  deactivate srs 
  
  activate inv
    inv -> invs: update INSTANCE
    activate invs
      invs -> invs: persist to DB
      invs --> inv: result
    deactivate invs
    inv -> invs: OKAPI: create HOLDING
    activate invs
      invs -> invs: persist to DB
      invs --> inv: result
    deactivate invs
    inv -> invs: OKAPI: create ITEMS
    activate invs
      invs -> invs: persist to DB
      invs --> inv: result
    deactivate invs
    inv -> ord: <<DI_ORDER_READY_TO_CREATE>>
  deactivate inv
else
  srm -> ord: <<DI_ORDER_READY_TO_CREATE>>
end      

Link to the standard flow of creating Instance, Holdings, Item in DI: 1. Create MARC Bib, create Instance, Holdings, Item


Approach #2 with creating Inventory instance, holdings, item in mod-inventory by mod-orders

 Click here to expand...

Benefits of this approach:

  • Easy to implement, just use functionality to create Instance, Holdings, Item by mod-orders from the box. Dont need to implement any new functionality to create Inventory entities by DI. It can be suitable if it's enought mapping capabilities for creating Instance, Holdings, Item provided by UI Create Orders screen. If we are not going to add new fields to Orders mapping profile than Create orders UI has - in this case this approach is a good candidate.

Drawbacks of this approach:

  • This approach not allows to use powerful mapping capabilities, that possible when creating Instance, Holdings, Item from marc file. So if new fields on the Order mapping profile would be added - this requires changes in mod-orders implementation

Example of Instance created by mod-orders:

Instance created from mod-orders
{
   "source":"FOLIO",
   "title":"Nod",
   "editions":[
      ""
   ],
   "statusId":"daf2681c-25af-4202-a3fa-e58fdf806183",
   "instanceTypeId":"30fffe0e-e985-4144-b2e2-1e8179bdb41f",
   "publication":[
      {
         "publisher":"",
         "dateOfPublication":""
      }
   ],
   "contributors":[
      {
         "contributorNameTypeId":"2b94c631-fca9-4892-a730-03ee529ffe2a",
         "name":"Barnes, Adrian"
      }
   ],
   "identifiers":[
      {
         "identifierTypeId":"8261054f-be78-422d-bd51-4ed9f33c3422",
         "value":"978-3-16-148410-0"
      }
   ]
}

Java code to build Instance from mod-orders:

Java code to create instance
public JsonObject buildInstanceRecordJsonObject(CompositePoLine compPOL, JsonObject lookupObj) {
    JsonObject instance = new JsonObject();

    // MODORDERS-145 The Source and source code are required by schema
    instance.put(INSTANCE_SOURCE, SOURCE_FOLIO);
    instance.put(INSTANCE_TITLE, compPOL.getTitleOrPackage());

    if (compPOL.getEdition() != null) {
      instance.put(INSTANCE_EDITIONS, new JsonArray(singletonList(compPOL.getEdition())));
    }
    instance.put(INSTANCE_STATUS_ID, lookupObj.getString(INSTANCE_STATUSES));
    instance.put(INSTANCE_TYPE_ID, lookupObj.getString(INSTANCE_TYPES));

    if (compPOL.getPublisher() != null || compPOL.getPublicationDate() != null) {
      JsonObject publication = new JsonObject();
      publication.put(INSTANCE_PUBLISHER, compPOL.getPublisher());
      publication.put(INSTANCE_DATE_OF_PUBLICATION, compPOL.getPublicationDate());
      instance.put(INSTANCE_PUBLICATION, new JsonArray(singletonList(publication)));
    }

    if (isNotEmpty(compPOL.getContributors())) {
      List<JsonObject> contributors = compPOL.getContributors().stream().map(compPolContributor -> {
        JsonObject invContributor = new JsonObject();
        invContributor.put(CONTRIBUTOR_NAME_TYPE_ID, compPolContributor.getContributorNameTypeId());
        invContributor.put(CONTRIBUTOR_NAME, compPolContributor.getContributor());
        return invContributor;
      }).collect(toList());
      instance.put(INSTANCE_CONTRIBUTORS, contributors);
    }

    if (isProductIdsExist(compPOL)) {
      List<JsonObject> identifiers =
        compPOL.getDetails()
          .getProductIds()
          .stream()
          .map(pId -> {
            JsonObject identifier = new JsonObject();
            identifier.put(INSTANCE_IDENTIFIER_TYPE_ID, pId.getProductIdType());
            identifier.put(INSTANCE_IDENTIFIER_TYPE_VALUE, pId.getProductId());
            return identifier;
          })
          .collect(toList());
      instance.put(INSTANCE_IDENTIFIERS, new JsonArray(identifiers));
    }
    return instance;
  } 

Example of Holdings created by mod-orders:

Holdings created from mod-orders
{
   "instanceId":"d72102d6-093d-41e7-841f-2842c153e669",
   "permanentLocationId":"53cf956f-c1df-410b-8bea-27f712cca7c0"
}

Java code to build Holdings from mod-orders:

Java code to create holdings
  private <T> CompletableFuture<T> createHoldingsRecord(String instanceId, String locationId, PostResponseType responseType,
                                                        Class<T> clazz, RequestContext requestContext) {
    return getSourceId(requestContext)
      .thenCompose(sourceId -> {
        JsonObject holdingsRecJson = new JsonObject();
        holdingsRecJson.put(HOLDING_INSTANCE_ID, instanceId);
        holdingsRecJson.put(HOLDING_PERMANENT_LOCATION_ID, locationId);
        holdingsRecJson.put(HOLDING_SOURCE, sourceId);
        RequestEntry requestEntry = new RequestEntry(INVENTORY_LOOKUP_ENDPOINTS.get(HOLDINGS_RECORDS));
        return restClient.post(requestEntry, holdingsRecJson, responseType, clazz, requestContext);
      });
  }

Example of Item created by mod-orders

Item created from mod-orders
{
   "holdingsRecordId":"ae5eb95b-03f7-4ea4-ba4d-f8600343613a",
   "status":{
      "name":"On order"
   },
   "permanentLoanTypeId":"2b94c631-fca9-4892-a730-03ee529ffe27",
   "purchaseOrderLineIdentifier":"d0ce84ab-5b62-4b78-8459-c4bc222ec33b",
   "materialTypeId":"1a54b431-2e4f-452d-9cae-9cee66c9a892"
}

Java code to build Item from mod-orders:

Java code to create item
/**
 * Builds JsonObject representing inventory item minimal data. The schema is located directly in 'mod-inventory-storage' module.
 *
 * @param compPOL   PO line to create Item Records for
 * @param holdingId holding uuid from the inventory
 * @return item data to be used as request body for POST operation
 */
private CompletableFuture<JsonObject> buildBaseItemRecordJsonObject(CompositePoLine compPOL, String holdingId, RequestContext requestContext) {
  return getLoanTypeId(requestContext)
    .thenApply(loanTypeId -> {
      JsonObject itemRecord = new JsonObject();
      itemRecord.put(ITEM_HOLDINGS_RECORD_ID, holdingId);
      itemRecord.put(ITEM_STATUS, new JsonObject().put(ITEM_STATUS_NAME, ReceivedItem.ItemStatus.ON_ORDER.value()));
      itemRecord.put(ITEM_PERMANENT_LOAN_TYPE_ID, loanTypeId);
      itemRecord.put(ITEM_PURCHASE_ORDER_LINE_IDENTIFIER, compPOL.getId());
      return itemRecord;
    });
}

private void updateItemWithPieceFields(Piece piece, JsonObject item) {
    Optional.ofNullable(piece.getEnumeration())
      .ifPresentOrElse(enumeration -> item.put(ITEM_ENUMERATION, enumeration), () -> item.remove(ITEM_ENUMERATION));
    Optional.ofNullable(piece.getChronology())
      .ifPresentOrElse(chronology -> item.put(ITEM_CHRONOLOGY, chronology), () -> item.remove(ITEM_CHRONOLOGY));
    Optional.ofNullable(piece.getDiscoverySuppress())
      .ifPresentOrElse(discSup -> item.put(ITEM_DISCOVERY_SUPPRESS, discSup), () -> item.remove(ITEM_DISCOVERY_SUPPRESS));
  }

Create Pending VS Open Order

User selects 'Create order' in Order mapping profile

PlantUML Diagram

Source code for sequence diagram
@startuml
!pragma teoz true
!theme cerulean

skinparam backgroundColor white
autonumber "<b>[0]"

actor di as di
participant "mod-orders" as ord
participant "mod-orders-storage" as ords
participant "mod-source-record-manager" as srm

di -> ord: <<DI_ORDER_READY_TO_CREATE>>
activate ord
  alt #White
    ord -> ord: validate order payload
    ord -> ords: persist order
    ords --> ord: created order dto
    ord -> ords: persist order lines
    ords --> ord: created order lines dto
    ord -> srm: <<DI_COMPLETED>>
  else #Pink Creation Failed
    ord -> srm: <<DI_ERROR>>
  end    
deactivate ord

@enduml
   

User selects 'Create and open order' in Order mapping profile

In this diagram assuming that aproach to create Inventory from mod-orders service chosen and user selects Create Instance, Holdings, Item from Order mapping profile

PlantUML Diagram

Source code for sequence diagram
@startuml
!pragma teoz true
!theme cerulean

skinparam backgroundColor white
autonumber "<b>[0]"

actor di as di
participant "mod-orders" as ord
participant "mod-orders-storage" as ords
participant "mod-inventory" as inv
participant "mod-finance" as finance
participant "mod-source-record-manager" as srm

di -> ord: <<DI_ORDER_READY_TO_CREATE>>
activate ord
  alt #White
    ord -> ord: validate order payload
    ord -> ords: persist order
    ord -> ords: persist order lines
    ord -> inv: create Instance, Holdings, Item
    ord -> ords: persist Pieces
    ord -> ord: connect Pieces with Inventory items
    ord -> inv: update Invenory items with pieces connection
    ord -> finance: create Encumbrances
    ord -> ords: update order status to Open
    ord -> srm: <<DI_COMPLETED>>
  else #Pink Creation Failed
    ord -> srm: <<DI_ERROR>>
  end    
deactivate ord

@enduml

   

Data Model

Purchase Order

Composite purchase order request payload
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "composite purchase order with dereferenced/expanded orders fields",
  "type": "object",
  "properties": {
    "id": {
      "description": "UUID of this purchase order",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "approved": {
      "description": "whether or not the purchase order has been approved",
      "type": "boolean",
      "default": false
    },
    "approvedById": {
      "description": "UUID of the user approving the order",
      "type": "object",
      "$ref": "../../common/schemas/uuid.json"
    },
    "approvalDate": {
      "description": "Date and time when purchase order was approved",
      "type": "string",
      "format": "date-time"
    },
    "assignedTo": {
      "description": "UUID of the user this purchase order his assigned to",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "billTo": {
      "description": "UUID of the billing address",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "closeReason": {
      "description": "Close reason for purchase order. Some values are predefined and can trigger actions, such as Cancelled. See mod-orders-storage/src/main/resources/data/system/reasons-for-closure",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/close_reason.json"
    },
    "dateOrdered": {
      "description": "Date and time when purchase order was opened",
      "type": "string",
      "format": "date-time",
      "readonly": true
    },
    "manualPo": {
      "description": "if true, order cannot be sent automatically, e.g. via EDI",
      "type": "boolean"
    },
    "notes": {
      "description": "free-form notes associated with this purchase order",
      "id": "notes",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "poNumber": {
      "description": "A human readable ID assigned to this purchase order",
      "type": "string",
      "pattern": "^[a-zA-Z0-9]{1,22}$"
    },
    "poNumberPrefix": {
      "description": "Purchase order number prefix",
      "type": "string"
    },
    "poNumberSuffix": {
      "description": "Purchase order number suffix",
      "type": "string"
    },
    "orderType": {
      "description": "the purchase order type",
      "type": "string",
      "enum": [
        "One-Time",
        "Ongoing"
      ]
    },
    "reEncumber": {
      "description": "indicates this purchase order should be re-encumbered each fiscal year. Only applies to ongoing orders",
      "type": "boolean",
      "default": false
    },
    "ongoing": {
      "description": "Ongoing information associated with this order",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/ongoing.json"
    },
    "shipTo": {
      "description": "UUID of the shipping address",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "template": {
      "description": "The ID of the order template used for this order. Applies to both PO and POL",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "totalEstimatedPrice": {
      "description": "total estimated price of this purchase order",
      "type": "number"
    },
    "totalEncumbered": {
      "description": "Total encumbered for the order",
      "type": "number",
      "readonly": true
    },
    "totalExpended": {
      "description": "Total expended for the order",
      "type": "number",
      "readonly": true
    },
    "totalItems": {
      "description": "total number of items included in the purchase order",
      "type": "integer"
    },
    "vendor": {
      "description": "UUID of the vendorDetails record",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "workflowStatus": {
      "description": "the workflow status for this purchase order",
      "type": "string",
      "$ref": "../../mod-orders-storage/schemas/workflow_status.json"
    },
    "compositePoLines": {
      "description": "a list of completely de-referenced purchase order lines",
      "id": "compositePoLines",
      "type": "array",
      "items": {
        "type": "object",
        "$ref": "composite_po_line.json"
      }
    },
    "acqUnitIds": {
      "description": "acquisition unit ids associated with this purchase order",
      "type": "array",
      "items": {
        "$ref": "../../common/schemas/uuid.json"
      }
    },
    "tags": {
      "type": "object",
      "description": "arbitrary tags associated with this purchase order",
      "$ref": "../../../raml-util/schemas/tags.schema"
    },
    "metadata": {
      "type": "object",
      "$ref": "../../../raml-util/schemas/metadata.schema",
      "readonly": true
    },
    "needReEncumber": {
      "description": "Indicates that order needs to be re-encumbered",
      "type": "boolean",
      "readonly": true
    }
  },
  "additionalProperties": false,
  "required": [
    "vendor",
    "orderType"
  ]
}

Required fields

Field nameAllowed valuesDescription
orderType[One-Time, Ongoing]DI will setup only One-Time order type
vendor

-

Vendor that user chooses on Order field mapping profile

Not applicable fields for DI orders flow

Business requirements to answer why provided fields are not necessary to populated

Business Requirements

  • Use for one-time orders (physical, electronic, or both). Do not use for ongoing or package orders
  • Should field mapping profiles use order templates?
    • No. The field mapping profile and its mapped/default data already acts as a template. Once a field mapping profile is set up, it does not have to be re-created each time a user imports
  • Can it create/assign order notes?
    • No. Not the order notes created in the separate notes app
  • Can it create/assign tags?
    • No, not yet, for any record type. Aiming to support tags in a future version of DI


Field nameAllowed valuesDescription
reEncumber[True, False]Indicates this purchase order should be re-encumbered each fiscal year
needReEncumber

[True, False]

Indicates that order needs to be re-encumbered
ongoing-

Ongoing object includes these fields: interval, isSubscription, manualRenewal, notes, reviewPeriod, renewalDate, reviewDate

All these fields should not be populated from DI, because DI does not support ongoing orders.

template-Predefined template to create order from
notes-Free-form notes associated with this purchase order
tags-List of simple tags that can be added to the purchase order

Composite Purchase Order Lines

Composite po lines request payload
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "composite purchase order line with dereferenced/expanded orders fields",
  "type": "object",
  "properties": {
    "id": {
      "description": "UUID identifying this purchase order line",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "edition": {
      "description": "edition of the material",
      "type": "string"
    },
    "checkinItems": {
      "description": "if true this will toggle the Check-in workflow for details associated with this PO line",
      "type": "boolean",
      "default": false
    },
    "instanceId": {
      "description": "UUID of the instance record this purchase order line is related to",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "agreementId": {
      "description": "UUID of the agreement this purchase order line is related to",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "acquisitionMethod": {
      "description": "UUID of the acquisition method for this purchase order line",
      "type": "string",
      "$ref": "../../common/schemas/uuid.json"
    },
    "automaticExport": {
      "description": "if true then line will be marked as available to export in the EDIFACT format or other format",
      "type": "boolean",
      "default": false
    },
    "alerts": {
      "description": "alerts associated with this purchase order line",
      "id": "alerts",
      "type": "array",
      "items": {
        "description": "an alert record",
        "type": "object",
        "$ref": "../../mod-orders-storage/schemas/alert.json"
      }
    },
    "cancellationRestriction": {
      "description": "whether or not there are cancellation restrictions for this purchase order line",
      "type": "boolean"
    },
    "cancellationRestrictionNote": {
      "description": "free-form notes related to cancellation restrictions",
      "type": "string"
    },
    "claims": {
      "description": "claims associated with this purchase order line",
      "id": "claims",
      "type": "array",
      "items": {
        "description": "a claim record",
        "type": "object",
        "$ref": "../../mod-orders-storage/schemas/claim.json"
      }
    },
    "collection": {
      "description": "whether or not this purchase order line is for a collection",
      "type": "boolean"
    },
    "contributors": {
      "description": "list of contributors to the material",
      "id": "contributors",
      "type": "array",
      "items": {
        "type": "object",
        "$ref": "../../mod-orders-storage/schemas/contributor.json"
      }
    },
    "cost": {
      "description": "cost details associated with this purchase order line",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/cost.json"
    },
    "description": {
      "description": "description of the material",
      "type": "string"
    },
    "details": {
      "description": "details about this purchase order line",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/details.json"
    },
    "donor": {
      "description": "the donor contributing to this purchase order line",
      "type": "string"
    },
    "eresource": {
      "description": "eresource-related details of this purchase order line",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/eresource.json"
    },
    "fundDistribution": {
      "description": "the UUIDs of the fund distribution records for this purchase order line",
      "id": "fundDistribution",
      "type": "array",
      "items": {
        "description": "a fund distribution record",
        "type": "object",
        "$ref": "../../mod-orders-storage/schemas/fund_distribution.json"
      }
    },
    "isPackage": {
      "description": "Indicates that this POL is for a package",
      "type": "boolean",
      "default": false
    },
    "locations": {
      "description": "a list of the location records for this purchase order line",
      "id": "locations",
      "type": "array",
      "items": {
        "description": "The location details",
        "type": "object",
        "$ref": "../../mod-orders-storage/schemas/location.json"
      }
    },
    "lastEDIExportDate": {
      "description": "The last date when line was exported in the EDIFACT file",
      "type": "string",
      "format": "date-time"
    },
    "orderFormat": {
      "description": "The purchase order line format",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/order_format.json"
    },
    "packagePoLineId": {
      "description": "UUID referencing the poLine that represents the package that this POLs title belongs to",
      "$ref": "../../common/schemas/uuid.json"
    },
    "paymentStatus": {
      "description": "The purchase order line payment status",
      "type": "string",
      "$ref": "../../mod-orders-storage/schemas/payment_status.json"
    },
    "physical": {
      "description": "details of this purchase order line relating to physical materials",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/physical.json"
    },
    "poLineDescription": {
      "description": "purchase order line description",
      "type": "string"
    },
    "poLineNumber": {
      "description": "A human readable number assigned to this PO line",
      "type": "string",
      "pattern": "^[a-zA-Z0-9]{1,22}-[0-9]{1,3}$",
      "readonly": true
    },
    "publicationDate": {
      "description": "date (year) of the material's publication",
      "type": "string"
    },
    "publisher": {
      "description": "publisher of the material",
      "type": "string"
    },
    "purchaseOrderId": {
      "description": "UUID of this parent purchase order",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "receiptDate": {
      "description": "date the purchase order line was received",
      "type": [
        "null",
        "string"
      ],
      "format": "date-time"
    },
    "receiptStatus": {
      "description": "The purchase order line receipt status",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/receipt_status.json"
    },
    "renewalNote": {
      "description": "Renewal note for this purchase order line",
      "type": "string"
    },
    "reportingCodes": {
      "description": "a list of reporting codes associated with this purchase order line",
      "id": "reportingCodes",
      "type": "array",
      "items": {
        "type": "object",
        "$ref": "../../mod-orders-storage/schemas/reporting_code.json"
      }
    },
    "requester": {
      "description": "who requested this purchase order line",
      "type": "string"
    },
    "rush": {
      "description": "whether or not this is a rush order",
      "type": "boolean"
    },
    "selector": {
      "description": "who selected this material",
      "type": "string"
    },
    "source": {
      "description": "the source of this purchase order line",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/source.json"
    },
    "tags": {
      "description": "arbitrary tags associated with this purchase order line",
      "id": "tags",
      "type": "object",
      "$ref": "../../../raml-util/schemas/tags.schema"
    },
    "titleOrPackage": {
      "description": "title of the material",
      "type": "string"
    },
    "vendorDetail": {
      "description": "details related to the vendor of this purchase order line",
      "type": "object",
      "$ref": "../../mod-orders-storage/schemas/vendor_detail.json"
    },
    "metadata": {
      "type": "object",
      "$ref": "../../../raml-util/schemas/metadata.schema",
      "readonly": true
    }
  },
  "additionalProperties": false,
  "required": [
    "acquisitionMethod",
    "cost",
    "orderFormat",
    "source",
    "titleOrPackage"
  ]
}

Required Fields

Field nameAllowed valuesDescription
acquisitionMethod-The UUID format string
cost-

The purchase order line cost.

Includes fields: "listUnitPrice", "listUnitPriceElectronic", "currency", "additionalCost", "discount", "discountType", "exchangeRate", "quantityPhysical", "quantityElectronic", "poLineEstimatedPrice", "fyroAdjustmentAmount"

Currency field is required.

orderFormat

[Electronic Resource, P/E Mix, Physical Resource, Other]

The purchase order line format
source[User, API, EDI, MARC, EBSCONET]The source of the order
titleOrPackage-The title of the materials

Not applicable fields for DI orders flow

Business requirements to answer why provided fields do not necessary to populate

Requirements from business

Use for one-time orders (physical, electronic, or both). Do not use for ongoing or package orders

Field nameAllowed valuesDescription
isPackage[True, False]Indicates that this POL is for a package. Default - false
packagePoLineId-Specify package po line id, applicable only package orders
renewalNote-Specify renewal note, applicable only for ongoing orders

Checking Collection

Receiving and check-in are the same things from mod-orders API side

Checking collection request payload
{
   "$schema":"http://json-schema.org/draft-04/schema#",
   "description":"A collection of check-in",
   "type":"object",
   "properties":{
      "toBeCheckedIn":{
         "description":"List of check-in",
         "id":"toBeCheckedIn",
         "type":"array",
         "items":{
            "type":"object",
            "properties":{
               "poLineId":{
                  "description":"The id of the checkin PO line",
                  "type":"string",
                  "pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
               },
               "checkedIn":{
                  "description":"The number of pieces to check-in",
                  "type":"integer"
               },
               "checkInPieces":{
                  "description":"A collection of piece records",
                  "type":"array",
                  "id":"checkInPieces",
                  "items":{
                     "type":"object",
                     "properties":{
                        "id":{
                           "description":"The id of the piece",
                           "type":"string",
                           "pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
                        },
                        "barcode":{
                           "description":"The barcode assigned to the piece",
                           "type":"string"
                        },
                        "callNumber":{
                           "description":"The call number assigned to the piece",
                           "type":"string"
                        },
                        "comment":{
                           "description":"The free form notes pertaining to the piece",
                           "type":"string"
                        },
                        "caption":{
                           "description":"The enumeration caption of the piece",
                           "type":"string"
                        },
                        "createItem":{
                           "description":"Whether or not to create an item record for this piece",
                           "type":"boolean"
                        },
                        "supplement":{
                           "description":"Whether or not this is a supplementary material for this piece",
                           "type":"boolean"
                        },
                        "locationId":{
                           "description":"The id of the location",
                           "type":"string",
                           "pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
                        },
                        "holdingId":{
                           "description":"UUID of the holding record",
                           "$ref":"../../common/schemas/uuid.json"
                        },
                        "displayOnHolding":{
                           "description":"Whether or not receiving history should be displayed in holding record view",
                           "type":"boolean",
                           "default":false
                        },
                        "enumeration":{
                           "type":"string",
                           "description":"Enumeration is the descriptive information for the numbering scheme of a serial. Synchronized with inventory item."
                        },
                        "chronology":{
                           "type":"string",
                           "description":"Chronology is the descriptive information for the dating scheme of a serial. Synchronized with inventory item."
                        },
                        "discoverySuppress":{
                           "type":"boolean",
                           "description":"Records the fact that the record should not be displayed in a discovery system"
                        },
                        "copyNumber":{
                           "type":"string",
                           "description":"Copy number of the piece"
                        },
                        "materialTypeId":{
                           "description":"The id of the materialType",
                           "type":"string",
                           "pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
                        },
                        "productId":{
                           "description":"The id of the Product",
                           "type":"string"
                        },
                        "productIdType":{
                           "description":"The UUID corresponding to the type of product id",
                           "type":"string",
                           "pattern":"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
                        },
                        "accessionNumber":{
                           "description":"The number referencing physical item acquired by the library",
                           "type":"string"
                        },
                        "itemDescription":{
                           "description":"The description associated with the item record",
                           "type":"string"
                        },
                        "electronicBookplate":{
                           "description":"A text that relates to the owner of the book",
                           "type":"string"
                        },
                        "itemStatus":{
                           "description":"The status of the Check in piece",
                           "$ref":"item_status.json"
                        }
                     },
                     "additionalProperties":false
                  }
               }
            },
            "additionalProperties":false,
            "required":[
               "poLineId"
            ]
         }
      },
      "totalRecords":{
         "description":"The total number of pieces to check-in in the list",
         "type":"integer"
      }
   },
   "additionalProperties":false,
   "required":[
      "toBeCheckedIn",
      "totalRecords"
   ]
}

Required fields

Field nameAllowed valuesDescription
toBeCheckedIn-Contains purchase order line id and collection of pieces to be checked in
totalRecords-

Total records size to be checked in

Mapping Instance, Holdings, Item in mod-orders

How to disable creating Instance, Holdings, Item in mod-orders (todo need to verify)

Disabling of creating Inventory records for physical resource

This schema represents a field 'physical' of Purchase Order line in order. To disable creating Inventory records need to set createInventory field to 'None'

Disable creating Inventory for physical resource
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "purchase order line physical material details",
  "type": "object",
  "properties": {
  "createInventory": {
      "description": "Shows what inventory objects need to be created for physical resource",
      "type": "string",
      "enum": [
        "Instance, Holding, Item",
        "Instance, Holding",
        "Instance",
        "None"
      ]
    },
    "materialType": {
      "description": "UUID of the material Type",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "materialSupplier": {
      "description": "UUID of the material supplier record",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "expectedReceiptDate": {
      "description": "vendor agreed date prior to the Receipt Due date item is expected to be received by",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "receiptDue": {
      "description": "date item should be received by",
      "type": [
        "string",
        "null"
      ],
      "format": "date-time"
    },
    "volumes": {
      "description": "list of volumes included to the physical material",
      "type": "array",
      "items": {
        "description": "the identifier of volume",
        "type": "string"
      }
    }
  },
  "additionalProperties": false,
  "required": [
    "volumes"
  ]
}

Disabling of creating Inventory records for electronic resource

This schema represents a field 'eresource' of PO line in order. To disable creating Inventory records need to set createInventory field to 'None'

Disable creating Inventory for electronic resource
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "description": "purchase order line e-resource details",
  "type": "object",
  "properties": {
    "activated": {
      "description": "whether or not this resource is activated",
      "type": "boolean",
      "default": false
    },
    "activationDue": {
      "description": "number of days until activation, from date of order placement",
      "type": "integer"
    },
    "createInventory": {
      "description": "Shows what inventory objects need to be created for electronic resource",
      "type": "string",
      "enum": [
        "Instance, Holding, Item",
        "Instance, Holding",
        "Instance",
        "None"
      ]
    },
    "trial": {
      "description": "whether or not this is a trial",
      "type": "boolean",
      "default": false
    },
    "expectedActivation": {
      "description": "expected date the resource will be activated",
      "type": "string",
      "format": "date-time"
    },
    "userLimit": {
      "description": "the concurrent user-limit",
      "type": "integer"
    },
    "accessProvider": {
      "description": "UUID of the access provider",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "license": {
      "description": "License record",
      "type": "object",
      "$ref": "license.json"
    },
     "materialType": {
      "description": "UUID of the material Type",
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"
    },
    "resourceUrl": {
      "description": "Electronic resource can be access via this URL",
      "type": "string",
      "pattern": "\\b((?:[a-z][\\w-]+:(?:\/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}\/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:'\".,<>?]))"
    }
  },
  "additionalProperties": false
}

APIs

DI will communicate with mod-orders using kafka messaging, the same approach used for Inventory and Invoice module areas

Business Requirements

  • Should orders be created as pending or open?
    • Use cases for both, so include required field at top of field mapping profile that allows the user to set the status

Create orders

Applies when user choses to create pending order on the field mapping profile screen.

There is field in compositePurchaseOrder request payload named workflowStatus, it will be PENDING by default.

For creating orders CreateOrderEventHandler will use the same implementation as corresponding endpoint uses.

MethodUrlRequest parametersRequest payloadDescription
POST/orders/composite-orderslang
compositePurchaseOrder
Post a purchase order (PO) and a number of PO lines; record fund transactions corresponding to the order. Only in case an acquisition unit has to be assigned to 
the Order it is required that user should have extra permission orders.acquisitions-units-assignments.item.post to create an purchase order.

Open orders

Applies when user choses to create open order on the field mapping profile screen.

There is field in compositePurchaseOrder request payload named workflowStatus, it should be set as OPEN.

For creating orders CreateOrderEventHandler will use the same implementation as corresponding endpoint uses.

MethodUrlRequest parametersRequest payloadDescription
POST/orders/composite-orderslang
compositePurchaseOrder
Post a purchase order (PO) and a number of PO lines; record fund transactions corresponding to the order. Only in case an acquisition unit has to be assigned to 
the Order it is required that user should have extra permission orders.acquisitions-units-assignments.item.post to create an purchase order.

Receive orders

Basically from mod-orders side receiving and  check-in API it the same thing.

MethodUrlRequest parametersRequest payloadDescription
POST/orders/check-inlang
checkingCollection
Check-in items spanning one or more PO lines

Work Breakdown Structure

Open Items

QuestionStatusAnswer
Oder payload has field 'approved', that is false by default. What value for this field DI should populate?  

IN PROCESS

Ann-Marie Breaux (Deactivated) will discuss with SMEs.

When we have situation that max order lines limit is 10, but have 15 order lines and 2 orders in marc file, how they should be mapped?

IN PROCESS

Ann-Marie Breaux (Deactivated) will discuss with SMEs.

PO number can be auto generated by mod-orders system. Does it necessary to place this field on Order mapping profile?

IN PROCESS

Ann-Marie Breaux (Deactivated) will discuss with SMEs.

When creating order lines - its necessary to specify order source from these allowed values: [User, API, EDI, MARC, EBSCONET]. For our case it should be MARC?

CLOSED

MARC value should be used.
Do we need to support orders check-in after receiving?

CLOSED

Updated after conversation with Thunderjet Actually, checking and receive is the same thing, DI should use checking endpoint implementation
Is it possible scenario when DI going to open order, but not create any Instance, Holdings. Item?

CLOSED

Yes, it's possible the library is ordering something that they do not want represented in Inventory (like a database that they maybe want to handle through the ERM app), or other random things that they do not plan to keep in their permanent collection.

Open items to brainstorm with SA, Devs

QuestionStatusAnswer

We use database tables for DI handlers deduplication. Mod-orders does not have schema in DB and any DB related code.

Do we need to follow the same approach with introducing schema as we did in mod-inventory

OPEN


If we are going to create Inventory Instance, Holdings, Item separately - need to discuss with Thunderjet possibility to indroduce

new order parameter inventoryFlow with allowed values: [Synchronized, Independent], the same as did in Order Receiving flow

CLOSED

We will not introduce new parameter inventoryFlow, if need possibility to distinguish logic - orderSource field should be used.

DI will always pass MARC as order source.

Created stories

DescriptionModule

Story

Create default order mapping profiledata-import-converter-storageTBD

Extend mapping engine with ability to create order and multiple order lines from the same MARC record.

data-import-processing-coreTBD
Adjust mod-orders to validate purchase order lines limit from Orders mapping profile

Add kafka handlers in mod-ordersmod-ordersTBD
  • No labels