Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions specs/product/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,10 @@ Cancelled`): drafts are fully editable (add/edit/remove lines, cancel);
exist in the status enum for later phases; nothing sets them yet, and only
`Confirmed` orders can be voided.)

Closing the order panel only hides it; it does not cancel the order. The panel
stays closed even if an earlier open finishes loading. Use **open** again to
view the order (#712).

**Worker sale allocation policy (#612)** — one farm setting deciding how a
restricted plain **Worker's** confirmation may draw stock: `AssignedFlocksOnly`
(default for every farm) tries the worker's own assigned flocks first, and
Expand Down
5 changes: 3 additions & 2 deletions web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2790,7 +2790,8 @@ export const en = {
+ "read-only). The quantity counts <strong>units, not eggs</strong> — the field is labeled with the "
+ "unit and shows the resulting egg count while you type (2 trays = 60 eggs, not 60 trays). Each "
+ "line remembers how many eggs its unit held when it was added, so redefining a carton later never "
+ "changes old orders.",
+ "changes old orders. Choose <strong>close</strong> while an order loads to keep the panel closed; "
+ "choose <strong>open</strong> again to view it.",
salesConfirming:
"<strong>Confirming</strong> an order allocates real stock — oldest lots first — and is the point "
+ "where inventory changes hands.",
Expand Down Expand Up @@ -3312,7 +3313,7 @@ export const en = {
"Undo of a mistaken confirm — stock returns to the exact lots it came from. Needs a reason.",

glossaryCancelOrderTerm: "Cancel (order)",
glossaryCancelOrderDef: "Close a draft that never happened. No stock involved.",
glossaryCancelOrderDef: "Close a draft that never happened. No stock involved. The close link only hides the panel; use open to view it again.",

glossaryInventoryItemTerm: "Inventory item",
glossaryInventoryItemDef:
Expand Down
5 changes: 3 additions & 2 deletions web/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2264,7 +2264,8 @@ export const es = {
+ "cuenta <strong>unidades, no huevos</strong> — el campo indica la unidad y muestra el total de huevos "
+ "resultante mientras escribe (2 bandejas = 60 huevos, no 60 bandejas). Cada línea "
+ "recuerda cuántos huevos contenía su unidad cuando se agregó, así que redefinir un cartón más tarde "
+ "nunca cambia pedidos antiguos.",
+ "nunca cambia pedidos antiguos. Si el pedido aún se está cargando, <strong>cerrar</strong> mantiene "
+ "el panel cerrado. Use <strong>abrir</strong> de nuevo para verlo.",
salesConfirming:
"<strong>Confirmar</strong> un pedido asigna existencias reales — los lotes más antiguos primero — y "
+ "es el punto donde el inventario cambia de manos.",
Expand Down Expand Up @@ -2808,7 +2809,7 @@ export const es = {
+ "vinieron. Requiere un motivo.",

glossaryCancelOrderTerm: "Cancelar (pedido)",
glossaryCancelOrderDef: "Cerrar un borrador que nunca se concretó. No involucra existencias.",
glossaryCancelOrderDef: "Cerrar un borrador que nunca se concretó. No involucra existencias. El enlace cerrar solo oculta el panel; use abrir para verlo de nuevo.",

glossaryInventoryItemTerm: "Artículo de inventario",
glossaryInventoryItemDef:
Expand Down
5 changes: 3 additions & 2 deletions web/src/i18n/tl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2351,7 +2351,8 @@ export const tl = {
+ "Ang dami ay bilang ng <strong>mga unit, hindi mga itlog</strong> — nakasaad sa field ang unit at "
+ "ipinapakita nito ang kabuuang itlog habang nagta-type (2 tray = 60 itlog, hindi 60 tray). "
+ "Naaalala ng bawat linya kung ilang itlog ang laman ng unit nito noong idinagdag ito, kaya hindi "
+ "kailanman binabago ng muling pagtukoy sa isang karton ang mga lumang order.",
+ "kailanman binabago ng muling pagtukoy sa isang karton ang mga lumang order. Kung naglo-load pa ang order, "
+ "mananatiling sarado ang panel kapag pinili ang <strong>isara</strong>. Piliin muli ang <strong>buksan</strong> para tingnan ang order.",
salesConfirming:
"Ang <strong>Pagkumpirma</strong> ng isang order ay naglalaan ng aktwal na stock — pinakalumang lote "
+ "muna — at ito ang sandali kung saan lumilipat ang inventory.",
Expand Down Expand Up @@ -2905,7 +2906,7 @@ export const tl = {
+ "Kailangan ng dahilan.",

glossaryCancelOrderTerm: "Kanselahin (order)",
glossaryCancelOrderDef: "Isara ang isang draft na hindi natuloy. Walang kinalaman na stock.",
glossaryCancelOrderDef: "Isara ang isang draft na hindi natuloy. Walang kinalaman na stock. Itinatago lang ng isara ang panel; piliin ang buksan para tingnan itong muli.",

glossaryInventoryItemTerm: "Item sa imbentaryo",
glossaryInventoryItemDef:
Expand Down
73 changes: 73 additions & 0 deletions web/src/routes/SalesPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2356,3 +2356,76 @@ it.each([false, true])("discards the line editor on Close before same-order relo
expect(within(reopened).getByLabelText(i18n.t("sales:editQuantityAriaLabel"))).toHaveValue(9);
expect(within(reopened).getByLabelText(i18n.t("sales:editUnitPriceAriaLabel"))).toHaveValue(4);
});

// #712 — primary Open reads respect a later panel dismissal.
describe.each(["Draft", "Confirmed"] as const)("Sales Open dismissal (#712), %s panel", (status) => {
it.each([false, true])("stays closed after a held Open settles (different order: %s), then allows a fresh Open", async (different) => {
const original: SalesOrder = { ...DRAFT_TWO, status };
const target: SalesOrder = different
? { ...original, id: "other-order", referenceNumber: "SO-OTHER" }
: original;
mockListOrders.mockResolvedValue(different ? [original, target] : [original]);
mockGetOrder.mockResolvedValue(original);
await renderReady();
const openRow = async (order: SalesOrder) => {
await act(async () => {
fireEvent.click(within(screen.getByRole("row", { name: new RegExp(order.referenceNumber) }))
.getByRole("button", { name: i18n.t("sales:open") }));
});
};
await openRow(original);
expect(document.querySelector(".order-panel")).not.toBeNull();

let resolveRead!: (order: SalesOrder) => void;
mockGetOrder.mockReturnValueOnce(new Promise<SalesOrder>((resolve) => { resolveRead = resolve; }));
await openRow(target);
const close = screen.getByRole("button", { name: i18n.t("sales:close") });
expect(close).toBeEnabled();
expect(within(screen.getByRole("row", { name: new RegExp(target.referenceNumber) }))
.getByRole("button", { name: i18n.t("sales:open") })).toBeDisabled();
fireEvent.click(close);
expect(document.querySelector(".order-panel")).toBeNull();
await act(async () => { resolveRead(target); });
expect(document.querySelector(".order-panel")).toBeNull();

mockGetOrder.mockResolvedValue(target);
await openRow(target);
expect(document.querySelector(".order-panel")).not.toBeNull();
expect(within(document.querySelector<HTMLElement>(".order-panel")!)
.getByText(new RegExp(target.referenceNumber))).toBeInTheDocument();
});
});

describe("Sales primary Open controls (#712)", () => {
it("opens from no panel", async () => {
mockListOrders.mockResolvedValue([DRAFT_TWO]);
mockGetOrder.mockResolvedValue(DRAFT_TWO);
await renderReady();
expect(document.querySelector(".order-panel")).toBeNull();
await act(async () => { fireEvent.click(screen.getByRole("button", { name: i18n.t("sales:open") })); });
expect(document.querySelector(".order-panel")).not.toBeNull();
});

it("shows an Open failure and allows retry", async () => {
mockListOrders.mockResolvedValue([DRAFT_TWO]);
mockGetOrder.mockRejectedValueOnce(new Error("Order read failed")).mockResolvedValue(DRAFT_TWO);
await renderReady();
await act(async () => { fireEvent.click(screen.getByRole("button", { name: i18n.t("sales:open") })); });
expect(document.querySelector(".order-panel")).toBeNull();
expect(screen.getByText("Order read failed")).toBeInTheDocument();
await act(async () => { fireEvent.click(screen.getByRole("button", { name: i18n.t("sales:open") })); });
expect(document.querySelector(".order-panel")).not.toBeNull();
expect(screen.queryByText("Order read failed")).not.toBeInTheDocument();
});

it("keeps a dismissed Open failure on the page and the panel closed", async () => {
await openOrder(DRAFT_TWO, /Grade A Dozen/);
let rejectRead!: (reason: Error) => void;
mockGetOrder.mockReturnValueOnce(new Promise<SalesOrder>((_, reject) => { rejectRead = reject; }));
await act(async () => { fireEvent.click(screen.getByRole("button", { name: i18n.t("sales:open") })); });
fireEvent.click(screen.getByRole("button", { name: i18n.t("sales:close") }));
await act(async () => { rejectRead(new Error("Dismissed order read failed")); });
expect(document.querySelector(".order-panel")).toBeNull();
expect(screen.getByText("Dismissed order read failed")).toBeInTheDocument();
});
});
9 changes: 6 additions & 3 deletions web/src/routes/SalesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,8 @@ export function SalesPage() {
// (#474 → #477 → #479 → #625 → #702); the rules and the incidents that
// earned them live with the hook. Sales-specific is only WHICH scopes own a
// dialog, and that the page-level success message clears on each attempt.
const { busy, isPending, errors, run, openDialog, dismissDialog } = useDialogAction(
["create-order", "record-payment"],
const { busy, isPending, errors, run, openDialog, dismissDialog, startLoad } = useDialogAction(
["create-order", "record-payment", "order-panel"],
{ onAttempt: () => setMessage(null) },
);
// Pulled out for the payments effect's dependency list: it is stable, and
Expand Down Expand Up @@ -454,6 +454,7 @@ export function SalesPage() {
};

const closeOrderPanel = () => {
dismissDialog("order-panel");
setActive(null);
// Dismissal discards the draft now; a later Open must use its fetched line.
setEditItemId(null);
Expand Down Expand Up @@ -705,7 +706,9 @@ export function SalesPage() {
// Always fetch fresh on open — the list row may be stale relative to
// mutations made through the panel since the list was loaded.
const onOpen = (id: string) => run(`open:${id}`, async () => {
setActive(await getOrder(id));
const current = startLoad("order-panel");
const loaded = await getOrder(id);
if (current()) setActive(loaded);
});

// A list failure no longer replaces the workspace: it renders as a banner
Expand Down
Loading