feat: add item-level order confirmation/decline support
Per Letzshop API, each inventory unit must be confirmed/declined individually.
This enables partial confirmation (some items confirmed, others declined).
Admin API endpoints:
- POST /vendors/{id}/orders/{id}/confirm - confirm all items
- POST /vendors/{id}/orders/{id}/reject - decline all items
- POST /vendors/{id}/orders/{id}/items/{id}/confirm - confirm single item
- POST /vendors/{id}/orders/{id}/items/{id}/decline - decline single item
Order detail modal now shows:
- Product name, EAN, SKU, MPN, price per item
- Per-item state badge (unconfirmed/confirmed/declined)
- Per-item confirm/decline buttons for pending items
- Bulk confirm/decline all buttons
Order status logic:
- If all items declined -> order is "declined"
- If any item confirmed -> order is "confirmed"
- Partial confirmation supported
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -274,7 +274,7 @@
|
||||
'bg-red-100 text-red-700': selectedOrder?.sync_status === 'rejected',
|
||||
'bg-blue-100 text-blue-700': selectedOrder?.sync_status === 'shipped'
|
||||
}"
|
||||
x-text="selectedOrder?.sync_status?.toUpperCase()"
|
||||
x-text="selectedOrder?.sync_status === 'rejected' ? 'DECLINED' : selectedOrder?.sync_status?.toUpperCase()"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -296,19 +296,79 @@
|
||||
</div>
|
||||
|
||||
<div x-show="selectedOrder?.inventory_units?.length > 0">
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-2">Items</h4>
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<template x-for="unit in selectedOrder?.inventory_units || []" :key="unit.id">
|
||||
<div class="flex justify-between text-sm py-1 border-b border-gray-200 dark:border-gray-600 last:border-0">
|
||||
<span class="text-gray-600 dark:text-gray-400" x-text="unit.id"></span>
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded-full"
|
||||
:class="unit.state === 'confirmed' ? 'bg-green-100 text-green-700' : 'bg-orange-100 text-orange-700'"
|
||||
x-text="unit.state"
|
||||
></span>
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Items
|
||||
<span class="text-xs font-normal text-gray-500">
|
||||
(Each item must be confirmed or declined individually)
|
||||
</span>
|
||||
</h4>
|
||||
<div class="space-y-2">
|
||||
<template x-for="(unit, index) in selectedOrder?.inventory_units || []" :key="unit.id">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-700 dark:text-gray-200 text-sm" x-text="unit.product_name || 'Unknown Product'"></p>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1 space-y-0.5">
|
||||
<p x-show="unit.ean">
|
||||
<span class="font-medium">EAN:</span> <span x-text="unit.ean"></span>
|
||||
<span x-show="unit.ean_type" class="text-gray-400" x-text="'(' + unit.ean_type + ')'"></span>
|
||||
</p>
|
||||
<p x-show="unit.sku"><span class="font-medium">SKU:</span> <span x-text="unit.sku"></span></p>
|
||||
<p x-show="unit.mpn"><span class="font-medium">MPN:</span> <span x-text="unit.mpn"></span></p>
|
||||
<p x-show="unit.price"><span class="font-medium">Price:</span> <span x-text="unit.price + ' EUR'"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<!-- Item State Badge -->
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded-full whitespace-nowrap"
|
||||
:class="{
|
||||
'bg-orange-100 text-orange-700': unit.state === 'unconfirmed',
|
||||
'bg-green-100 text-green-700': unit.state === 'confirmed_available' || unit.state === 'confirmed',
|
||||
'bg-red-100 text-red-700': unit.state === 'confirmed_unavailable',
|
||||
'bg-gray-100 text-gray-700': unit.state === 'returned'
|
||||
}"
|
||||
x-text="unit.state === 'confirmed_unavailable' ? 'DECLINED' : (unit.state === 'confirmed_available' ? 'CONFIRMED' : unit.state?.toUpperCase())"
|
||||
></span>
|
||||
<!-- Item Actions (only for unconfirmed items) -->
|
||||
<template x-if="unit.state === 'unconfirmed' && selectedOrder?.sync_status === 'pending'">
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
@click="confirmInventoryUnit(selectedOrder, unit, index)"
|
||||
class="p-1 text-green-600 hover:bg-green-100 rounded"
|
||||
title="Confirm this item"
|
||||
>
|
||||
<span x-html="$icon('check', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<button
|
||||
@click="declineInventoryUnit(selectedOrder, unit, index)"
|
||||
class="p-1 text-red-600 hover:bg-red-100 rounded"
|
||||
title="Decline this item"
|
||||
>
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Bulk Actions -->
|
||||
<div x-show="selectedOrder?.sync_status === 'pending'" class="mt-4 flex gap-2 justify-end">
|
||||
<button
|
||||
@click="confirmAllItems(selectedOrder)"
|
||||
class="px-3 py-1.5 text-sm text-white bg-green-600 hover:bg-green-700 rounded-lg"
|
||||
>
|
||||
Confirm All Items
|
||||
</button>
|
||||
<button
|
||||
@click="declineAllItems(selectedOrder)"
|
||||
class="px-3 py-1.5 text-sm text-white bg-red-600 hover:bg-red-700 rounded-lg"
|
||||
>
|
||||
Decline All Items
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user