Rust discount function (one automatic discount evaluating all offers in priority order, parity with the Odoo engine), React Router admin with Polaris, theme app extension and app proxy pages, webhooks incl. GDPR, managed-pricing limits, and a Docker Compose stack (Postgres + Caddy HTTPS) for a single VPS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1391 lines
34 KiB
JSON
1391 lines
34 KiB
JSON
{
|
|
"generated_from": "reference/odoo-c2c_bundle_offers/models/bundle_engine.py",
|
|
"rounding": "per offer: largest remainder to cents (see largest_remainder_cents)",
|
|
"vectors": [
|
|
{
|
|
"name": "fixed_percent_complete",
|
|
"note": "Odoo test: 20% of (10 + 2 x 5) = 4.00, spread 2.00 / 2.00 by price.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "diya",
|
|
"product_id": "P-diya",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "camphor",
|
|
"product_id": "P-camphor",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 2
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "kit",
|
|
"type": "fixed",
|
|
"discount_type": "percent",
|
|
"value": 20,
|
|
"lines": [
|
|
{
|
|
"variant_id": "diya",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "camphor",
|
|
"quantity": 2
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "kit",
|
|
"discount": 4.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "kit",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 2.0,
|
|
"amount": 2.0
|
|
},
|
|
{
|
|
"offer_id": "kit",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 2.0,
|
|
"amount": 2.0
|
|
}
|
|
],
|
|
"total_discount": 4.0
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_incomplete_no_discount",
|
|
"note": "Bundle needs 2 camphor; only 1 in cart -> nothing.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "diya",
|
|
"product_id": "P-diya",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "camphor",
|
|
"product_id": "P-camphor",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "kit",
|
|
"type": "fixed",
|
|
"discount_type": "percent",
|
|
"value": 20,
|
|
"lines": [
|
|
{
|
|
"variant_id": "diya",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "camphor",
|
|
"quantity": 2
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [],
|
|
"allocations": [],
|
|
"total_discount": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_three_tax_groups_split",
|
|
"note": "Odoo split per tax; on Shopify the discount is allocated per line (tax follows the line).",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 2
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "c",
|
|
"product_id": "P-c",
|
|
"collection_ids": [],
|
|
"unit_price": 4.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "percent",
|
|
"value": 20,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 2
|
|
},
|
|
{
|
|
"variant_id": "c",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 4.8,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/3"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 2.0,
|
|
"amount": 2.0
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 2.0,
|
|
"amount": 2.0
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/3",
|
|
"amount_exact": 0.8,
|
|
"amount": 0.8
|
|
}
|
|
],
|
|
"total_discount": 4.8
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_repeats_twice",
|
|
"note": "Enough items for 2 bundles and no limit -> applied twice ($6).",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 2
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 4
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "amount",
|
|
"value": 3,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 2
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 3,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 3,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 3.0,
|
|
"amount": 3.0
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 3.0,
|
|
"amount": 3.0
|
|
}
|
|
],
|
|
"total_discount": 6.0
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_max_uses_1",
|
|
"note": "\"Max uses per order\" = 1.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 2
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 4
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "amount",
|
|
"value": 3,
|
|
"max_uses": 1,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 2
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 3,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 1.5,
|
|
"amount": 1.5
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 1.5,
|
|
"amount": 1.5
|
|
}
|
|
],
|
|
"total_discount": 3.0
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_amount_capped_at_regular",
|
|
"note": "$ off never exceeds the items' price (discount 3.00, not 10.00).",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 2.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 1.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "amount",
|
|
"value": 10,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 3.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 2.0,
|
|
"amount": 2.0
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 1.0,
|
|
"amount": 1.0
|
|
}
|
|
],
|
|
"total_discount": 3.0
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_price_bundle",
|
|
"note": "Bundle price 20 for items worth 29 -> 9.00 off.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 12.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 9.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "c",
|
|
"product_id": "P-c",
|
|
"collection_ids": [],
|
|
"unit_price": 8.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "fixed_price",
|
|
"value": 20,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "c",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 9.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/3"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 3.724138,
|
|
"amount": 3.73
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 2.793103,
|
|
"amount": 2.79
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/3",
|
|
"amount_exact": 2.482759,
|
|
"amount": 2.48
|
|
}
|
|
],
|
|
"total_discount": 9.0
|
|
}
|
|
},
|
|
{
|
|
"name": "fixed_price_above_regular_gives_nothing",
|
|
"note": "Bundle price higher than the items -> no discount, items stay free for other offers.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 5.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "fixed_price",
|
|
"value": 15,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [],
|
|
"allocations": [],
|
|
"total_discount": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "mix_fixed_price_uses_cheapest_no_gain",
|
|
"note": "Cheapest 3 (3+8+9=20) already cost 20 -> no discount (Odoo test).",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "p1",
|
|
"product_id": "P-p1",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 12.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "p2",
|
|
"product_id": "P-p2",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 9.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "p3",
|
|
"product_id": "P-p3",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 8.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/4",
|
|
"variant_id": "p4",
|
|
"product_id": "P-p4",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 3.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "M",
|
|
"type": "mix",
|
|
"discount_type": "fixed_price",
|
|
"value": 20,
|
|
"qty": 3,
|
|
"scope": {
|
|
"variant_ids": [],
|
|
"collection_ids": [
|
|
"sweets"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [],
|
|
"allocations": [],
|
|
"total_discount": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "mix_fixed_price_any_3_for_20",
|
|
"note": "29 -> 20: 9.00 off, spread by price.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "p1",
|
|
"product_id": "P-p1",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 12.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "p2",
|
|
"product_id": "P-p2",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 9.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "p3",
|
|
"product_id": "P-p3",
|
|
"collection_ids": [
|
|
"sweets"
|
|
],
|
|
"unit_price": 8.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "M",
|
|
"type": "mix",
|
|
"discount_type": "fixed_price",
|
|
"value": 20,
|
|
"qty": 3,
|
|
"scope": {
|
|
"variant_ids": [],
|
|
"collection_ids": [
|
|
"sweets"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "M",
|
|
"discount": 9.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/3",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/1"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 3.724138,
|
|
"amount": 3.73
|
|
},
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 2.793103,
|
|
"amount": 2.79
|
|
},
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/3",
|
|
"amount_exact": 2.482759,
|
|
"amount": 2.48
|
|
}
|
|
],
|
|
"total_discount": 9.0
|
|
}
|
|
},
|
|
{
|
|
"name": "mix_percent_twice_cheapest_first",
|
|
"note": "Two uses: first takes the three 4.00 items, second the three 10.00 items.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "p1",
|
|
"product_id": "P-p1",
|
|
"collection_ids": [
|
|
"g"
|
|
],
|
|
"unit_price": 10.0,
|
|
"quantity": 3
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "p2",
|
|
"product_id": "P-p2",
|
|
"collection_ids": [
|
|
"g"
|
|
],
|
|
"unit_price": 4.0,
|
|
"quantity": 3
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "M",
|
|
"type": "mix",
|
|
"discount_type": "percent",
|
|
"value": 10,
|
|
"qty": 3,
|
|
"scope": {
|
|
"variant_ids": [],
|
|
"collection_ids": [
|
|
"g"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "M",
|
|
"discount": 1.2,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
},
|
|
{
|
|
"offer_id": "M",
|
|
"discount": 3.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 3.0,
|
|
"amount": 3.0
|
|
},
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 1.2,
|
|
"amount": 1.2
|
|
}
|
|
],
|
|
"total_discount": 4.2
|
|
}
|
|
},
|
|
{
|
|
"name": "mix_by_variants_and_collections",
|
|
"note": "A unit matches if its variant OR any of its collections is in the scope.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "x",
|
|
"product_id": "P-x",
|
|
"collection_ids": [],
|
|
"unit_price": 6.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "y",
|
|
"product_id": "P-y",
|
|
"collection_ids": [
|
|
"flowers"
|
|
],
|
|
"unit_price": 7.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "z",
|
|
"product_id": "P-z",
|
|
"collection_ids": [],
|
|
"unit_price": 50.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "M",
|
|
"type": "mix",
|
|
"discount_type": "amount",
|
|
"value": 5,
|
|
"qty": 2,
|
|
"scope": {
|
|
"variant_ids": [
|
|
"x"
|
|
],
|
|
"collection_ids": [
|
|
"flowers"
|
|
]
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "M",
|
|
"discount": 5,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 2.307692,
|
|
"amount": 2.31
|
|
},
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 2.692308,
|
|
"amount": 2.69
|
|
}
|
|
],
|
|
"total_discount": 5.0
|
|
}
|
|
},
|
|
{
|
|
"name": "bxgy_same_scope_cheapest_free",
|
|
"note": "Most expensive count as \"buy\", cheapest remaining is free -> 6.00 (Odoo test).",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 8.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "c",
|
|
"product_id": "P-c",
|
|
"collection_ids": [],
|
|
"unit_price": 6.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "B",
|
|
"type": "bxgy",
|
|
"buy_qty": 2,
|
|
"get_qty": 1,
|
|
"get_percent": 100,
|
|
"buy_scope": {
|
|
"variant_ids": [
|
|
"a",
|
|
"b",
|
|
"c"
|
|
],
|
|
"collection_ids": []
|
|
},
|
|
"get_scope": {
|
|
"variant_ids": [
|
|
"a",
|
|
"b",
|
|
"c"
|
|
],
|
|
"collection_ids": []
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "B",
|
|
"discount": 6.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/3"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "B",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 2.5,
|
|
"amount": 2.5
|
|
},
|
|
{
|
|
"offer_id": "B",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 2.0,
|
|
"amount": 2.0
|
|
},
|
|
{
|
|
"offer_id": "B",
|
|
"line_id": "gid://shopify/CartLine/3",
|
|
"amount_exact": 1.5,
|
|
"amount": 1.5
|
|
}
|
|
],
|
|
"total_discount": 6.0
|
|
}
|
|
},
|
|
{
|
|
"name": "bxgy_get_item_missing",
|
|
"note": "Buy part complete but no \"get\" item -> nothing, buy items released.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 2
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "B",
|
|
"type": "bxgy",
|
|
"buy_qty": 2,
|
|
"get_qty": 1,
|
|
"get_percent": 50,
|
|
"buy_scope": {
|
|
"variant_ids": [
|
|
"a"
|
|
],
|
|
"collection_ids": []
|
|
},
|
|
"get_scope": {
|
|
"variant_ids": [
|
|
"gift"
|
|
],
|
|
"collection_ids": []
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [],
|
|
"allocations": [],
|
|
"total_discount": 0
|
|
}
|
|
},
|
|
{
|
|
"name": "bxgy_half_price",
|
|
"note": "Get item at 50% -> 2.00. Discount is spread over ALL used units (buy + get) by price, as Odoo does.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 2
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "gift",
|
|
"product_id": "P-gift",
|
|
"collection_ids": [],
|
|
"unit_price": 4.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "B",
|
|
"type": "bxgy",
|
|
"buy_qty": 2,
|
|
"get_qty": 1,
|
|
"get_percent": 50,
|
|
"buy_scope": {
|
|
"variant_ids": [
|
|
"a"
|
|
],
|
|
"collection_ids": []
|
|
},
|
|
"get_scope": {
|
|
"variant_ids": [
|
|
"gift"
|
|
],
|
|
"collection_ids": []
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "B",
|
|
"discount": 2.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "B",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 1.666667,
|
|
"amount": 1.67
|
|
},
|
|
{
|
|
"offer_id": "B",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 0.333333,
|
|
"amount": 0.33
|
|
}
|
|
],
|
|
"total_discount": 2.0
|
|
}
|
|
},
|
|
{
|
|
"name": "bxgy_repeats",
|
|
"note": "6 items: buy 2 get 1 twice -> 20.00.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 6
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "B",
|
|
"type": "bxgy",
|
|
"buy_qty": 2,
|
|
"get_qty": 1,
|
|
"get_percent": 100,
|
|
"buy_scope": {
|
|
"variant_ids": [
|
|
"a"
|
|
],
|
|
"collection_ids": []
|
|
},
|
|
"get_scope": {
|
|
"variant_ids": [
|
|
"a"
|
|
],
|
|
"collection_ids": []
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "B",
|
|
"discount": 10.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1"
|
|
]
|
|
},
|
|
{
|
|
"offer_id": "B",
|
|
"discount": 10.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/1"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "B",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 20.0,
|
|
"amount": 20.0
|
|
}
|
|
],
|
|
"total_discount": 20.0
|
|
}
|
|
},
|
|
{
|
|
"name": "priority_incomplete_first_offer_frees_items",
|
|
"note": "Odoo test: the incomplete fixed bundle leaves the item for the next offer.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "percent",
|
|
"value": 50,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "M",
|
|
"type": "mix",
|
|
"discount_type": "percent",
|
|
"value": 10,
|
|
"qty": 1,
|
|
"scope": {
|
|
"variant_ids": [
|
|
"a"
|
|
],
|
|
"collection_ids": []
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "M",
|
|
"discount": 1.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "M",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 1.0,
|
|
"amount": 1.0
|
|
}
|
|
],
|
|
"total_discount": 1.0
|
|
}
|
|
},
|
|
{
|
|
"name": "priority_item_used_once",
|
|
"note": "Offers in priority order: the first takes both items, the better second offer gets nothing.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 10.0,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "percent",
|
|
"value": 10,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"id": "M",
|
|
"type": "mix",
|
|
"discount_type": "percent",
|
|
"value": 50,
|
|
"qty": 2,
|
|
"scope": {
|
|
"variant_ids": [
|
|
"a",
|
|
"b"
|
|
],
|
|
"collection_ids": []
|
|
}
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 2.0,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 1.0,
|
|
"amount": 1.0
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 1.0,
|
|
"amount": 1.0
|
|
}
|
|
],
|
|
"total_discount": 2.0
|
|
}
|
|
},
|
|
{
|
|
"name": "rounding_three_way_split",
|
|
"note": "Per-line cents use largest remainder so they add up to exactly 1.00.",
|
|
"cart": {
|
|
"lines": [
|
|
{
|
|
"id": "gid://shopify/CartLine/1",
|
|
"variant_id": "a",
|
|
"product_id": "P-a",
|
|
"collection_ids": [],
|
|
"unit_price": 3.33,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/2",
|
|
"variant_id": "b",
|
|
"product_id": "P-b",
|
|
"collection_ids": [],
|
|
"unit_price": 3.33,
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"id": "gid://shopify/CartLine/3",
|
|
"variant_id": "c",
|
|
"product_id": "P-c",
|
|
"collection_ids": [],
|
|
"unit_price": 3.34,
|
|
"quantity": 1
|
|
}
|
|
]
|
|
},
|
|
"offers": [
|
|
{
|
|
"id": "F",
|
|
"type": "fixed",
|
|
"discount_type": "amount",
|
|
"value": 1,
|
|
"lines": [
|
|
{
|
|
"variant_id": "a",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "b",
|
|
"quantity": 1
|
|
},
|
|
{
|
|
"variant_id": "c",
|
|
"quantity": 1
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"expected": {
|
|
"applications": [
|
|
{
|
|
"offer_id": "F",
|
|
"discount": 1,
|
|
"line_ids_used": [
|
|
"gid://shopify/CartLine/1",
|
|
"gid://shopify/CartLine/2",
|
|
"gid://shopify/CartLine/3"
|
|
]
|
|
}
|
|
],
|
|
"allocations": [
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/1",
|
|
"amount_exact": 0.333,
|
|
"amount": 0.33
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/2",
|
|
"amount_exact": 0.333,
|
|
"amount": 0.33
|
|
},
|
|
{
|
|
"offer_id": "F",
|
|
"line_id": "gid://shopify/CartLine/3",
|
|
"amount_exact": 0.334,
|
|
"amount": 0.34
|
|
}
|
|
],
|
|
"total_discount": 1.0
|
|
}
|
|
}
|
|
]
|
|
} |