[object Object]

← back to Shopify Sample Shipping

TK-11333: interior-designer shipping scenarios (read-only)

05b34434dc097afcc151c832296da92deba329f2 · 2026-09-09 15:08:39 -0700 · Steve Abrams

- designer-scenarios.mjs: bulk swatch orders (<=7 free, 8+ charged ~$20) + mixed cart
- designer-discount.mjs: proves the free-shipping band reads the POST-discount subtotal,
  so trade discounts re-qualify bulk swatch orders (25%->~9 free, 30%->~10, 50%->~14).
  Uses draftOrderCalculate (creates nothing). No store writes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 05b34434dc097afcc151c832296da92deba329f2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 9 15:08:39 2026 -0700

    TK-11333: interior-designer shipping scenarios (read-only)
    
    - designer-scenarios.mjs: bulk swatch orders (<=7 free, 8+ charged ~$20) + mixed cart
    - designer-discount.mjs: proves the free-shipping band reads the POST-discount subtotal,
      so trade discounts re-qualify bulk swatch orders (25%->~9 free, 30%->~10, 50%->~14).
      Uses draftOrderCalculate (creates nothing). No store writes.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 designer-discount.mjs  | 24 ++++++++++++++++++++++++
 designer-scenarios.mjs | 20 ++++++++++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/designer-discount.mjs b/designer-discount.mjs
new file mode 100644
index 0000000..9668fcb
--- /dev/null
+++ b/designer-discount.mjs
@@ -0,0 +1,24 @@
+import {query} from './query.mjs';
+const S=['gid://shopify/ProductVariant/44090076954675','gid://shopify/ProductVariant/44086096756787','gid://shopify/ProductVariant/44044973408307'];
+const spread=n=>{const it=[];let l=n;for(let i=0;i<S.length;i++){const q=Math.ceil(l/(S.length-i));if(q>0)it.push({variantId:S[i],quantity:q});l-=q;}return it;};
+// draftOrderCalculate = READ-ONLY (creates no draft). Tests whether a trade discount re-qualifies free shipping.
+const q=`mutation($input:DraftOrderInput!){draftOrderCalculate(input:$input){calculatedDraftOrder{
+  subtotalPriceSet{shopMoney{amount}} totalPriceSet{shopMoney{amount}}
+  availableShippingRates{title price{amount currencyCode}}} userErrors{field message}}}`;
+const addr={address1:'15442 Ventura Blvd',city:'Sherman Oaks',provinceCode:'CA',countryCode:'US',zip:'91403'};
+async function run(label,n,discount){
+  const input={lineItems:spread(n),shippingAddress:addr};
+  if(discount) input.appliedDiscount={value:discount,valueType:'PERCENTAGE',title:`Trade ${discount}%`};
+  const d=(await query(q,{input})).draftOrderCalculate;
+  if(d.userErrors?.length){console.log(label,'ERR',JSON.stringify(d.userErrors));return;}
+  const c=d.calculatedDraftOrder;
+  const sub=c.subtotalPriceSet.shopMoney.amount;
+  const rates=c.availableShippingRates||[];
+  const free=rates.some(x=>parseFloat(x.price.amount)===0);
+  console.log(`${label}: subtotal $${sub} -> ${free?'FREE ✅':'no free ❌'}  [rates: ${rates.map(r=>r.title+' $'+r.price.amount).join(' | ')||'none'}]`);
+}
+console.log('=== TRADE-DISCOUNT interaction (8 samples, list $34) ===');
+await run('8 samples, no discount ', 8, null);
+await run('8 samples, 25% trade   ', 8, 25);   // 34 -> 25.50
+await run('10 samples, 30% trade  ', 10, 30);  // 42.50 -> 29.75
+await run('15 samples, 50% trade  ', 15, 50);  // 63.75 -> 31.88 (still >30)
diff --git a/designer-scenarios.mjs b/designer-scenarios.mjs
new file mode 100644
index 0000000..c9affd7
--- /dev/null
+++ b/designer-scenarios.mjs
@@ -0,0 +1,20 @@
+import {query} from './query.mjs';
+const S=['gid://shopify/ProductVariant/44090076954675','gid://shopify/ProductVariant/44086096756787','gid://shopify/ProductVariant/44044973408307'];
+const ROLL='gid://shopify/ProductVariant/40348093055027'; // $168 roll
+const qq=`query($items:[DraftOrderLineItemInput!]!){draftOrderAvailableDeliveryOptions(input:{lineItems:$items,shippingAddress:{address1:"15442 Ventura Blvd",city:"Sherman Oaks",provinceCode:"CA",countryCode:US,zip:"91403"}}){availableShippingRates{title price{amount currencyCode}}}}`;
+const rates=async items=>(await query(qq,{items})).draftOrderAvailableDeliveryOptions.availableShippingRates;
+const free=r=>r.some(x=>parseFloat(x.price.amount)===0);
+const cheapestPaid=r=>r.filter(x=>parseFloat(x.price.amount)>0).map(x=>parseFloat(x.price.amount)).sort((a,b)=>a-b)[0];
+const spread=n=>{const it=[];let l=n;for(let i=0;i<S.length;i++){const q=Math.ceil(l/(S.length-i));if(q>0)it.push({variantId:S[i],quantity:q});l-=q;}return it;};
+
+console.log('=== INTERIOR DESIGNER: bulk swatch orders ($4.25 each) ===');
+for(const n of [3,6,7,8,10,15,20]){
+  const r=await rates(spread(n));
+  const sub=(n*4.25).toFixed(2);
+  console.log(`  ${String(n).padStart(2)} samples ($${sub}): ${free(r)?'FREE ✅':'charged ~$'+cheapestPaid(r)+' ❌'}`);
+}
+console.log('\n=== INTERIOR DESIGNER: project order (samples + a roll) ===');
+const mixed=[{variantId:S[0],quantity:5},{variantId:ROLL,quantity:1}]; // 5 samples + 1 roll
+const rm=await rates(mixed);
+console.log(`  5 samples + 1 roll ($${(5*4.25+168.40).toFixed(2)}): ${free(rm)?'FREE':'no free — cheapest paid $'+cheapestPaid(rm)}`);
+console.log('  (mixed cart >$30 => whole cart loses the free-sample rate, incl. the samples)');

← 8911e53 TK-11333: create live test order #33047 proving sample free-  ·  back to Shopify Sample Shipping  ·  TK-11333: create bulk designer test order #33048 (12 samples d4101ca →