@argusoft/medplat-product-management 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{BrandsPage-CSA3meke.js → BrandsPage-NSvaMXWg.js} +76 -75
  2. package/dist/BrandsPage-NSvaMXWg.js.map +1 -0
  3. package/dist/{CategoriesPage-BrspzkJL.js → CategoriesPage-aZBZrDOi.js} +8 -8
  4. package/dist/{CategoriesPage-BrspzkJL.js.map → CategoriesPage-aZBZrDOi.js.map} +1 -1
  5. package/dist/{DashboardPage-CqaquOs2.js → DashboardPage-DjwlOhrd.js} +22 -22
  6. package/dist/DashboardPage-DjwlOhrd.js.map +1 -0
  7. package/dist/{FacilityPicker-Dv_H95WW.js → FacilityPicker-Chh-ZaR0.js} +2 -2
  8. package/dist/{FacilityPicker-Dv_H95WW.js.map → FacilityPicker-Chh-ZaR0.js.map} +1 -1
  9. package/dist/{InventoryPage-CPbzfDMR.js → InventoryPage-cy76UsMp.js} +109 -119
  10. package/dist/InventoryPage-cy76UsMp.js.map +1 -0
  11. package/dist/{MemberProfileCard-0vjO3Gzd.js → MemberProfileCard-CinPt09F.js} +6 -6
  12. package/dist/MemberProfileCard-CinPt09F.js.map +1 -0
  13. package/dist/{PaymentCreatePage-BYmDZAIB.js → PaymentCreatePage-BJe3LyFF.js} +148 -153
  14. package/dist/PaymentCreatePage-BJe3LyFF.js.map +1 -0
  15. package/dist/{PaymentsPage-BXPvKon-.js → PaymentsPage-DxHoKksN.js} +11 -11
  16. package/dist/{PaymentsPage-BXPvKon-.js.map → PaymentsPage-DxHoKksN.js.map} +1 -1
  17. package/dist/PickupCreatePage-Cq8zJ62Y.js +705 -0
  18. package/dist/PickupCreatePage-Cq8zJ62Y.js.map +1 -0
  19. package/dist/{PickupsPage-D66_Wta4.js → PickupsPage-BOygBQAb.js} +66 -74
  20. package/dist/PickupsPage-BOygBQAb.js.map +1 -0
  21. package/dist/{PmTable-B9970kPY.js → PmTable-CCpaJEA_.js} +4 -4
  22. package/dist/PmTable-CCpaJEA_.js.map +1 -0
  23. package/dist/{ProductEditPage-B8qFTCW9.js → ProductEditPage-DmdHO-fS.js} +109 -109
  24. package/dist/ProductEditPage-DmdHO-fS.js.map +1 -0
  25. package/dist/{ProductsPage-C7_r9cKn.js → ProductsPage-BglMe8EM.js} +6 -6
  26. package/dist/{ProductsPage-C7_r9cKn.js.map → ProductsPage-BglMe8EM.js.map} +1 -1
  27. package/dist/PurchaseCreatePage-1tYk41RS.js +590 -0
  28. package/dist/PurchaseCreatePage-1tYk41RS.js.map +1 -0
  29. package/dist/{PurchaseRequestDetailPage-C7aAwEqw.js → PurchaseRequestDetailPage-BYLx3aBI.js} +14 -14
  30. package/dist/PurchaseRequestDetailPage-BYLx3aBI.js.map +1 -0
  31. package/dist/{PurchaseRequestsPage-BwCkbpaX.js → PurchaseRequestsPage-D2yorc4s.js} +4 -4
  32. package/dist/{PurchaseRequestsPage-BwCkbpaX.js.map → PurchaseRequestsPage-D2yorc4s.js.map} +1 -1
  33. package/dist/{PurchasesPage-CvaZouVy.js → PurchasesPage-CanQTjlw.js} +3 -3
  34. package/dist/{PurchasesPage-CvaZouVy.js.map → PurchasesPage-CanQTjlw.js.map} +1 -1
  35. package/dist/{SaleCreatePage-DP1Qmltu.js → SaleCreatePage-DxFvPvju.js} +145 -170
  36. package/dist/SaleCreatePage-DxFvPvju.js.map +1 -0
  37. package/dist/{SalesPage-CO3cdYX5.js → SalesPage-CPMrdr5-.js} +12 -12
  38. package/dist/SalesPage-CPMrdr5-.js.map +1 -0
  39. package/dist/SettingsPage-CEy922rW.js +662 -0
  40. package/dist/SettingsPage-CEy922rW.js.map +1 -0
  41. package/dist/{StorageUnitsPage-B9Hr_RkW.js → StorageUnitsPage-DfLT6xC_.js} +13 -13
  42. package/dist/{StorageUnitsPage-B9Hr_RkW.js.map → StorageUnitsPage-DfLT6xC_.js.map} +1 -1
  43. package/dist/{TransactionsPage-HKIGwEZN.js → TransactionsPage-KFUJ4O5g.js} +4 -4
  44. package/dist/TransactionsPage-KFUJ4O5g.js.map +1 -0
  45. package/dist/TransferCreatePage-k1tsRGB3.js +315 -0
  46. package/dist/TransferCreatePage-k1tsRGB3.js.map +1 -0
  47. package/dist/TransfersPage-CDafSVO9.js +470 -0
  48. package/dist/TransfersPage-CDafSVO9.js.map +1 -0
  49. package/dist/VialsPage-DPsYFgaQ.js +897 -0
  50. package/dist/VialsPage-DPsYFgaQ.js.map +1 -0
  51. package/dist/{currency-ClRLIEg2.js → currency-bVQ7aEk2.js} +141 -506
  52. package/dist/currency-bVQ7aEk2.js.map +1 -0
  53. package/dist/index.js +1 -1
  54. package/package.json +1 -1
  55. package/dist/BrandsPage-CSA3meke.js.map +0 -1
  56. package/dist/DashboardPage-CqaquOs2.js.map +0 -1
  57. package/dist/InventoryPage-CPbzfDMR.js.map +0 -1
  58. package/dist/MemberProfileCard-0vjO3Gzd.js.map +0 -1
  59. package/dist/PaymentCreatePage-BYmDZAIB.js.map +0 -1
  60. package/dist/PickupCreatePage-CMGwoAXj.js +0 -426
  61. package/dist/PickupCreatePage-CMGwoAXj.js.map +0 -1
  62. package/dist/PickupsPage-D66_Wta4.js.map +0 -1
  63. package/dist/PmTable-B9970kPY.js.map +0 -1
  64. package/dist/ProductEditPage-B8qFTCW9.js.map +0 -1
  65. package/dist/PurchaseCreatePage-CDfNDcKG.js +0 -627
  66. package/dist/PurchaseCreatePage-CDfNDcKG.js.map +0 -1
  67. package/dist/PurchaseRequestDetailPage-C7aAwEqw.js.map +0 -1
  68. package/dist/SaleCreatePage-DP1Qmltu.js.map +0 -1
  69. package/dist/SalesPage-CO3cdYX5.js.map +0 -1
  70. package/dist/SettingsPage-DvVRwSFw.js +0 -711
  71. package/dist/SettingsPage-DvVRwSFw.js.map +0 -1
  72. package/dist/TransactionsPage-HKIGwEZN.js.map +0 -1
  73. package/dist/TransferCreatePage-DMuZqQ3c.js +0 -363
  74. package/dist/TransferCreatePage-DMuZqQ3c.js.map +0 -1
  75. package/dist/TransfersPage-COIyouL4.js +0 -480
  76. package/dist/TransfersPage-COIyouL4.js.map +0 -1
  77. package/dist/VialsPage-BNPUwGCU.js +0 -922
  78. package/dist/VialsPage-BNPUwGCU.js.map +0 -1
  79. package/dist/currency-ClRLIEg2.js.map +0 -1
@@ -0,0 +1,315 @@
1
+ import { M as e, P as t, et as n, i as r, j as i, n as a, o, r as s } from "./currency-bVQ7aEk2.js";
2
+ import { o as c, r as l } from "./PmTable-CCpaJEA_.js";
3
+ import { t as u } from "./format-UOyOu75i.js";
4
+ import { useMemo as d, useState as f } from "react";
5
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
6
+ import { v4 as h } from "uuid";
7
+ import { Alert as g, Autocomplete as _, Box as v, Button as y, Card as b, CardContent as x, Chip as S, Grid as C, Stack as w, TextField as T } from "@mui/material";
8
+ import { showToast as E } from "@/common/toaster/toaster";
9
+ import { useNavigate as D } from "react-router-dom";
10
+ import O from "@mui/icons-material/SaveRounded";
11
+ //#region src/views/TransferCreatePage.jsx
12
+ function k(e) {
13
+ return !(e.ownerType !== "FACILITY" || Number(e.qtyRemaining ?? 0) <= 0 || e.status === "EXPIRED" || e.status === "EXHAUSTED" || e.status === "QUARANTINED");
14
+ }
15
+ function A() {
16
+ let A = D(), [j, M] = f([]), [N, P] = f([]), [F, I] = f([]), [ee, te] = f(!0), [L, R] = f(!1), [z, B] = f(""), [V, H] = f({
17
+ id: h(),
18
+ sourceFacilityId: "",
19
+ sourceBatchId: "",
20
+ destFacilityId: "",
21
+ destStorageUnitId: "",
22
+ qty: 1,
23
+ notes: ""
24
+ });
25
+ r("update.manage.productManagement.transfer", () => {
26
+ async function n() {
27
+ try {
28
+ let [n, r, a] = await Promise.all([
29
+ t(),
30
+ e(),
31
+ i({ activeOnly: !0 })
32
+ ]);
33
+ M(n), P(r), I(a);
34
+ } catch (e) {
35
+ B(e?.response?.data?.message ?? e?.message ?? "Failed to load reference data");
36
+ } finally {
37
+ te(!1);
38
+ }
39
+ }
40
+ n();
41
+ }, []);
42
+ function U(e, t) {
43
+ H((n) => ({
44
+ ...n,
45
+ [e]: t
46
+ }));
47
+ }
48
+ function W(e) {
49
+ H((t) => ({
50
+ ...t,
51
+ sourceFacilityId: e,
52
+ sourceBatchId: ""
53
+ }));
54
+ }
55
+ function G(e) {
56
+ H((t) => ({
57
+ ...t,
58
+ destFacilityId: e,
59
+ destStorageUnitId: ""
60
+ }));
61
+ }
62
+ let K = d(() => V.sourceFacilityId ? N.filter(k).filter((e) => e.ownerId === V.sourceFacilityId) : [], [N, V.sourceFacilityId]), q = d(() => N.find((e) => e.id === V.sourceBatchId) ?? null, [N, V.sourceBatchId]), J = d(() => V.destFacilityId ? F.filter((e) => e.ownerId === V.destFacilityId) : [], [F, V.destFacilityId]), Y = d(() => j.filter((e) => e.id !== V.sourceFacilityId), [j, V.sourceFacilityId]), X = Number(V.qty), Z = Number(q?.qtyRemaining ?? 0), Q = q ? !Number.isInteger(X) || X <= 0 ? "Enter a whole number greater than 0." : X > Z ? `Only ${Z} available in this batch.` : "" : "", $ = !!V.sourceFacilityId && !!V.sourceBatchId && !!V.destFacilityId && !Q;
63
+ async function ne() {
64
+ if ($) {
65
+ R(!0), B("");
66
+ try {
67
+ let e = await n({
68
+ id: V.id,
69
+ sourceBatchId: V.sourceBatchId,
70
+ destFacilityId: V.destFacilityId,
71
+ destStorageUnitId: V.destStorageUnitId || null,
72
+ qty: X,
73
+ notes: V.notes || null
74
+ });
75
+ E({
76
+ message: `Shipped ${e.qty} ${e.qty === 1 ? "unit" : "units"} to ${e.destFacilityName ?? "destination"}.`,
77
+ type: "success"
78
+ }), A("/ui/medplat/manage/pm/transfers");
79
+ } catch (e) {
80
+ B(e?.response?.data?.message ?? e?.message ?? "Failed to ship transfer");
81
+ } finally {
82
+ R(!1);
83
+ }
84
+ }
85
+ }
86
+ return /* @__PURE__ */ m(v, { children: [/* @__PURE__ */ p(s, {}), /* @__PURE__ */ p(a, {
87
+ perm: "update.manage.productManagement.transfer",
88
+ title: "You don’t have permission to ship transfers",
89
+ children: /* @__PURE__ */ m(o, {
90
+ title: "Ship Stock Transfer",
91
+ subtitle: "Send stock between facilities. The sender loses stock right away. The receiver creates the batch when they confirm receipt.",
92
+ actions: /* @__PURE__ */ m(w, {
93
+ direction: "row",
94
+ spacing: 1,
95
+ children: [/* @__PURE__ */ p(y, {
96
+ variant: "outlined",
97
+ onClick: () => A("/ui/medplat/manage/pm/transfers"),
98
+ children: "Cancel"
99
+ }), /* @__PURE__ */ p(y, {
100
+ variant: "contained",
101
+ startIcon: /* @__PURE__ */ p(O, {}),
102
+ disabled: !$ || L,
103
+ onClick: () => void ne(),
104
+ children: L ? "Shipping…" : "Ship Transfer"
105
+ })]
106
+ }),
107
+ children: [
108
+ z ? /* @__PURE__ */ p(g, {
109
+ severity: "error",
110
+ sx: { mb: 2 },
111
+ children: z
112
+ }) : null,
113
+ /* @__PURE__ */ p(b, {
114
+ sx: {
115
+ borderRadius: 1.5,
116
+ mb: 2,
117
+ border: "1px solid",
118
+ borderColor: "divider",
119
+ boxShadow: "0 12px 32px rgba(15, 23, 42, 0.045)"
120
+ },
121
+ children: /* @__PURE__ */ m(x, {
122
+ sx: { p: 2.5 },
123
+ children: [/* @__PURE__ */ p(l, {
124
+ eyebrow: "Step 1",
125
+ title: "Source",
126
+ hint: "Select the sending facility and the batch.",
127
+ sx: { mb: 2 }
128
+ }), /* @__PURE__ */ m(C, {
129
+ container: !0,
130
+ spacing: 2,
131
+ children: [/* @__PURE__ */ p(C, {
132
+ item: !0,
133
+ xs: 12,
134
+ md: 5,
135
+ children: /* @__PURE__ */ p(_, {
136
+ size: "small",
137
+ options: j,
138
+ getOptionLabel: (e) => e?.name ?? "",
139
+ isOptionEqualToValue: (e, t) => e?.id === t?.id,
140
+ value: j.find((e) => e.id === V.sourceFacilityId) ?? null,
141
+ onChange: (e, t) => W(t?.id ?? ""),
142
+ loading: ee,
143
+ renderInput: (e) => /* @__PURE__ */ p(T, {
144
+ ...e,
145
+ required: !0,
146
+ label: "Source Facility",
147
+ placeholder: "Pick the shipping facility"
148
+ })
149
+ })
150
+ }), /* @__PURE__ */ p(C, {
151
+ item: !0,
152
+ xs: 12,
153
+ md: 7,
154
+ children: /* @__PURE__ */ p(c, {
155
+ label: "Source Batch",
156
+ required: !0,
157
+ disabled: !V.sourceFacilityId,
158
+ value: V.sourceBatchId,
159
+ onChange: (e) => U("sourceBatchId", e),
160
+ options: K.map((e) => ({
161
+ value: e.id,
162
+ label: `${e.productName} · qty ${e.qtyRemaining}${e.lotNumber ? ` · ${e.lotNumber}` : ""}${e.expiryDate ? ` · exp ${u(e.expiryDate)}` : ""}`
163
+ })),
164
+ noOptionsText: "No shippable stock at this facility",
165
+ helperText: V.sourceFacilityId ? K.length === 0 ? "This facility has no shippable batches." : "Active facility batches only — quarantined / expired / exhausted stock is hidden." : "Pick a source facility first."
166
+ })
167
+ })]
168
+ })]
169
+ })
170
+ }),
171
+ /* @__PURE__ */ p(b, {
172
+ sx: {
173
+ borderRadius: 1.5,
174
+ mb: 2,
175
+ border: "1px solid",
176
+ borderColor: "divider",
177
+ boxShadow: "0 12px 32px rgba(15, 23, 42, 0.045)"
178
+ },
179
+ children: /* @__PURE__ */ m(x, {
180
+ sx: { p: 2.5 },
181
+ children: [/* @__PURE__ */ p(l, {
182
+ eyebrow: "Step 2",
183
+ title: "Destination",
184
+ hint: "Choose the receiving facility and where to store it. The receiver can change the storage location when they confirm.",
185
+ sx: { mb: 2 }
186
+ }), /* @__PURE__ */ m(C, {
187
+ container: !0,
188
+ spacing: 2,
189
+ children: [/* @__PURE__ */ p(C, {
190
+ item: !0,
191
+ xs: 12,
192
+ md: 6,
193
+ children: /* @__PURE__ */ p(_, {
194
+ size: "small",
195
+ options: Y,
196
+ getOptionLabel: (e) => e?.name ?? "",
197
+ isOptionEqualToValue: (e, t) => e?.id === t?.id,
198
+ value: j.find((e) => e.id === V.destFacilityId) ?? null,
199
+ onChange: (e, t) => G(t?.id ?? ""),
200
+ renderInput: (e) => /* @__PURE__ */ p(T, {
201
+ ...e,
202
+ required: !0,
203
+ label: "Destination Facility",
204
+ placeholder: "Pick the receiving facility",
205
+ helperText: "The sending facility is not shown. You can only transfer between different facilities."
206
+ })
207
+ })
208
+ }), /* @__PURE__ */ p(C, {
209
+ item: !0,
210
+ xs: 12,
211
+ md: 6,
212
+ children: /* @__PURE__ */ p(c, {
213
+ label: "Suggested Storage",
214
+ disabled: !V.destFacilityId || J.length === 0,
215
+ value: V.destStorageUnitId,
216
+ onChange: (e) => U("destStorageUnitId", e),
217
+ options: J.map((e) => ({
218
+ value: e.id,
219
+ label: e.unitName
220
+ })),
221
+ helperText: V.destFacilityId ? J.length === 0 ? "No storage units configured at this facility." : "The receiver can override on confirm." : "Pick a destination facility first."
222
+ })
223
+ })]
224
+ })]
225
+ })
226
+ }),
227
+ q ? /* @__PURE__ */ p(b, {
228
+ sx: {
229
+ borderRadius: 1.5,
230
+ mb: 2,
231
+ border: "1px solid",
232
+ borderColor: "divider",
233
+ boxShadow: "0 12px 32px rgba(15, 23, 42, 0.045)"
234
+ },
235
+ children: /* @__PURE__ */ m(x, {
236
+ sx: { p: 2.5 },
237
+ children: [
238
+ /* @__PURE__ */ p(l, {
239
+ eyebrow: "Step 3",
240
+ title: "Quantity",
241
+ hint: `Available: ${Z}. You can send up to this amount.`,
242
+ sx: { mb: 2 }
243
+ }),
244
+ /* @__PURE__ */ m(C, {
245
+ container: !0,
246
+ spacing: 2,
247
+ children: [/* @__PURE__ */ p(C, {
248
+ item: !0,
249
+ xs: 12,
250
+ md: 4,
251
+ children: /* @__PURE__ */ p(T, {
252
+ required: !0,
253
+ fullWidth: !0,
254
+ size: "small",
255
+ type: "number",
256
+ label: "Ship Quantity",
257
+ inputProps: {
258
+ min: 1,
259
+ max: Z
260
+ },
261
+ value: V.qty,
262
+ onChange: (e) => U("qty", e.target.value),
263
+ error: !!Q,
264
+ helperText: Q || `Out of ${Z} available`
265
+ })
266
+ }), /* @__PURE__ */ p(C, {
267
+ item: !0,
268
+ xs: 12,
269
+ md: 8,
270
+ children: /* @__PURE__ */ p(T, {
271
+ fullWidth: !0,
272
+ size: "small",
273
+ label: "Notes (optional)",
274
+ placeholder: "Tracking number, courier, anything worth recording",
275
+ value: V.notes,
276
+ onChange: (e) => U("notes", e.target.value)
277
+ })
278
+ })]
279
+ }),
280
+ /* @__PURE__ */ m(w, {
281
+ direction: "row",
282
+ spacing: 1,
283
+ sx: { mt: 2 },
284
+ flexWrap: "wrap",
285
+ useFlexGap: !0,
286
+ children: [
287
+ /* @__PURE__ */ p(S, {
288
+ size: "small",
289
+ label: `Product · ${q.productName}`
290
+ }),
291
+ q.lotNumber ? /* @__PURE__ */ p(S, {
292
+ size: "small",
293
+ label: `Lot · ${q.lotNumber}`
294
+ }) : null,
295
+ q.expiryDate ? /* @__PURE__ */ p(S, {
296
+ size: "small",
297
+ label: `Expiry · ${u(q.expiryDate)}`
298
+ }) : null,
299
+ q.storageUnitName ? /* @__PURE__ */ p(S, {
300
+ size: "small",
301
+ label: `From · ${q.storageUnitName}`
302
+ }) : null
303
+ ]
304
+ })
305
+ ]
306
+ })
307
+ }) : null
308
+ ]
309
+ })
310
+ })] });
311
+ }
312
+ //#endregion
313
+ export { A as default };
314
+
315
+ //# sourceMappingURL=TransferCreatePage-k1tsRGB3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TransferCreatePage-k1tsRGB3.js","names":[],"sources":["../src/views/TransferCreatePage.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { v4 as uuidv4 } from 'uuid';\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Card,\n CardContent,\n Chip,\n Grid,\n Stack,\n TextField,\n} from '@mui/material';\nimport SaveRoundedIcon from '@mui/icons-material/SaveRounded';\nimport { showToast } from '@/common/toaster/toaster';\nimport PmPageShell from '@pm/components/PmPageShell';\nimport PmNav from '@pm/components/PmNav';\nimport PmGuard from '@pm/components/PmGuard';\nimport { usePermissionedEffect } from '@pm/utils/permissions';\nimport { PmFormSection, PmSelect } from '@pm/components/PmTable';\nimport {\n getStorageUnits,\n listBatches,\n listFacilities,\n shipTransfer,\n} from '@pm/services/pmApi';\nimport { formatDate } from '@pm/utils/format';\n\n// Only facility-owned, in-stock, non-quarantined, non-terminal batches are\n// shippable. Mirrors the backend's check so the dropdown stays actionable.\nfunction isShippable(batch) {\n if (batch.ownerType !== 'FACILITY') return false;\n if (Number(batch.qtyRemaining ?? 0) <= 0) return false;\n if (batch.status === 'EXPIRED' || batch.status === 'EXHAUSTED' || batch.status === 'QUARANTINED') return false;\n return true;\n}\n\nexport default function TransferCreatePage() {\n const navigate = useNavigate();\n const [facilities, setFacilities] = useState([]);\n const [batches, setBatches] = useState([]);\n const [storageUnits, setStorageUnits] = useState([]);\n const [loading, setLoading] = useState(true);\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState('');\n const [form, setForm] = useState({\n id: uuidv4(),\n sourceFacilityId: '',\n sourceBatchId: '',\n destFacilityId: '',\n destStorageUnitId: '',\n qty: 1,\n notes: '',\n });\n\n usePermissionedEffect('update.manage.productManagement.transfer', () => {\n async function load() {\n try {\n const [f, b, su] = await Promise.all([\n listFacilities(),\n listBatches(),\n getStorageUnits({ activeOnly: true }),\n ]);\n setFacilities(f);\n setBatches(b);\n setStorageUnits(su);\n } catch (err) {\n setError(err?.response?.data?.message ?? err?.message ?? 'Failed to load reference data');\n } finally {\n setLoading(false);\n }\n }\n void load();\n }, []);\n\n function patch(k, v) {\n setForm((f) => ({ ...f, [k]: v }));\n }\n\n function patchSourceFacility(id) {\n setForm((f) => ({ ...f, sourceFacilityId: id, sourceBatchId: '' }));\n }\n\n function patchDestFacility(id) {\n setForm((f) => ({ ...f, destFacilityId: id, destStorageUnitId: '' }));\n }\n\n const eligibleBatches = useMemo(() => {\n if (!form.sourceFacilityId) return [];\n return batches.filter(isShippable).filter((b) => b.ownerId === form.sourceFacilityId);\n }, [batches, form.sourceFacilityId]);\n\n const selectedBatch = useMemo(\n () => batches.find((b) => b.id === form.sourceBatchId) ?? null,\n [batches, form.sourceBatchId],\n );\n\n // Storage units belonging to the chosen destination facility. Receiver can\n // override at confirm time so we keep the dropdown optional.\n const destUnits = useMemo(() => {\n if (!form.destFacilityId) return [];\n return storageUnits.filter((u) => u.ownerId === form.destFacilityId);\n }, [storageUnits, form.destFacilityId]);\n\n // Destination facility list excludes the source — the schema rejects\n // self-transfers (the CHECK constraint enforces source != dest).\n const destFacilities = useMemo(\n () => facilities.filter((f) => f.id !== form.sourceFacilityId),\n [facilities, form.sourceFacilityId],\n );\n\n const qty = Number(form.qty);\n const remaining = Number(selectedBatch?.qtyRemaining ?? 0);\n const qtyError = !selectedBatch\n ? ''\n : !Number.isInteger(qty) || qty <= 0\n ? 'Enter a whole number greater than 0.'\n : qty > remaining\n ? `Only ${remaining} available in this batch.`\n : '';\n const formIsValid =\n !!form.sourceFacilityId\n && !!form.sourceBatchId\n && !!form.destFacilityId\n && !qtyError;\n\n async function save() {\n if (!formIsValid) return;\n setSubmitting(true);\n setError('');\n try {\n const created = await shipTransfer({\n id: form.id,\n sourceBatchId: form.sourceBatchId,\n destFacilityId: form.destFacilityId,\n destStorageUnitId: form.destStorageUnitId || null,\n qty,\n notes: form.notes || null,\n });\n showToast({ message: `Shipped ${created.qty} ${created.qty === 1 ? 'unit' : 'units'} to ${created.destFacilityName ?? 'destination'}.`, type: 'success' });\n navigate('/ui/medplat/manage/pm/transfers');\n } catch (err) {\n setError(err?.response?.data?.message ?? err?.message ?? 'Failed to ship transfer');\n } finally {\n setSubmitting(false);\n }\n }\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'update.manage.productManagement.transfer'} title=\"You don’t have permission to ship transfers\">\n <PmPageShell\n title=\"Ship Stock Transfer\"\n subtitle=\"Send stock between facilities. The sender loses stock right away. The receiver creates the batch when they confirm receipt.\"\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" onClick={() => navigate('/ui/medplat/manage/pm/transfers')}>Cancel</Button>\n <Button variant=\"contained\" startIcon={<SaveRoundedIcon />}\n disabled={!formIsValid || submitting}\n onClick={() => void save()}>\n {submitting ? 'Shipping…' : 'Ship Transfer'}\n </Button>\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <PmFormSection\n eyebrow=\"Step 1\"\n title=\"Source\"\n hint=\"Select the sending facility and the batch.\"\n sx={{ mb: 2 }}\n />\n <Grid container spacing={2}>\n <Grid item xs={12} md={5}>\n <Autocomplete\n size=\"small\"\n options={facilities}\n getOptionLabel={(f) => f?.name ?? ''}\n isOptionEqualToValue={(a, b) => a?.id === b?.id}\n value={facilities.find((f) => f.id === form.sourceFacilityId) ?? null}\n onChange={(_, next) => patchSourceFacility(next?.id ?? '')}\n loading={loading}\n renderInput={(params) => (\n <TextField {...params} required label=\"Source Facility\" placeholder=\"Pick the shipping facility\" />\n )}\n />\n </Grid>\n <Grid item xs={12} md={7}>\n <PmSelect\n label=\"Source Batch\"\n required\n disabled={!form.sourceFacilityId}\n value={form.sourceBatchId}\n onChange={(v) => patch('sourceBatchId', v)}\n options={eligibleBatches.map((b) => ({\n value: b.id,\n label: `${b.productName} · qty ${b.qtyRemaining}`\n + `${b.lotNumber ? ` · ${b.lotNumber}` : ''}`\n + `${b.expiryDate ? ` · exp ${formatDate(b.expiryDate)}` : ''}`,\n }))}\n noOptionsText=\"No shippable stock at this facility\"\n helperText={!form.sourceFacilityId\n ? 'Pick a source facility first.'\n : eligibleBatches.length === 0\n ? 'This facility has no shippable batches.'\n : 'Active facility batches only — quarantined / expired / exhausted stock is hidden.'}\n />\n </Grid>\n </Grid>\n </CardContent>\n </Card>\n\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <PmFormSection\n eyebrow=\"Step 2\"\n title=\"Destination\"\n hint=\"Choose the receiving facility and where to store it. The receiver can change the storage location when they confirm.\"\n sx={{ mb: 2 }}\n />\n <Grid container spacing={2}>\n <Grid item xs={12} md={6}>\n <Autocomplete\n size=\"small\"\n options={destFacilities}\n getOptionLabel={(f) => f?.name ?? ''}\n isOptionEqualToValue={(a, b) => a?.id === b?.id}\n value={facilities.find((f) => f.id === form.destFacilityId) ?? null}\n onChange={(_, next) => patchDestFacility(next?.id ?? '')}\n renderInput={(params) => (\n <TextField {...params} required label=\"Destination Facility\"\n placeholder=\"Pick the receiving facility\"\n helperText=\"The sending facility is not shown. You can only transfer between different facilities.\" />\n )}\n />\n </Grid>\n <Grid item xs={12} md={6}>\n <PmSelect\n label=\"Suggested Storage\"\n disabled={!form.destFacilityId || destUnits.length === 0}\n value={form.destStorageUnitId}\n onChange={(v) => patch('destStorageUnitId', v)}\n options={destUnits.map((u) => ({ value: u.id, label: u.unitName }))}\n helperText={!form.destFacilityId\n ? 'Pick a destination facility first.'\n : destUnits.length === 0\n ? 'No storage units configured at this facility.'\n : 'The receiver can override on confirm.'}\n />\n </Grid>\n </Grid>\n </CardContent>\n </Card>\n\n {selectedBatch ? (\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <PmFormSection\n eyebrow=\"Step 3\"\n title=\"Quantity\"\n hint={`Available: ${remaining}. You can send up to this amount.`}\n sx={{ mb: 2 }}\n />\n <Grid container spacing={2}>\n <Grid item xs={12} md={4}>\n <TextField\n required fullWidth size=\"small\" type=\"number\"\n label=\"Ship Quantity\"\n inputProps={{ min: 1, max: remaining }}\n value={form.qty}\n onChange={(e) => patch('qty', e.target.value)}\n error={!!qtyError}\n helperText={qtyError || `Out of ${remaining} available`}\n />\n </Grid>\n <Grid item xs={12} md={8}>\n <TextField\n fullWidth size=\"small\"\n label=\"Notes (optional)\"\n placeholder=\"Tracking number, courier, anything worth recording\"\n value={form.notes}\n onChange={(e) => patch('notes', e.target.value)}\n />\n </Grid>\n </Grid>\n <Stack direction=\"row\" spacing={1} sx={{ mt: 2 }} flexWrap=\"wrap\" useFlexGap>\n <Chip size=\"small\" label={`Product · ${selectedBatch.productName}`} />\n {selectedBatch.lotNumber ? <Chip size=\"small\" label={`Lot · ${selectedBatch.lotNumber}`} /> : null}\n {selectedBatch.expiryDate ? <Chip size=\"small\" label={`Expiry · ${formatDate(selectedBatch.expiryDate)}`} /> : null}\n {selectedBatch.storageUnitName ? <Chip size=\"small\" label={`From · ${selectedBatch.storageUnitName}`} /> : null}\n </Stack>\n </CardContent>\n </Card>\n ) : null}\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}