Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 71 additions & 20 deletions project-ideas/fulfillment-center-mgmt/doRateShopping.md
Original file line number Diff line number Diff line change
@@ -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.
78 changes: 65 additions & 13 deletions project-ideas/fulfillment-center-mgmt/getShippingRates.md
Original file line number Diff line number Diff line change
@@ -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.
34 changes: 34 additions & 0 deletions project-ideas/unigate/carriers.md
Original file line number Diff line number Diff line change
@@ -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.
90 changes: 90 additions & 0 deletions project-ideas/unigate/readme.md
Original file line number Diff line number Diff line change
@@ -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.