# Manual Delivery Agreement Implementation Plan > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Make manager/logistician manual delivery agreement safe and clear: only future delivery dates are allowed, controls match the app theme, order group counters are correct, and confusing technical fields are removed from the card. **Architecture:** Keep the workflow centered on `order_groups`. The UI validates future dates before submit, the Edge Function enforces the same rule server-side, and the repository maps partial `order_groups` rows into a clean view model for dashboards and cards. **Tech Stack:** React 18, Vite, Vitest, Supabase JS, Supabase Edge Functions in Deno, Tailwind utility classes with CSS variables. --- ## File Structure - Modify: `src/components/orders/OrderDetailPanel.jsx` Responsible for the order group detail card, manual agreement UI, local form validation, and hiding confusing technical fields. - Modify: `src/components/orders/OrderDetailPanel.test.jsx` Server-rendered component tests for the detail card, editable controls, and visible copy. - Modify: `src/services/supabase/orderGroupRepository.js` Responsible for mapping raw `order_groups` rows into the frontend delivery group model and saving manual delivery choices through the Edge Function. - Modify: `src/services/supabase/orderGroupRepository.test.js` Mapping tests for missing counters, real delivery dates, and fallback behavior. - Modify: `supabase/functions/update-order-group-delivery-choice/index.ts` Server-side manual agreement validation and update logic. - Optional modify: `src/components/UI/Select.jsx` Only touch if other select controls still need a global design correction after the manual agreement block switches to app-styled buttons. - Optional modify: `docs/sql/order-groups-manual-delivery-choice.sql` Only touch if database constraints are added later. Current requirement can be enforced in the Edge Function. --- ## Chunk 1: Data Mapping Correctness ### Task 1: Stop Showing Fake Delivery Dates **Files:** - Modify: `src/services/supabase/orderGroupRepository.js` - Test: `src/services/supabase/orderGroupRepository.test.js` - [ ] **Step 1: Write the failing mapping test** Add a case where `customer_date` exists but `delivery_date` is null. ```js const group = mapOrderGroupRowToDeliveryGroup({ id: "group-without-delivery-date", group_key: "9781632663|28.04.26", customer_date: "28.04.26", order_numbers: ["СФ Т\\ЕА-26979"], status: "ready_for_notification", delivery_status: "pending_confirmation", created_at: "2026-05-05 09:43:53.750061+00", updated_at: "2026-05-05 09:43:53.750061+00", }); expect(group.customerDate).toBe("28.04.26"); expect(group.deliveryDate).toBe(""); ``` - [ ] **Step 2: Run the focused test** Run: `npm test -- --run src/services/supabase/orderGroupRepository.test.js` Expected before implementation: FAIL because `deliveryDate` is incorrectly filled from `customerDate`. - [ ] **Step 3: Implement the mapping fix** In `mapOrderGroupRowToDeliveryGroup`, set: ```js const deliveryDate = normalizeText(row.delivery_date); ``` Do not fall back to `customerDate` for actual delivery agreement data. - [ ] **Step 4: Run the focused test** Run: `npm test -- --run src/services/supabase/orderGroupRepository.test.js` Expected: PASS. ### Task 2: Infer Counters When `order_groups` Counter Columns Are Empty **Files:** - Modify: `src/services/supabase/orderGroupRepository.js` - Test: `src/services/supabase/orderGroupRepository.test.js` - [ ] **Step 1: Write the failing counter test** Use a real-shaped row where `order_numbers` has values, but `orders_count`, `ready_count`, and `not_ready_count` are missing. ```js const group = mapOrderGroupRowToDeliveryGroup({ id: "group-without-counters", group_key: "9781632663|28.04.26", order_numbers: ["СФ Т\\ЕА-26979"], status: "ready_for_notification", delivery_status: "pending_confirmation", created_at: "2026-05-05 09:43:53.750061+00", updated_at: "2026-05-05 09:43:53.750061+00", }); expect(group.ordersCount).toBe(1); expect(group.readyCount).toBe(1); expect(group.notReadyCount).toBe(0); ``` - [ ] **Step 2: Run the focused test** Run: `npm test -- --run src/services/supabase/orderGroupRepository.test.js` Expected before implementation: FAIL with `0` counters. - [ ] **Step 3: Implement fallback counters** Use `order_numbers.length` as a fallback for total count. For `status === "ready_for_notification"`, infer `readyCount` as `ordersCount` when explicit ready counters are absent. ```js const orderNumbers = toStringArray(row.order_numbers); const inferredOrderCount = orderNumbers.length; const ordersCount = toNumber(row.orders_count ?? row.orders_total ?? row.legacy_orders_total, inferredOrderCount); const readyCount = toNumber( row.ready_count ?? row.orders_ready ?? row.legacy_orders_ready, row.status === "ready_for_notification" ? ordersCount : 0, ); const notReadyCount = toNumber( row.not_ready_count ?? row.orders_not_ready ?? row.legacy_orders_not_ready, Math.max(ordersCount - readyCount, 0), ); ``` - [ ] **Step 4: Run mapping tests** Run: `npm test -- --run src/services/supabase/orderGroupRepository.test.js` Expected: PASS. --- ## Chunk 2: Manual Agreement UI ### Task 3: Replace Native Date Input With Themed Future-Date Picker **Files:** - Modify: `src/components/orders/OrderDetailPanel.jsx` - Test: `src/components/orders/OrderDetailPanel.test.jsx` - [ ] **Step 1: Add date helper functions** Add local helpers near `normalizeDateForInput`: ```js const toDateKey = (date) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); return `${year}-${month}-${day}`; }; const addDays = (date, amount) => { const nextDate = new Date(date); nextDate.setDate(nextDate.getDate() + amount); return nextDate; }; const getTomorrowDateKey = () => toDateKey(addDays(new Date(), 1)); const isFutureDeliveryDate = (value) => Boolean(value) && value >= getTomorrowDateKey(); ``` - [ ] **Step 2: Default the editable form to tomorrow** When the selected group has no valid future `deliveryDate`, initialize the manual form with tomorrow. ```js const normalizedDeliveryDate = normalizeDateForInput(order?.deliveryDate); setDeliveryDate(isFutureDeliveryDate(normalizedDeliveryDate) ? normalizedDeliveryDate : getTomorrowDateKey()); ``` - [ ] **Step 3: Replace ``** Render a styled button that opens a compact 21-day date grid. The grid should use app CSS variables: `--color-card`, `--color-surface`, `--color-border`, `--color-accent`, `--color-accent-soft`, `--color-text`, and `--color-text-muted`. - [ ] **Step 4: Test editable controls** Update `OrderDetailPanel.test.jsx` so editable markup includes: ```js expect(editableMarkup).toContain("Ближайшие даты"); expect(editableMarkup).toContain("Согласовать"); ``` - [ ] **Step 5: Run component test** Run: `npm test -- --run src/components/orders/OrderDetailPanel.test.jsx` Expected: PASS. ### Task 4: Replace Native Time Select With Themed Segmented Buttons **Files:** - Modify: `src/components/orders/OrderDetailPanel.jsx` - Test: `src/components/orders/OrderDetailPanel.test.jsx` - [ ] **Step 1: Remove `Select` import from `OrderDetailPanel.jsx`** The manual agreement block should no longer use the native dropdown. - [ ] **Step 2: Render time options as buttons** Use `DELIVERY_TIME_OPTIONS`: ```js const DELIVERY_TIME_OPTIONS = ["Первая половина дня", "Вторая половина дня"]; ``` Each option should be a `button type="button"` with `aria-pressed={deliveryTime === option}` and selected styling through app CSS variables. - [ ] **Step 3: Ensure mobile layout is comfortable** Use a responsive grid: ```jsx
``` - [ ] **Step 4: Run component test** Run: `npm test -- --run src/components/orders/OrderDetailPanel.test.jsx` Expected: PASS. --- ## Chunk 3: Validation And Server Enforcement ### Task 5: Block Today And Past Dates In The UI **Files:** - Modify: `src/components/orders/OrderDetailPanel.jsx` - Test: `src/components/orders/OrderDetailPanel.test.jsx` - [ ] **Step 1: Add submit validation** Before calling `onSaveManualDeliveryChoice`, check: ```js if (!isFutureDeliveryDate(deliveryDate)) { setFormMessage("Выберите дату доставки позже сегодняшнего дня."); return; } ``` - [ ] **Step 2: Add a client-side interaction test if the test setup supports events** If this component is only tested with `renderToStaticMarkup`, keep validation covered by a server-side Edge Function test/check instead. Do not add a brittle DOM test just to satisfy coverage. - [ ] **Step 3: Run component tests** Run: `npm test -- --run src/components/orders/OrderDetailPanel.test.jsx` Expected: PASS. ### Task 6: Enforce Future Dates In Edge Function **Files:** - Modify: `supabase/functions/update-order-group-delivery-choice/index.ts` - [ ] **Step 1: Add date comparison helpers** ```ts const getTodayKey = () => new Date().toISOString().slice(0, 10); const isFutureDeliveryDate = (value: string) => isValidDate(value) && value > getTodayKey(); ``` - [ ] **Step 2: Replace date validation** Change: ```ts if (!isValidDate(deliveryDate)) { return jsonResponse({ ok: false, error: "Valid deliveryDate is required" }, 400, corsHeaders); } ``` to: ```ts if (!isFutureDeliveryDate(deliveryDate)) { return jsonResponse({ ok: false, error: "Future deliveryDate is required" }, 400, corsHeaders); } ``` - [ ] **Step 3: Run Deno check** Run: `deno check supabase/functions/update-order-group-delivery-choice/index.ts` Expected: PASS. - [ ] **Step 4: Deploy function after local verification** Run when ready: ```bash supabase functions deploy update-order-group-delivery-choice ``` Expected: deployed function rejects today/past dates even if someone bypasses the UI. --- ## Chunk 4: Detail Card Cleanup ### Task 7: Remove Confusing Legacy Fields From The Card **Files:** - Modify: `src/components/orders/OrderDetailPanel.jsx` - Test: `src/components/orders/OrderDetailPanel.test.jsx` - [ ] **Step 1: Change empty value wording** Use `Нет данных` for generic missing data instead of `Не указано`, except for binary fields where the user expects `Да` or `Нет`. ```js const renderValue = (value) => value || "Нет данных"; ``` - [ ] **Step 2: Show SMS as a binary value** Change the SMS field: ```jsx

{order.smsSentAt ? "Да" : "Нет"}

``` - [ ] **Step 3: Hide legacy customer** Remove the visible `Клиент из старых данных` field from the card. - [ ] **Step 4: Hide empty technical fields** Only render `Создано из обмена` and `Ключ источника` when values exist. Do not show `Нет данных` for these technical fields. - [ ] **Step 5: Run component tests** Run: `npm test -- --run src/components/orders/OrderDetailPanel.test.jsx` Expected: PASS. --- ## Chunk 5: Verification ### Task 8: Run Focused Tests **Files:** - Test: `src/components/orders/OrderDetailPanel.test.jsx` - Test: `src/services/supabase/orderGroupRepository.test.js` - Test: `src/pages/DashboardPage.test.jsx` - [ ] **Step 1: Run focused frontend tests** Run: ```bash npm test -- --run src/components/orders/OrderDetailPanel.test.jsx src/services/supabase/orderGroupRepository.test.js src/pages/DashboardPage.test.jsx ``` Expected: PASS. - [ ] **Step 2: Run Edge Function type check** Run: ```bash deno check supabase/functions/update-order-group-delivery-choice/index.ts ``` Expected: PASS. - [ ] **Step 3: Run production build** Run: ```bash npm run build ``` Expected: PASS. ### Task 9: Manual Browser Verification **Files:** - Manual check: `http://localhost:5174/dashboard` - [ ] **Step 1: Open an order group as manager/logistician** Expected: card shows order counters based on available orders, not misleading `0/0` when `order_numbers` has values. - [ ] **Step 2: Check manual agreement block** Expected: date picker starts from tomorrow, not today or an old customer date. - [ ] **Step 3: Select date and half-day** Expected: controls visually match dark/light theme, no native browser dropdown styling dominates the UI. - [ ] **Step 4: Save manual agreement** Expected: valid future date saves; today/past date cannot be sent from UI. - [ ] **Step 5: Check additional data block** Expected: `SMS отправлено` shows `Да` or `Нет`; no `Клиент из старых данных`; empty technical fields are hidden. --- ## Commit Plan - [ ] **Commit 1: Data mapping** ```bash git add src/services/supabase/orderGroupRepository.js src/services/supabase/orderGroupRepository.test.js git commit -m "fix(order-groups): normalize delivery group counters" ``` - [ ] **Commit 2: Manual agreement UI** ```bash git add src/components/orders/OrderDetailPanel.jsx src/components/orders/OrderDetailPanel.test.jsx git commit -m "feat(order-groups): improve manual delivery agreement" ``` - [ ] **Commit 3: Edge Function validation** ```bash git add supabase/functions/update-order-group-delivery-choice/index.ts git commit -m "fix(edge): require future delivery dates" ``` --- ## Rollout Notes - The frontend change is immediate after deploy. - The Edge Function must be deployed separately with `supabase functions deploy update-order-group-delivery-choice`. - Existing rows with old `delivery_date` values will still contain those dates in the database. This plan prevents new manual agreements from writing today or past dates. - Temporary open RLS used for testing should be revisited before production hardening.