$npx -y skills add 40RTY-ai/shopify-admin-skills --skill shopify-admin-profit-margin-calculatorRead-only: calculates true net profit per order and per product by factoring in COGS, shipping costs, transaction fees, discounts, refunds, and taxes.
| 1 | ## Purpose |
| 2 | Calculates true net profit and margin at both order-level and product-level granularity. Unlike basic revenue reports, this skill deducts all cost components — COGS (from inventoryItem.unitCost), shipping costs, transaction/payment processing fees, applied discounts, refund amounts, and duties/taxes — to surface actual margin percentages. Read-only — no mutations. |
| 3 | |
| 4 | ## Prerequisites |
| 5 | - Authenticated Shopify CLI session: `shopify store auth --store <domain> --scopes read_orders,read_products,read_inventory` |
| 6 | - API scopes: `read_orders`, `read_products`, `read_inventory` |
| 7 | - For accurate results, products should have `inventoryItem.unitCost` populated |
| 8 | |
| 9 | ## Parameters |
| 10 | |
| 11 | | Parameter | Type | Required | Default | Description | |
| 12 | |-----------|------|----------|---------|-------------| |
| 13 | | store | string | yes | — | Store domain (e.g., mystore.myshopify.com) | |
| 14 | | days_back | integer | no | 30 | Lookback window for orders | |
| 15 | | group_by | string | no | order | Grouping: `order`, `product`, or `variant` | |
| 16 | | min_orders | integer | no | 1 | Minimum orders for a product to appear (product/variant mode) | |
| 17 | | include_refunded | boolean | no | true | Include fully refunded orders in calculation | |
| 18 | | format | string | no | human | Output format: `human` or `json` | |
| 19 | |
| 20 | ## Safety |
| 21 | |
| 22 | > ℹ️ Read-only skill — no mutations are executed. Safe to run at any time. |
| 23 | |
| 24 | ## Workflow Steps |
| 25 | |
| 26 | 1. **OPERATION:** `orders` — query |
| 27 | **Inputs:** `query: "created_at:>='<NOW - days_back days>'"`, `first: 250`, select `id`, `name`, `createdAt`, `totalPriceSet`, `subtotalPriceSet`, `totalShippingPriceSet`, `totalTaxSet`, `totalDiscountsSet`, `currentTotalPriceSet`, `displayFinancialStatus`, `refunds { totalRefundedSet }`, `lineItems { variant { id, inventoryItem { id, unitCost { amount, currencyCode } } }, quantity, originalTotalSet, discountedTotalSet }`, pagination cursor |
| 28 | **Expected output:** All orders in window with full cost breakdown |
| 29 | |
| 30 | 2. **OPERATION:** `inventoryItems` — query |
| 31 | **Inputs:** Batch of `inventoryItemIds` from line item variants for any missing unitCost data |
| 32 | **Expected output:** Unit cost for each inventory item |
| 33 | |
| 34 | 3. For each order, calculate: |
| 35 | - **Revenue** = `currentTotalPriceSet.shopMoney.amount` |
| 36 | - **COGS** = Σ(lineItem.quantity × variant.inventoryItem.unitCost) |
| 37 | - **Shipping Cost** = `totalShippingPriceSet.shopMoney.amount` (merchant-paid portion estimate) |
| 38 | - **Discounts** = `totalDiscountsSet.shopMoney.amount` |
| 39 | - **Transaction Fee** = estimated at 2.9% + $0.30 of total (configurable) |
| 40 | - **Refunds** = Σ(refunds.totalRefundedSet.shopMoney.amount) |
| 41 | - **Net Profit** = Revenue - COGS - Shipping - Transaction Fee - Refunds |
| 42 | - **Margin %** = (Net Profit / Revenue) × 100 |
| 43 | |
| 44 | 4. If `group_by: product` or `variant`, aggregate profits by product/variant across all orders |
| 45 | |
| 46 | 5. **OPERATION:** `productVariants` — query (enrichment) |
| 47 | **Inputs:** Variant IDs from profitable/unprofitable items for product title context |
| 48 | **Expected output:** Product titles, SKUs for display |
| 49 | |
| 50 | ## GraphQL Operations |
| 51 | |
| 52 | ```graphql |
| 53 | # orders:query — validated against api_version 2025-01 |
| 54 | query OrdersWithCosts($query: String!, $after: String) { |
| 55 | orders(first: 250, after: $after, query: $query) { |
| 56 | edges { |
| 57 | node { |
| 58 | id |
| 59 | name |
| 60 | createdAt |
| 61 | displayFinancialStatus |
| 62 | totalPriceSet { shopMoney { amount currencyCode } } |
| 63 | subtotalPriceSet { shopMoney { amount currencyCode } } |
| 64 | totalShippingPriceSet { shopMoney { amount currencyCode } } |
| 65 | totalTaxSet { shopMoney { amount currencyCode } } |
| 66 | totalDiscountsSet { shopMoney { amount currencyCode } } |
| 67 | currentTotalPriceSet { shopMoney { amount currencyCode } } |
| 68 | refunds { |
| 69 | totalRefundedSet { shopMoney { amount currencyCode } } |
| 70 | } |
| 71 | lineItems(first: 50) { |
| 72 | edges { |
| 73 | node { |
| 74 | quantity |
| 75 | originalTotalSet { shopMoney { amount currencyCode } } |
| 76 | discountedTotalSet { shopMoney { amount currencyCode } } |
| 77 | variant { |
| 78 | id |
| 79 | sku |
| 80 | inventoryItem { |
| 81 | id |
| 82 | unitCost { amount currencyCode } |
| 83 | } |
| 84 | product { |
| 85 | id |
| 86 | title |
| 87 | } |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | pageInfo { hasNextPage endCursor } |
| 95 | } |
| 96 | } |
| 97 | ``` |
| 98 | |
| 99 | ```graphql |
| 100 | # inventoryItems:query — validated against api_version 2025-01 |
| 101 | query InventoryItemCosts($ids: [ID!]!) { |
| 102 | nodes(ids: $ids) { |