Service - Sell Offer Plan: Difference between revisions

From Izara Wiki
Jump to navigation Jump to search
 
(19 intermediate revisions by the same user not shown)
Line 43: Line 43:
</syntaxhighlight>
</syntaxhighlight>


== LogicalResults ==
== LogicalResultsMain ==
[[Standard LogicalResults Per Service|LogicalResultsMain]]


Caches final results for logical elements
== LogicalResultsData ==
[[Standard LogicalResults Per Service|LogicalResultsData]]


<syntaxhighlight lang="JavaScript">
== LogicalAwaitingStep ==
{
resultId: xxx // eg: filterMainId for a single logical element
dataId: xxx // one sellofferId
}
</syntaxhighlight>
 
* partition key: resultId
* sort key: dataId
 
== PriceLogical ==
 
Temporary link/flow records for ProcessLogical requests for pricing based elements linking set of prices to logical resultId, so when pricing flow completes can find pending logical elements that compare against this set of data, save results into LogicalResults, and send message out for complexFilter service to receive.
 
<syntaxhighlight lang="JavaScript">
{
filteredSellOfferPlanListId: "xx",
resultId: "xx",
logicalElement: {}, // needed for logicalTag after FilteredSellOfferPlanList completes
timestamp: "xx", //when record created
uniqueRequestId: "xx", used for idempotence when first process request called, in case that invocation fails and restarts
}
</syntaxhighlight>
 
* partition key: filteredSellOfferPlanListId
* sort key: resultId
* links shared FilteredSellOfferPlanList records to any number of processLogical resultId requests (differentiated by comparison and value fields)
* is temporary, after save into LogicalResults and send OutProcessLogical can delete record so not keep sending OutProcessLogical when other comparison/value requests complete
 
== FilteredSellOfferPlanList ==
 
Parent record for cache of SellOfferPlanPrices, one list is identified by deliverToLocationIds / orderQuantity / paymentMethodIds / deliveryMethodIds
 
<syntaxhighlight lang="JavaScript">
{
filteredSellOfferPlanListId: "xx",
status: "xx" // "processing"|"complete"
expiryTime: "xx",
uniqueRequestId: "xx", used for idempotence when first process request called, in case that invocation fails and restarts, can be removed when set to complete/error
}
</syntaxhighlight>
 
* partition key: filteredSellOfferPlanListId
* sort key: none
* filteredSellOfferPlanListId: hash of deliverToLocationIds / orderQuantity / paymentMethodIds / deliveryMethodIds
* many PriceLogical records can shared one FilteredSellOfferPlanList list and apply different comparisons to FilteredSellOfferPlanPrice to get their logicalResults
 
== FilteredSellOfferPlanPending ==
 
Temporary link/flow list of records waiting to complete before FilteredSellOfferPlanList can be completed, only use is to make checking any remaining more efficient
 
<syntaxhighlight lang="JavaScript">
{
filteredSellOfferPlanListId: "xx",
SellOfferPlanId: "xx",
}
</syntaxhighlight>
 
* partition key: filteredSellOfferPlanListId
* sort key: SellOfferPlanId
* is temporary and is removed after completed data saved into FilteredSellOfferPlanPrice
* could use SellOfferPlanPriceRange and not have this table, but would need to iterate all SellOfferPlanPriceRange records each time one completes to check if this FilteredSellOfferPlanList is complete, this temporary table might be more efficient when large number of child records are expected
 
== FilteredSellOfferPlanPrice ==
 
Cache of all SellOfferPlanPrices found for one FilteredSellOfferPlanList
 
<syntaxhighlight lang="JavaScript">
{
filteredSellOfferPlanListId: "xx",
price: "xx",
priceSettingId: "xx",
paymentIdDeliveryId: "xx",
SellOfferPlanId
}
</syntaxhighlight>
 
* partition key: filteredSellOfferPlanListId
* sort key: price
* priceSettingId and paymentIdDeliveryId uniquely identifies a SellOfferPlanPrice record
* SellOfferPlanId could be found from SellOfferPlanPrice record, but maybe more efficient to have copy here
 
== SellOfferPlanPriceRange ==
 
Parent record for cache of SellOfferPlanPrices grouped by SellOfferPlan, one list is identified by deliverToLocationIds / orderQuantity / paymentMethodIds / deliveryMethodIds / sellOfferPlanId
 
<syntaxhighlight lang="JavaScript">
{
filteredSellOfferPlanListId: "xx",
sellOfferPlanId: "xx",
// (not needed?) deliverToLocationIds: ["xx","yy"], // array of locationIds
// (not needed?) orderQuantity: x,
// (not needed?) paymentMethodIds: ["xx","yy"], // array of paymentMethodIds(strings), empty array means all available paymentMethods
// (not needed?) deliveryMethodIds: ["xx","yy"], // array of deliveryMethodIds(strings), empty array means all available deliveryMethods
status: "xx", // "processing"|"complete"
expiryTime: "xx"
uniqueRequestId: "xx", used for idempotence when first process request called, in case that invocation fails and restarts, can be removed when set to complete/error
}
</syntaxhighlight>
 
* partition key: filteredSellOfferPlanListId
* sort key: sellOfferPlanId
* a unique record is made up of deliverToLocationIds / orderQuantity / paymentMethodIds / deliveryMethodIds (filteredSellOfferPlanListId), and one sellOfferPlanId
* this is a parent record for individual price results for all combinations of deliverToLocationIds, paymentMethodIds, deliveryMethodIds
 
== SellOfferPlanPriceRangePricePending ==
 
Temporary link/flow list of SellOfferPlanPrices waiting to complete before SellOfferPlanPriceRange can be completed, only use is to make checking any remaining more efficient
 
<syntaxhighlight lang="JavaScript">
{
sellOfferPlanPriceRangeId: "xx"
priceId: "xx",
}
</syntaxhighlight>
 
* partition key: sellOfferPlanPriceRangeId
* sort key: priceId
* sellOfferPlanPriceRangeId is {filteredSellOfferPlanListId}_{sellOfferPlanId}
* priceId is {priceSettingId}_{paymentIdDeliveryId}
* is temporary and is removed after completed data saved into SellOfferPlanPrice and SellOfferPlanPriceRange triggered to check is complete
* maybe let CheckSellOfferPlanPriceRangeComplete lambda remove the SellOfferPlanPriceRangePricePending record to protect against idempotence, eg if we delete then send the message to CheckSellOfferPlanPriceRangeComplete Lambda might end in between, meaning message never gets sent
 
Global secondary index:
* partition key: priceId
* sort key: price sellOfferPlanPriceRangeId
* projected attributes: (none)
* main primary key is used to check if any pending remain for parent SellOfferPlanPriceRange, GSI is used to find parent SellOfferPlanPriceRange after a SellOfferPlanPrice completes
 
== SellOfferPlanPriceRangePrice ==
 
Cache of all SellOfferPlanPrices found for one SellOfferPlan/SellOfferPlanPriceRange, all combinations of the requested deliverToLocationIds, paymentMethodIds, deliveryMethodIds, for the given orderQuantity (so one sellOfferPlanId/sellOfferPlanPriceRangeId will have many records here)
 
<syntaxhighlight lang="JavaScript">
{
sellOfferPlanPriceRangeId: "xx"
priceId: "xx",
status: "xx" // same SellOfferPlanPrice
}
</syntaxhighlight>
 
* partition key: sellOfferPlanPriceRangeId
* sort key: priceId


== SellOfferPlanPrice ==
[[NPM module - izara-shared|AwaitingStep]]


Cache of one completed calculation for a list of sellOffer quantities, and one combination of deliverToLocationId, paymentMethodId, deliveryMethodId. Price includes Payment and Delivery method costs,
== FindDataMain ==
[[Standard FindData Per Service|FindDataMain]]


<syntaxhighlight lang="JavaScript">
== FindDataAwaitingStep ==
{
[[NPM module - izara-shared|AwaitingStep]]
priceSettingId: "xx",
paymentIdDeliveryId: "xx",
sellOfferPlanQuantities: {},
price: "xx"
status: "xx", // "processing"|"complete"|"error"
expiryTime: "xx",
uniqueRequestId: "xx", used for idempotence when first process request called, in case that invocation fails and restarts, can be removed when set to complete/error
}
</syntaxhighlight>
 
* partition key: priceSettingId
* sort key: paymentIdDeliveryId
* priceSettingId is {hash of sellOfferPlanQuantities}_{deliverToLocationId}
* paymentIdDeliveryId is {sellOfferPlanUserPaymentMethodLinkId}_{sellOfferPlanDeliveryMethodLinkId}
* could hash partition and sort key into one partition key but though splitting them out might group them a little better, not need to add these fields separately in SellOfferPlanPriceRangePriceComplete records because can extract from priceSettingId / paymentIdDeliveryId
* sellOfferPlanQuantities works out the combined total for multiple sellOfferPans, all sellOfferPlans must offer the given payment and delivery Ids, if not will be status error
 
Global secondary index:
* partition key: priceSettingId
* sort key: price (for comparison queries)
* projected attributes: paymentIdDeliveryId
 
== MatchPaymentDeliveryLinkList ==
 
Parent record for cache of SellOfferPlans + sellOfferPlanUserPaymentMethodLinkId + sellOfferPlanDeliveryMethodLinkId combinations given a list of paymentMethodIds and deliveryMethodIds
 
<syntaxhighlight lang="JavaScript">
{
paymentDeliveryMethodIds: "xx",
status: "xx" // "processing"|"complete"
expiryTime: "xx"
}
</syntaxhighlight>
 
* partition key: paymentDeliveryMethodIds
* sort key: (none)
* paymentDeliveryMethodIds: hash of paymentMethodIds and any deliveryMethodIds
 
== MatchPaymentDeliveryLink ==
 
Cache of one SellOfferPlans + sellOfferPlanUserPaymentMethodLinkId + sellOfferPlanDeliveryMethodLinkId combination grouped by hash of paymentMethodIds and deliveryMethodIds list
 
<syntaxhighlight lang="JavaScript">
{
paymentDeliveryMethodIds: "xx",
planPaymentDeliveryMethodLinkIds: "xx",
}
</syntaxhighlight>


* partition key: paymentDeliveryMethodIds
== FindDataSortedRequest ==
* sort key: planPaymentDeliveryMethodLinkIds
[[Standard FindData Per Service|FindDataSortedRequest]]
* planPaymentDeliveryMethodLinkIds: {sellOfferPlanId}_{sellOfferPlanUserPaymentMethodLinkId}_{sellOfferPlanDeliveryMethodLinkId}


= Graph database =  
= Graph database =  
Line 271: Line 83:
},
},
currencyId: {
currencyId: {
immutable: true, // all added deliveryMethods must match
immutable: true, // all added deliveryMethods and sellOffers must match},
},
},
},
},
Line 321: Line 133:
addToCalculatedValuePerOrder: {}, // added once per order to the value after calculating delivery method price
addToCalculatedValuePerOrder: {}, // added once per order to the value after calculating delivery method price
addToCalculatedValuePerUnit: {}, // added once per orderQuantity to the value after calculating delivery method price
addToCalculatedValuePerUnit: {}, // added once per orderQuantity to the value after calculating delivery method price
valueType: "xx", // orderQuantity|orderValue|productAttributeId|sellOfferTermId, if set overwrites deliveryMethod defaultValueType
valueType: "xx", // orderQuantity|orderSubtotal|productAttributeId|sellOfferTermId, if set overwrites deliveryMethod defaultValueType
valueId: "xx", // if set overwrites deliveryMethod defaultValueId (used for productAttributeId|sellOfferTermId)
valueId: "xx", // if set overwrites deliveryMethod defaultValueId (used for productAttributeId|sellOfferTermId)
valueLanguageId: {}, // used for productAttributeId|sellOfferTermId, when finding attribute value need to set language to find
accumulateValue: true // default: true, if set to false will find the largest value out of all sellOffers in the order
},
},
}
}
Line 338: Line 152:
</syntaxhighlight>
</syntaxhighlight>
* if both oneTimePercentage and oneTimeAmount are set, applies oneTimePercentage first
* if both oneTimePercentage and oneTimeAmount are set, applies oneTimePercentage first
* applies on to orderSubtotal + deliveryPrice


==== sellOfferPlanSettings ====
==== sellOfferPlanSettings ====
Line 344: Line 159:
{
{
properties: {
properties: {
combineWins: {}, // true|false
combineWins: true, // true|false, default true
minimumQuantity: xx,
prices: {
sellOfferQuantity: [ // sellOfferQuantity type price settings, ordered low to high
{
upToValue: "xx.xx",
price: "xx.xx",
},
// ...
],
orderQuantity: [ // orderQuantity type price settings, ordered low to high
{
upToValue: "xx.xx",
price: "xx.xx",
},
// ...
]
orderValue: [ // orderValue type price settings, ordered low to high
{
upToValue: "xx.xx",
price: "xx.xx",
},
// ...
]
},


},
},
Line 378: Line 169:
<syntaxhighlight lang="JavaScript">
<syntaxhighlight lang="JavaScript">
{
{
relationshipType: "{SellOfferPlanLib.createHasSellOfferPlanGraphRelationshipType()}", // "has_sellOfferPlan"
relationshipType: "{HAS_SELLOFFERPLAN_GRAPH_REL_TYPE}", // "has_sellOfferPlan"
schema: {
schema: {
elementCanBeRemoved: true,
elementCanBeRemoved: false,
allPropertiesImmutable: true,
allPropertiesImmutable: true,
restrictProperties: true,
restrictProperties: true,
Line 450: Line 241:
relationshipType: "{SellOfferPlanLib.createIsDeliveryMethodGraphRelationshipType()}", // "is_deliveryMethod"
relationshipType: "{SellOfferPlanLib.createIsDeliveryMethodGraphRelationshipType()}", // "is_deliveryMethod"
schema: {
schema: {
elementCanBeRemoved: true,
elementCanBeRemoved: false,
allPropertiesImmutable: true,
allPropertiesImmutable: true,
restrictProperties: true,
restrictProperties: true,
Line 464: Line 255:
relationshipType: "{SellOfferPlanLib.createIsUserPaymentMethodGraphRelationshipType()}", // "is_userPaymentMethod"
relationshipType: "{SellOfferPlanLib.createIsUserPaymentMethodGraphRelationshipType()}", // "is_userPaymentMethod"
schema: {
schema: {
elementCanBeRemoved: true,
elementCanBeRemoved: false,
allPropertiesImmutable: true,
allPropertiesImmutable: true,
restrictProperties: true,
restrictProperties: true,
Line 492: Line 283:
logicalTag: "combineWins",
logicalTag: "combineWins",
comparison: "true",
comparison: "true",
},
{
type: "logical",
logicalTag: "maxPrice",
comparison: "lessThan",
value: "xx"
},
{
type: "logical",
logicalTag: "minPrice",
comparison: "lessThan",
value: "xx"
},
},
{
{
type: "complexFilter",
type: "complexFilter",
complexFilter: {
complexFilter: {
filterType: "userPaymentMethod",
filterType: "managerUserPaymentMethod",
// see [[Service - Payment Method Generic|Complex Filter requests]]
// see [[Service - Payment Method Manager|Complex Filter requests]]
}
}
},
},
Line 515: Line 294:
type: "complexFilter",
type: "complexFilter",
complexFilter: {
complexFilter: {
filterType: "deliveryMethod",
filterType: "managerDeliveryMethod",
// see [[Service - Delivery Method Standard|Complex Filter requests]]
// see [[Service - Delivery Method Manager|Complex Filter requests]]
}
}
},
},
...
...
]
]
}
}
Line 536: Line 316:
paymentMethodIds: [],
paymentMethodIds: [],
deliveryMethodIds: []
deliveryMethodIds: []
}
},
{
//will find all PlanDeliveryPaymentCombinations for this sellOfferId
type: "logical",
logicalTag: "sellOfferId",
comparison: "equals",
value: {sellOfferId}
},
{
//will find all PlanDeliveryPaymentCombinations for listed sellOfferIds
type: "logical",
logicalTag: "sellOfferIds",
comparison: "equals",
value: [] // array of sellOfferIds
},
...
...
]
]
Line 554: Line 348:


Complex filter (filterType = sellOfferPlan) can find all sellOfferPlanIds that match a pricing comparison, eg minimumPrice < 100:
Complex filter (filterType = sellOfferPlan) can find all sellOfferPlanIds that match a pricing comparison, eg minimumPrice < 100:
# request enters at ProcessLogical and saves PriceLogical record that is used to send final ProcessLogicalComplete message
# creates FilteredSellOfferPlanList which manages the final status of a list of sellOfferPlans that match a range of deliverToLocationIds / orderQuantity / paymentMethodIds / deliveryMethodIds (set by the ProcessLogical request)
# all sellOfferPlans that actively offer both any paymentMethodIds and any deliveryMethodIds are processed
# each sellOfferPlan is saved into FilteredSellOfferPlanPending, then check if SellOfferPlanPriceRange exists and complete/not expired, if not then initiate SellOfferPlanPriceRange processing, if is complete (or no sellOfferPlans found) need to copy all matching SellOfferPlanPrices into FilteredSellOfferPlanPrice and remove FilteredSellOfferPlanPending record
# for each sellOfferPlan find all delivery/payment method links that match given arrays
# for each sellOfferPlan all deliverToLocationIds / orderQuantity / sellOfferPlanUserPaymentMethodLinkId_sellOfferPlanDeliveryMethodLinkId combinations create SellOfferPlanPriceRangePricePending record then check if SellOfferPlanPrice complete/not expired, if not then send to PriceCalc, if is complete need to copy SellOfferPlanPrice into FilteredSellOfferPlanPrice remove SellOfferPlanPriceRangePricePending record, and if all combinations already exist (or no combinations found) invoke CheckSellOfferPlanPriceRangeComplete (be careful if paginating lambda processing, can only check this paginations set of found combinations)
# PriceCalc result is saved into SellOfferPlanPrice
# all matching SellOfferPlanPriceRangePricePending records trigger CheckSellOfferPlanPriceRangeComplete:
## removes SellOfferPlanPriceRangePricePending record (do this in CheckSellOfferPlanPriceRangeComplete for idempodence)
## saves record into FilteredSellOfferPlanPrice according to the SellOfferPlanPrice that was just completed (maybe do this in one hot when SellOfferPlanPriceRange completes, we can use same code for when new FilteredSellOfferPlanList finds existing SellOfferPlanPriceRange
## if no SellOfferPlanPriceRangePricePendings remain, set SellOfferPlanPriceRange status to complete and trigger CheckFilteredSellOfferPlanComplete:
### remove FilteredSellOfferPlanPending record (do this in CheckFilteredSellOfferPlanComplete for idempodence)
### if no FilteredSellOfferPlanPending remain, set FilteredSellOfferPlanList status to complete, find all matching PriceLogicals and trigger CompletePriceLogical:
#### perform comparison on FilteredSellOfferPlanPrice and save results into LogicalResults
#### send final ProcessLogicalComplete message


== Cart price ==
== Cart price ==
Line 578: Line 356:


* For a range of (or all) paymentMethodIds / deliveryMethodIds, deliverToLocationIds / orderQuantity are fixed.  
* For a range of (or all) paymentMethodIds / deliveryMethodIds, deliverToLocationIds / orderQuantity are fixed.  
* Enters at SellOfferPlanPriceRange flow
* Maybe have another table that triggers result actions, if any (can maybe poll SellOfferPlanPriceRange table to see if complete)


== Presenting a product's price options ==
== Presenting a product's price options ==


* For a range of (or all) paymentMethodIds / deliveryMethodIds, deliverToLocationIds / orderQuantity are fixed
* For a range of (or all) paymentMethodIds / deliveryMethodIds, deliverToLocationIds / orderQuantity are fixed
* Enters at SellOfferPlanPriceRange flow, for each of the product's sellOffers
* Maybe have a table that manages the status of all found sellOfferPlans's and their SellOfferPlanPriceRange, similar to FilteredSellOfferPlanList/FilteredSellOfferPlanPending
* Probably find all sellOfferPlan's that match paymentMethodIds / deliveryMethodIds for that product before sending to SellOfferPlanPriceRange flow, similar to FilteredSellOfferPlanList flow


= Handling SellOfferPlanPrice errors =
= Handling SellOfferPlanPrice errors =
Line 593: Line 366:
* SellOfferPlanPrice's that are error do save into SellOfferPlanPriceRangePrice, so a SellOfferPlanPriceRange object can see all prices calculated, any requests at this level will need to filter out error prices
* SellOfferPlanPrice's that are error do save into SellOfferPlanPriceRangePrice, so a SellOfferPlanPriceRange object can see all prices calculated, any requests at this level will need to filter out error prices
* SellOfferPlanPrice's that are error do not save into FilteredSellOfferPlanPrice, they are ignored as they cannot be filtered effectively
* SellOfferPlanPrice's that are error do not save into FilteredSellOfferPlanPrice, they are ignored as they cannot be filtered effectively
= Notes =
* A Sell Offer Plan can link to multiple payment/delivery links, but those multiple links should not point to the same delivery/userpaymentmethod nodes, try to catch/validate this when creating new link nodes


= Working documents =
= Working documents =

Latest revision as of 09:32, 6 August 2023

Overview

A user's plan that connects a user's sell offer to any number of delivery method links and payment method links, and also to the sell offer's prices.

Each sell offer will have one plan, the plan controls the sell offer's prices, and many sell offers can point to the same plan (if they all share the same prices), so any change in price will mean a new sell offer plan.

The plan also links to delivery methods and payment methods using link nodes, so sell offers that use the same sell offer plan must offer the same delivery method and payment methods and their link settings.

Many sell offer plans can use the same delivery method links and payment method links, so if multiple sell offers have the same delivery and payment method settings but different prices, they can share the links but with separate sell offer plans + pricing.

Repository

https://bitbucket.org/izara-market-products/izara-market-products-sell-offer-plan

DynamoDB tables

Standard Config Table Per Service

Configuration tags

{
	configTag: "DeliveryMethodManagerServiceName"
	configKey: "DeliveryMethodManagerServiceName"
	configValue: xxx // eg: "DeliveryMethodManager"
}
{
	configTag: "PaymentMethodManagerServiceName"
	configKey: "PaymentMethodManagerServiceName"
	configValue: xxx // eg: "PaymentMethodManager"
}
{
	configTag: "ProductGraphServiceName"
	configKey: "ProductGraphServiceName"
	configValue: xxx // eg: "ProductGraph"
}

LogicalResultsMain

LogicalResultsMain

LogicalResultsData

LogicalResultsData

LogicalAwaitingStep

AwaitingStep

FindDataMain

FindDataMain

FindDataAwaitingStep

AwaitingStep

FindDataSortedRequest

FindDataSortedRequest

Graph database

Service - Products Graph

Nodes

{
	nodeLabel: "{SellOfferPlanLib.SELLOFFERPLAN_GRAPH_NODE_LABEL}", // "sellOfferPlan"
	schema: {
		identifier: true,
		restrictProperties: true,
		restrictRelationships: true,
		properties: {
			sellOfferPlanId: {
				identifier: true, // create unique id from request params and uniqueMessageId
			},
			userId: {
				immutable: true, // a sellOfferPlan can only be used by one user (because links to their userPaymentMethods)
			},
			currencyId: {
				immutable: true, // all added deliveryMethods and sellOffers must match},
			},
		},
	}
}
{
	nodeLabel: "{SellOfferPlanLib.SELLOFFERPLAN_DELIVERYMETHODLINK_GRAPH_NODE_LABEL}", // "sellOfferPlanDeliveryMethodLink"
	schema: {
		identifier: true,
		restrictProperties: true,
		restrictRelationships: true,
		properties: {
			sellOfferPlanDeliveryMethodLinkId: {
				identifier: true, // create unique id from request params and uniqueMessageId
			},
		},
	}
}
{
	nodeLabel: "{SellOfferPlanLib.SELLOFFERPLAN_USERPAYMENTMETHODLINK_GRAPH_NODE_LABEL}", // "sellOfferPlanUserPaymentMethodLink"
	schema: {
		identifier: true,
		restrictProperties: true,
		restrictRelationships: true,
		properties: {
			sellOfferPlanUserPaymentMethodLinkId: {
				identifier: true, // create unique id from request params and uniqueMessageId
			},
		},
	}
}

Versioned data

sellOfferPlanDeliveryMethodLinkSettings

{
	properties: {
		addToRateTableValuePerOrder: {}, // added once per order to the value before calculating delivery method price
		addToRateTableValuePerUnit: {}, // added once per orderQuantity to the value before calculating delivery method price
		addToCalculatedValuePerOrder: {}, // added once per order to the value after calculating delivery method price
		addToCalculatedValuePerUnit: {}, // added once per orderQuantity to the value after calculating delivery method price
		valueType: "xx", // orderQuantity|orderSubtotal|productAttributeId|sellOfferTermId, if set overwrites deliveryMethod defaultValueType
		valueId: "xx", // if set overwrites deliveryMethod defaultValueId (used for productAttributeId|sellOfferTermId)
		valueLanguageId: {}, // used for productAttributeId|sellOfferTermId, when finding attribute value need to set language to find
		accumulateValue: true // default: true, if set to false will find the largest value out of all sellOffers in the order
	},
}

sellOfferPlanUserPaymentMethodLinkSettings

{
	properties: {
		oneTimePercentage: {}, // increase paymentMethod price by percentage
		oneTimeAmount: {}, // increase paymentMethod price by set amount
	},
}
  • if both oneTimePercentage and oneTimeAmount are set, applies oneTimePercentage first
  • applies on to orderSubtotal + deliveryPrice

sellOfferPlanSettings

{
	properties: {
		combineWins: true, // true|false, default true

	},
}

Relationships

{
	relationshipType: "{HAS_SELLOFFERPLAN_GRAPH_REL_TYPE}", // "has_sellOfferPlan"
	schema: {
		elementCanBeRemoved: false,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}
  • links a user to one of their sell offer plans
{
	relationshipType: "{SellOfferPlanLib.createHasDeliveryMethodLinkGraphRelationshipType()}", // "has_deliveryMethodLink"
	schema: {
		elementCanBeRemoved: true,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}
{
	relationshipType: "{SellOfferPlanLib.createDisabledDeliveryMethodLinkGraphRelationshipType()}", // "disabled_deliveryMethodLink"
	schema: {
		elementCanBeRemoved: true,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}
{
	relationshipType: "{SellOfferPlanLib.createHasUserPaymentMethodLinkGraphRelationshipType()}", // "has_userPaymentMethodLink"
	schema: {
		elementCanBeRemoved: true,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}
{
	relationshipType: "{SellOfferPlanLib.createDisabledUserPaymentMethodLinkGraphRelationshipType()}", // "disabled_userPaymentMethodLink"
	schema: {
		elementCanBeRemoved: true,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}
  • many sell offer plans can link to one delivery method link, or one payment method link, this allows user to share settings in the link nodes across multiple sell offer plans
{
	relationshipType: "{SellOfferPlanLib.createIsDeliveryMethodGraphRelationshipType()}", // "is_deliveryMethod"
	schema: {
		elementCanBeRemoved: false,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}
{
	relationshipType: "{SellOfferPlanLib.createIsUserPaymentMethodGraphRelationshipType()}", // "is_userPaymentMethod"
	schema: {
		elementCanBeRemoved: false,
		allPropertiesImmutable: true,
		restrictProperties: true,
		properties: {
			originTimestamp: //timestamp the request to create/change this relationship was sent
		},
	}
}

Complex Filter requests

{
	filterType: "sellOfferPlan" //unique id is sellOfferPlanId
	type: "group",
	elements:
	[
		{
			type: "logical",
			logicalTag: "userId",
			comparison: "equals",
			value: "xx"
		},
		{
			type: "logical",
			logicalTag: "combineWins",
			comparison: "true",
		},
		{
			type: "complexFilter",
			complexFilter: {
				filterType: "managerUserPaymentMethod",
				// see [[Service - Payment Method Manager|Complex Filter requests]]
			}
		},		
		{
			type: "complexFilter",
			complexFilter: {
				filterType: "managerDeliveryMethod",
				// see [[Service - Delivery Method Manager|Complex Filter requests]]
			}
		},
		...

	]
}
{
	// finds all combinations where the plan has both a deliveryMethodId and paymentMethodId from the given lists of deliveryMethodId and paymentMethodIds
	filterType: "planDeliveryPaymentCombination" //unique id is planDeliveryPaymentCombinationId
	type: "group",
	elements:
	[
		{
			type: "logical",
			logicalTag: "planDeliveryPaymentCombination",
			paymentMethodIds: [],
			deliveryMethodIds: []
		},
		{
			//will find all PlanDeliveryPaymentCombinations for this sellOfferId
			type: "logical",
			logicalTag: "sellOfferId",
			comparison: "equals",
			value: {sellOfferId}
		},
		{
			//will find all PlanDeliveryPaymentCombinations for listed sellOfferIds
			type: "logical",
			logicalTag: "sellOfferIds",
			comparison: "equals",
			value: [] // array of sellOfferIds
		},
		...
	]
}
  • planDeliveryPaymentCombinationId = {sellOfferPlanId}_{sellOfferPlanDeliveryMethodLinkId}_{sellOfferPlanPaymentMethodLinkId}

Translate Ids - userPaymentMethod and deliveryMethod to sellOfferPlan

  • only find sellOfferPlanIds that have has_userPaymentMethodLink or has_deliveryMethodLink relationships (ignore disabled)
  • bypasses link nodes (no complex filterType)

Calc pricing use cases

Complex filter

Complex filter (filterType = sellOfferPlan) can find all sellOfferPlanIds that match a pricing comparison, eg minimumPrice < 100:

Cart price

A cart has a fixed deliveryMethod and paymentMethod so no comparisons are needed across these, instead the array of sellOfferQuantities are sent to PriceCalc which returns the total price, per sellOffer price, orderQuantity, etc..

Presenting a sellOffer's price options

  • For a range of (or all) paymentMethodIds / deliveryMethodIds, deliverToLocationIds / orderQuantity are fixed.

Presenting a product's price options

  • For a range of (or all) paymentMethodIds / deliveryMethodIds, deliverToLocationIds / orderQuantity are fixed

Handling SellOfferPlanPrice errors

  • If a SellOfferPlanPrice has an error it's status is set to error
  • SellOfferPlanPrice's that are error do save into SellOfferPlanPriceRangePrice, so a SellOfferPlanPriceRange object can see all prices calculated, any requests at this level will need to filter out error prices
  • SellOfferPlanPrice's that are error do not save into FilteredSellOfferPlanPrice, they are ignored as they cannot be filtered effectively

Notes

  • A Sell Offer Plan can link to multiple payment/delivery links, but those multiple links should not point to the same delivery/userpaymentmethod nodes, try to catch/validate this when creating new link nodes

Working documents

Sell Offer Plan