From 8deddf26301c559500f9c59c671967aeb0497fbd Mon Sep 17 00:00:00 2001 From: vladtechno Date: Fri, 14 Aug 2026 12:48:53 +1000 Subject: [PATCH] =?UTF-8?q?feat(tyres):=20=D0=BD=D0=B0=20=D0=BC=D0=B0?= =?UTF-8?q?=D1=88=D0=B8=D0=BD=D0=B5=20=D1=81=20=D0=BA=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BB=D0=B5=D0=BA=D1=82=D0=BE=D0=BC=20=E2=80=94=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=20=C2=AB=D0=B4=D0=BE=D0=BB=D0=BE=D0=B6=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=C2=BB=20=D0=B8=D0=BB=D0=B8=20=C2=AB=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B8=D1=82=D1=8C=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Раньше экран требовал закрыть старый комплект (вернули / списали / не найден), даже когда старые колёса остаются на машине и добавляется одно новое. Теперь развилка: доложить — комплект тот же, колёса прибавляются; заменить — прежний порядок с закрытием. В шапке комплекта видно, сколько и когда доложили. Подпись кнопки следует выбору: «Доложить 1 покрышку» вместо «Выдать комплект». Co-Authored-By: Claude Opus 5 --- mechanic-pwa/frontend/src/api/warehouse.ts | 16 ++++ .../src/pages/warehouse/TyresPage.tsx | 95 ++++++++++++++----- 2 files changed, 86 insertions(+), 25 deletions(-) diff --git a/mechanic-pwa/frontend/src/api/warehouse.ts b/mechanic-pwa/frontend/src/api/warehouse.ts index 0575716..c5d8197 100644 --- a/mechanic-pwa/frontend/src/api/warehouse.ts +++ b/mechanic-pwa/frontend/src/api/warehouse.ts @@ -379,6 +379,18 @@ export interface TyreStockRow { fits: boolean; } +/** Докладка в выданный комплект — ещё колесо на ту же машину. */ +export interface TyreAddition { + id: number; + part_id: number; + part_name: string | null; + qty: number; + grade: TyreGrade | null; + created_at: string; + created_by: string | null; + comment: string | null; +} + export interface TyreSet { id: number; car_id: number | null; @@ -400,6 +412,8 @@ export interface TyreSet { grade_in: TyreGrade | null; close_comment: string | null; comment: string | null; + /** Докладки. `qty` выше их уже включает: это все колёса на машине. */ + additions?: TyreAddition[]; } export interface TyreContext { @@ -437,6 +451,8 @@ export async function issueTyres( place_id?: number | null; comment?: string | null; close?: TyreClose | null; + /** Доложить в открытый комплект вместо выдачи нового (тогда close не нужен). */ + add_to_open?: boolean; }, idempotencyKey: string, ): Promise { diff --git a/mechanic-pwa/frontend/src/pages/warehouse/TyresPage.tsx b/mechanic-pwa/frontend/src/pages/warehouse/TyresPage.tsx index 492b6d2..b05dc92 100644 --- a/mechanic-pwa/frontend/src/pages/warehouse/TyresPage.tsx +++ b/mechanic-pwa/frontend/src/pages/warehouse/TyresPage.tsx @@ -53,9 +53,11 @@ function daysOn(iso: string): number { /** Подпись кнопки выдачи. «Комплект» — только когда колёс действительно четыре: * с тех пор как выдавать можно поштучно, на одной покрышке эта подпись врала. */ -function issueLabel(qty: number): string { +function issueLabel(qty: number, addToOpen: boolean): string { + const noun = qty === 1 ? "покрышку" : "покрышки"; + if (addToOpen) return `Доложить ${qty} ${noun}`; if (qty === 4) return "Выдать комплект"; - return `Выдать ${qty} ${qty === 1 ? "покрышку" : "покрышки"}`; + return `Выдать ${qty} ${noun}`; } export default function TyresPage() { @@ -179,6 +181,7 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) { const [comment, setComment] = useState(""); const [saving, setSaving] = useState(false); const [showAll, setShowAll] = useState(false); + const [addToOpen, setAddToOpen] = useState(false); // Ключ идемпотентности живёт, пока форма открыта: ретрай после обрыва связи // должен попасть в ТУ ЖЕ выдачу, а не создать вторую. const [idemKey] = useState(() => crypto.randomUUID()); @@ -208,7 +211,10 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) { } const openSet = ctx.open_set; - const needClose = openSet !== null && closeAction === null; + // «Доложить» — когда старые колёса остаются на машине, а добавляется ещё + // одно-два. Закрывать комплект ради этого нельзя: «списали» врёт на живой + // резине, «вернули» приходует на склад то, что с машины не снимали. + const needClose = openSet !== null && !addToOpen && closeAction === null; const chosen = ctx.stock.find((s) => s.part_id === partId) ?? null; const submit = async () => { @@ -225,16 +231,20 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) { qty, grade, comment: comment.trim() || null, - close: closeAction - ? { - action: closeAction, - grade: closeAction === "returned" ? closeGrade : undefined, - } - : null, + add_to_open: addToOpen, + close: + !addToOpen && closeAction + ? { + action: closeAction, + grade: closeAction === "returned" ? closeGrade : undefined, + } + : null, }, idemKey, ); - toast.success(`Выдано ${qty} шт · ${ctx.car_number ?? ""}`.trim()); + toast.success( + `${addToOpen ? "Доложено" : "Выдано"} ${qty} шт · ${ctx.car_number ?? ""}`.trim(), + ); onDone(); } catch (e) { toast.error(await apiErrorText(e, "Не удалось выдать резину")); @@ -275,20 +285,55 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) { {` (${daysOn(openSet.issued_at)} дн.)`} {openSet.grade_out ? ` · при выдаче ${TYRE_GRADE_LABEL[openSet.grade_out].toLowerCase()}` : ""}

-

Что с ним?

-
- {(Object.keys(CLOSE_LABEL) as TyreClose["action"][]).map((a) => ( - - ))} + {openSet.additions && openSet.additions.length > 0 && ( +

+ из них доложено:{" "} + {openSet.additions + .map((a) => `${a.qty} шт ${fmtDate(a.created_at)}`) + .join(", ")} +

+ )} + + {/* Развилка: доложить к тем колёсам или заменить их целиком. */} +
+ +
- {closeAction === "returned" && ( + + {addToOpen ? ( +

+ Комплект остаётся тем же, колёса добавятся к нему. Размер должен совпадать. +

+ ) : ( + <> +

Что со старым?

+
+ {(Object.keys(CLOSE_LABEL) as TyreClose["action"][]).map((a) => ( + + ))} +
+ + )} + {!addToOpen && closeAction === "returned" && (

Состояние снятой резины

@@ -383,8 +428,8 @@ function IssueForm({ carId, onDone }: { carId: number; onDone: () => void }) { {saving ? "Выдаём…" : needClose - ? "Сначала закройте предыдущий комплект" - : issueLabel(qty)} + ? "Сначала выберите: доложить или заменить" + : issueLabel(qty, addToOpen)}