diff --git a/project-ideas/fulfillment-center-mgmt/doRateShopping.md b/project-ideas/fulfillment-center-mgmt/doRateShopping.md index d1350e48..dceca23d 100644 --- a/project-ideas/fulfillment-center-mgmt/doRateShopping.md +++ b/project-ideas/fulfillment-center-mgmt/doRateShopping.md @@ -1,29 +1,80 @@ # doRateShopping -The doRateShopping service's main objective is to identify the most suitable shipping method for a given shipment. It uses [getShippingRates](getShippingRates.md) service to retrieve shipping rates for a shipment and then selects the most cost-effective shipping method. +## Service name +`co.hotwax.poorti.shipping.ShippingServices.do#RateShopping` -## Detailed Implementation +## Purpose +Select the best shipping rate for a shipment and update the shipment route segment with the chosen carrier and cost details. The service delegates rate retrieval to [getShippingRates](getShippingRates.md), which may call Unigate or fall back to OMS based on configuration. -Parameters -IN -* ShipmentId +## Inputs +- `shipmentId` (required) -OUT -* ShippingMethodTypeId -* carrierPartyId +## Outputs +- `bestRate` (Map) + - Selected rate map from `shippingRates` (same shape as entries returned by [getShippingRates](getShippingRates.md)). -To identify Unigate Gateway config Need productStoreId, carrierPartyId, facilityId -Get it from OrderHeaderAndShipment view. -https://demo-oms.hotwax.io/webtools/control/FindGeneric?entityName=OrderHeaderAndShipment +The service also updates the shipment route segment in the database. +## Detailed flow +1. Load shipment data + - Fetch `co.hotwax.shipment.OrderHeaderAndShipment` by `shipmentId`. + - If missing, return error `Shipment [${shipmentId}] not found; cannot continue.` +2. Load SLA date + - Fetch `OrderItemShipGroup` using `primaryOrderId` and `primaryShipGroupSeqId`. + - Parse `estimatedDeliveryDate` to a timestamp when present. +3. Retrieve rates + - Call `ShippingServices.get#ShippingRates` with `shipmentId`. + - Extract `shippingRates` from the response. + - If `isRateShoppingSupported` is false, log and return without error. +4. Choose the best rate + - If `estimatedDeliveryDate` exists, filter to rates with `estimatedDeliveryDateTs <= estimatedDeliveryDate`. + - If the filter yields none, fall back to all rates. + - Sort eligible rates by `shippingEstimateAmount` (ascending) and choose the first. +5. Resolve shipment method type (if missing) + - Pull `carrierServiceCode` from `bestRate.shipmentMethod` or `bestRate.carrierServiceCode`. + - If `bestRate.shipmentMethodTypeId` is empty, look up `CarrierShipmentMethod` by: + - `partyId = bestRate.carrierPartyId` (fallback to shipment carrierPartyId) + - `roleTypeId = CARRIER` + - `carrierServiceCode` + - Use the resulting `shipmentMethodTypeId` when found. +6. Update shipment route segment + - Update the first `ShipmentRouteSegment` with: + - `carrierPartyId` from `bestRate` + - `shipmentMethodTypeId` (resolved above) + - `actualCost` from `shippingEstimateAmount` + - `carrierServiceStatusId = SHRSCS_CONFIRMED` + - `carrierService` + - `actualCarrierCode` + - `gatewayRateId` + - Persist using `update#org.apache.ofbiz.shipment.shipment.ShipmentRouteSegment`. -For given estimatedDeliveryDate, get all rates from shipping gateway +## Error and edge cases +- If `get#ShippingRates` returns no rates, the service does not update the shipment route segment. +- If `isRateShoppingSupported` is false, the service logs the reason and exits without error. +- If `bestRate` cannot be determined, it returns `No rate found for shipmentId: ${shipmentId}`. +- Errors from `get#ShippingRates` will bubble up (for example, missing shipment or Unigate configuration). -Rate Comparison and Selection: - 1. Filter rates based on the SLA criteria - 1. If the rates contain service days, filter results to match requested SLA - 2. If rates contain estimated delivery date, compute the number of days and filter results to match requested SLA - 3. If some rates don't contain service days or estimated delivery date, those rates should be demoted to the bottom of the rates. - 4. If no date attributes are present, return all rates with no addional sorting - 2. Select the cheapest rate by sorting on estimated cost. - 1. If multiple rates have the same estimated cost, select the rate with the least number of days. +## Sample Unigate response payload +The Unigate rate response is returned by `call#RateRequest` and propagated into `get#ShippingRates`. + +```json +{ + "success": true, + "statusCode": 200, + "isRateShoppingSupported": true, + "shippingRates": [ + { + "shippingEstimateAmount": 12.75, + "shipmentMethod": "GROUND", + "carrierPartyId": "FEDEX", + "carrierService": "FEDEX_GROUND", + "gatewayRateId": "rate_123", + "actualCarrierCode": "FDX", + "estimatedDeliveryDate": "2026-01-20" + } + ] +} +``` + +## Related services +- [getShippingRates](getShippingRates.md) provides the rate list used for selection. diff --git a/project-ideas/fulfillment-center-mgmt/getShippingRates.md b/project-ideas/fulfillment-center-mgmt/getShippingRates.md index a47417f8..98815bb8 100644 --- a/project-ideas/fulfillment-center-mgmt/getShippingRates.md +++ b/project-ideas/fulfillment-center-mgmt/getShippingRates.md @@ -1,15 +1,67 @@ # getShippingRates -The getShippingRates service's main objective is to retrieve a list of qualified shipping rates for a given shipment. -It takes into account factors such as the shipment's origin and destination, weight, dimensions, and desired delivery time to obtain rates from integrated shipping gateways. -This service returns all available rates that meet the criteria. -[doRateShopping](doRateShopping.md) uses this service and then retuns the best rate for the shipment. - -6. Return - * Return a list of qualified `shippingRates`, where each rate object contains: - * shippingEstimateAmount - Actual service cost - * shipmentMethodTypeId - Service code (e.g., standard, express) - * carrierPartyId - Actual carrier code - * carrierService - Service name returned from gateway - * gatewayRateId - Rate id returned from gateway - * currencyUomId - Currency of the rate +## Service name +`co.hotwax.poorti.shipping.ShippingServices.get#ShippingRates` + +## Purpose +Fetch all available shipping rates for a shipment from Unigate. This service does not select a best rate or update shipment data. + +## Inputs +- `shipmentId` (required) + +## Outputs +- `shippingRates` (List) + - `shippingEstimateAmount` - Estimated shipping cost returned by the gateway + - `shipmentMethod` - Carrier service code used in the request (when available) + - `carrierPartyId` - Carrier party ID used for the rate request + - `carrierService` - Carrier service name returned by the gateway + - `gatewayRateId` - Rate identifier returned by the gateway + - `actualCarrierCode` - Carrier code returned by the gateway + - `estimatedDeliveryDate` - Estimated delivery date (timestamp when parsed, otherwise string) + - `estimatedDeliveryDateTs` - Parsed delivery date timestamp (null when parsing fails) +- `isRateShoppingSupported` (Boolean) + - `true` when the carrier supports rate shopping + - `false` when rate shopping is not supported (no error is thrown) + +## Detailed flow +1. Load shipment data + - Query `co.hotwax.shipment.OrderHeaderAndShipment` by `shipmentId`. + - If no shipment exists, return error `Shipment [${shipmentId}] not found; cannot continue.` +2. Check whether Unigate is enabled + - Call `ShippingServices.check#UnigateEnabled`. + - If not enabled, call `OmsRestShippingServices.get#ShippingRates` and return those rates. +3. Validate Unigate configuration + - Fetch `SystemMessageRemote` with `systemMessageRemoteId = UNIGATE_CONFIG`. + - If missing, return error `SystemMessageRemote [UNIGATE_CONFIG] not found; carrier call aborted.` +3. Determine carrier list + - If `shipmentMethodTypeId` is present, fetch carriers from: + - `org.apache.ofbiz.product.facility.FacilityCarrierShipment` by `facilityId`, `shipmentMethodTypeId`, `roleTypeId = CARRIER` + - If none found, fall back to `org.apache.ofbiz.product.facility.FacilityParty` for the facility (with `date-filter`). + - If no carrier list is found (or `shipmentMethodTypeId` is empty), use the shipment's `carrierPartyId` directly. +4. Resolve carrier shipment method (per carrier list entry) + - Load `_NA_` `CarrierShipmentMethod` for `shipmentMethodTypeId` to get `deliveryDays`. + - Find `CarrierShipmentMethod` for the carrier and matching `deliveryDays`. + - If found, use its `carrierServiceCode` as `shipmentMethod`. +5. Build the rate request map + - Template: `component://poorti/template/shipping/unigate/GetRateRequest.ftl` + - Template requires: + - `ShipmentAndOrder` and `ShipmentRouteSegment` + - `ShippingCarrierConfig` (carrier + facility, with facility-null fallback) + - When available, set `deliveryDays` and `shipmentMethod` on the request map. +6. Call Unigate + - Call `ShippingServices.call#RateRequest`. + - If `isRateShoppingSupported` is false, return with `shippingRates = []` and `isRateShoppingSupported = false`. + - Otherwise, map `shippingRates` from the Unigate response. +7. Build the response list + - For each successful response, map gateway fields into a `shippingRate` entry. + - Parse `estimatedDeliveryDate` to `estimatedDeliveryDateTs` when possible and store both. + +## Error and edge cases +- Missing shipment or `SystemMessageRemote` returns an error immediately. +- Missing `ShippingCarrierConfig` causes the request template to stop and the call to fail. +- If carrier shipment methods are missing for a carrier, that carrier is skipped. +- If Unigate calls fail or return incomplete data, the entry is skipped, and the list may be empty. +- When a carrier does not support rate shopping, the service returns `isRateShoppingSupported = false` with an empty list. + +## Related services +- [doRateShopping](doRateShopping.md) uses this service to pick a best rate and update the shipment route segment. diff --git a/project-ideas/unigate/carriers.md b/project-ideas/unigate/carriers.md new file mode 100644 index 00000000..80e99035 --- /dev/null +++ b/project-ideas/unigate/carriers.md @@ -0,0 +1,34 @@ +# Uniship carrier notes + +This document captures carrier-specific capabilities and how Uniship normalizes responses. It is intended as a quick reference for integration behavior. + +## Normalized rate response (Unigate) +All carrier rate services return a common response shape: +- `success` (Boolean) +- `statusCode` (Integer) +- `isRateShoppingSupported` (Boolean) +- `shippingRates` (List) + +When rate shopping is not supported, Uniship returns: +- `isRateShoppingSupported = false` +- `statusCode = 501` +- `shippingRates = []` + +## Carrier capability matrix +| Carrier | Rate shopping | Labels | Notes | +| --- | --- | --- | --- | +| FedEx | Yes | Yes | Returns rate list or a single normalized rate. | +| Purolator | Yes | Yes | SOAP-based rate and label APIs. | +| Shiphawk | Yes | Yes | Rate shopping via `/rates`. | +| Canada Post | Yes | Yes | XML rate response parsed to normalized fields. | +| Forza | Yes | Yes | Rate response decoded from payload; normalized to rate list. | +| C807 | No | Yes | Returns `isRateShoppingSupported = false`. | +| DrivIn | No | Yes | Uses `post#Order` flow, no rate API. | +| Terminal Express | No | Yes | Uses `post#Order` flow, no rate API. | +| Multientrega | No | Yes | Uses third-party aggregator, no rate API. | +| CargoTrans | No | Yes | Uses `post#Order` flow, no rate API. | + +## Implementation guidelines +- Carrier services that do not support rate shopping must return a successful response with `isRateShoppingSupported = false` so upstream callers can continue without errors. +- Carrier services that do support rate shopping should return `shippingRates` as a list of maps, even when the carrier only returns a single rate. +- Label requests may return label bytes or a `labelImageUrl`, but should always place those under `shippingLabelMap.packages` for consistency. diff --git a/project-ideas/unigate/readme.md b/project-ideas/unigate/readme.md new file mode 100644 index 00000000..d9a1828b --- /dev/null +++ b/project-ideas/unigate/readme.md @@ -0,0 +1,90 @@ +# Unigate + +## What it is +Unigate is a shared integration layer for communication and shipping. It keeps the auth and routing logic in one place so other apps (like Poorti) can call a single interface instead of dealing with each provider or carrier directly. + +## What it does +Unigate provides: +- communication services (send emails, create workflow events) +- shipping services (rate shopping, request labels, refund labels) +- tenant-aware gateway auth and configuration + +## Key services +Communication: +- `co.hotwax.unigate.ApiInterfaceServices.send#EmailCommunication` +- `co.hotwax.unigate.ApiInterfaceServices.create#WorkflowEvent` + +Shipping: +- `co.hotwax.unigate.ApiInterfaceServices.get#ShippingRate` +- `co.hotwax.unigate.ApiInterfaceServices.request#ShippingLabels` +- `co.hotwax.unigate.ApiInterfaceServices.refund#ShippingLabels` + +## Communication services (simple view) +Unigate supports a single communication contract and maps it to a provider: +- **Send Email**: `send#EmailCommunication` + - For **Klaviyo**, Unigate maps `emailType` to a Klaviyo metric event and posts it to trigger a flow. + - For **SMTP/custom gateways** (for example, Mayur), Unigate renders a template and sends email directly. +- **Workflow Event**: `create#WorkflowEvent` + - For **Klaviyo**, Unigate posts an event to `/api/events/`. + - For **SMTP/custom gateways**, events are typically not supported. + +## How shipping works (high level) +1. A client calls Unigate (rate, label, or refund). +2. Unigate validates tenant and gateway auth. +3. Unigate routes the request to the configured carrier service. +4. The carrier service returns a normalized response. + +## Rate shopping behavior +When a carrier does **not** support rate shopping, Unigate returns: +- `isRateShoppingSupported = false` +- `statusCode = 501` +- `shippingRates = []` + +The caller should continue the flow without throwing errors. + +## Related docs +- Carrier capability notes: `project-ideas/unigate/carriers.md` + +## How Poorti uses Unigate +- Poorti calls Unigate for rates and labels. +- If Unigate is not enabled, Poorti can fall back to OMS services. + +## Entities and workflows +This section lists the Unigate entities and how they are used, in a simple format. + +### 1) Entities in scope + +#### A. Party and tenant entities +- **Party**: base record for people/organizations. +- **Person**: person details for a Party. +- **Organization**: organization details for a Party. +- **PartyRole**: assigns roles to parties (tenant role). +- **RoleType**: defines role hierarchy. +- **UserAccount (extend)**: links a user to a Party. + +#### B. Communication gateway entities +- **CommGatewayConfig**: defines which services are used for email and events. +- **CommGatewayAuth**: tenant-specific auth for a communication gateway (ties to `SystemMessageRemote`). + +#### C. Shipping gateway entities +- **ShippingGatewayConfig**: defines which services are used for rate, label, refund, track, and validate address. +- **ShippingGatewayAuth**: tenant-specific auth for shipping gateway (ties to `SystemMessageRemote`). + +#### D. View entities (for read convenience) +- **ShippingGatewayAuthAndConfig**: joins auth + config for shipping. +- **CommGatewayAuthAndConfig**: joins auth + config for communication. +- **UserLoginKeyAndParty**: links user login key with Party. + +### 2) Workflows (simple view) + +#### Communication +1. Configure `CommGatewayConfig` (provider service mapping). +2. Add tenant credentials in `CommGatewayAuth`. +3. Call `send#EmailCommunication` or `create#WorkflowEvent`. + +#### Shipping +1. Configure `ShippingGatewayConfig` (rate/label/refund/track/validate mappings). +2. Add tenant credentials in `ShippingGatewayAuth`. +3. Call `get#ShippingRate` for rate shopping. +4. Call `request#ShippingLabels` for labels. +5. Call `refund#ShippingLabels` or other shipping services when needed.