supersam/docs/superpowers/plans/2026-05-11-manual-delivery-...

454 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `<Input type="date">`**
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
<div className="grid gap-2 sm:grid-cols-2">
```
- [ ] **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
<p className="mt-1 font-medium">{order.smsSentAt ? "Да" : "Нет"}</p>
```
- [ ] **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.