@apptimate/ui 7.2.0 → 7.4.0
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.
- package/package.json +1 -1
- package/src/base-components/FormattedNumberInput.tsx +118 -0
- package/src/base-components/Select.tsx +1 -1
- package/src/common-components/DashboardLayout.tsx +101 -57
- package/src/common-components/PaymentSection.tsx +134 -130
- package/src/common-components/item-wizard/ItemFormWizard.tsx +118 -60
- package/src/common-components/pickers/PartyPicker.tsx +28 -401
- package/src/common-components/pickers/QuickPartyAddModal.tsx +414 -0
- package/src/common-components/pickers/UomPicker.tsx +20 -20
- package/src/common-components/pickers/modals/BatchSelectionModal.tsx +167 -114
- package/src/common-components/transaction/ProductSelectionPanel.tsx +6 -4
- package/src/common-components/transaction/ProductTransactionScreen.tsx +3 -3
- package/src/common-components/transaction/types.ts +2 -0
- package/src/components/shared/ImageUploadComponent.tsx +3 -0
- package/src/finance-components/DirectTransactionDetailModal.tsx +9 -1
- package/src/index.tsx +2 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Search, Plus, X } from "lucide-react";
|
|
2
|
+
import { Search, Plus, X, Save } from "lucide-react";
|
|
3
3
|
import { Modal, ModalFooter } from "../../../base-components/Modal";
|
|
4
4
|
import { Button } from "../../../base-components/Button";
|
|
5
5
|
import { Table, THeader, TBody, TRow, TCell } from "../../../base-components/Table";
|
|
@@ -84,7 +84,7 @@ export function BatchSelectionModal({
|
|
|
84
84
|
const params = new URLSearchParams({ item_id: String(item.id) });
|
|
85
85
|
if (variant) params.append("variant_id", String(variant.id));
|
|
86
86
|
if (warehouseId) params.append("warehouse_id", String(warehouseId));
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
const res = await sendRequest({ url: `${process.env.NEXT_PUBLIC_API_URL}/api/inventory/batches/lookup?${params.toString()}`, method: "GET" });
|
|
89
89
|
if (res.responseData?.result) {
|
|
90
90
|
setBatches(res.responseData.result);
|
|
@@ -127,7 +127,7 @@ export function BatchSelectionModal({
|
|
|
127
127
|
}
|
|
128
128
|
const batch = batches.find(b => String(b.id) === String(batchId));
|
|
129
129
|
if (!batch) return;
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
// Check if already in rows
|
|
132
132
|
if (rows.some(r => String(r.batch_id) === String(batch.id))) {
|
|
133
133
|
toast.error("Batch already added to the list.");
|
|
@@ -150,30 +150,69 @@ export function BatchSelectionModal({
|
|
|
150
150
|
setSelectKey(prev => prev + 1);
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
-
const updateRow = (uid: string, field: keyof BatchRow, value:
|
|
153
|
+
const updateRow = (uid: string, field: keyof BatchRow, value: any) => {
|
|
154
154
|
setRows(rows.map(r => r.uid === uid ? { ...r, [field]: value } : r));
|
|
155
155
|
};
|
|
156
156
|
|
|
157
|
+
const saveBatchToDB = async (row: BatchRow) => {
|
|
158
|
+
if (!item) return;
|
|
159
|
+
const price = parseFloat(row.selling_price);
|
|
160
|
+
if (isNaN(price) || price < 0) {
|
|
161
|
+
toast.error("Please enter a valid selling price to save.");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const payload = {
|
|
166
|
+
item_id: item.id,
|
|
167
|
+
variant_id: variant?.id,
|
|
168
|
+
warehouse_id: warehouseId,
|
|
169
|
+
batch_number: row.batch_number || undefined,
|
|
170
|
+
manufacturing_date: row.manufacturing_date || undefined,
|
|
171
|
+
expiry_date: row.expiry_date || undefined,
|
|
172
|
+
supplier_reference: row.supplier_reference || undefined,
|
|
173
|
+
selling_price: price,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const loadingToast = toast.loading("Saving batch...");
|
|
177
|
+
try {
|
|
178
|
+
const res = await sendRequest({
|
|
179
|
+
url: `${process.env.NEXT_PUBLIC_API_URL}/api/inventory/batches`,
|
|
180
|
+
method: "POST",
|
|
181
|
+
data: payload
|
|
182
|
+
});
|
|
183
|
+
if (res.ok) {
|
|
184
|
+
toast.success("Batch saved successfully", { id: loadingToast });
|
|
185
|
+
const newBatch = res.responseData?.result;
|
|
186
|
+
if (newBatch) {
|
|
187
|
+
setRows(rows.map(r => r.uid === row.uid ? {
|
|
188
|
+
...r,
|
|
189
|
+
isNew: false,
|
|
190
|
+
batch_id: newBatch.id,
|
|
191
|
+
batch_number: newBatch.batch_number
|
|
192
|
+
} : r));
|
|
193
|
+
fetchBatches();
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
toast.error(res.responseData?.message || "Failed to save batch", { id: loadingToast });
|
|
197
|
+
}
|
|
198
|
+
} catch (e: any) {
|
|
199
|
+
toast.error("Failed to save batch", { id: loadingToast });
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
157
203
|
const removeRow = (uid: string) => {
|
|
158
204
|
setRows(rows.filter(r => r.uid !== uid));
|
|
159
205
|
};
|
|
160
206
|
|
|
161
207
|
const handleConfirm = () => {
|
|
162
208
|
const selections: { batch: TransactionBatch; quantity: number }[] = [];
|
|
163
|
-
|
|
209
|
+
|
|
164
210
|
for (const row of rows) {
|
|
165
211
|
const qty = parseFloat(row.quantity);
|
|
166
212
|
if (isNaN(qty) || qty <= 0) {
|
|
167
213
|
toast.error("Please enter a valid quantity for all selected batches.");
|
|
168
214
|
return;
|
|
169
215
|
}
|
|
170
|
-
if (row.isNew) {
|
|
171
|
-
const price = parseFloat(row.selling_price);
|
|
172
|
-
if (isNaN(price) || price < 0) {
|
|
173
|
-
toast.error("Please enter a valid selling price for new batches.");
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
216
|
if (!row.isNew && !allowCreate && row.qty_available !== undefined && qty > row.qty_available) {
|
|
178
217
|
toast.error(`Quantity exceeds available stock for batch ${row.batch_number}.`);
|
|
179
218
|
return;
|
|
@@ -206,37 +245,39 @@ export function BatchSelectionModal({
|
|
|
206
245
|
return (
|
|
207
246
|
<Modal isOpen={isOpen} onClose={onClose} size="xl" title={`Batch Selection - ${item?.name} ${variant ? `(${variant.variant_name})` : ""}`}>
|
|
208
247
|
<div className="flex flex-col gap-4">
|
|
209
|
-
|
|
248
|
+
|
|
210
249
|
<div className="flex justify-between items-center">
|
|
211
250
|
<div className="flex gap-2 items-center">
|
|
212
251
|
{allowCreate && (
|
|
213
252
|
<Button size="small" onClick={handleAddRow} icon={<Plus size={16} />}>New Batch</Button>
|
|
214
253
|
)}
|
|
215
254
|
<div className="w-64">
|
|
216
|
-
<SearchableSelect
|
|
255
|
+
<SearchableSelect
|
|
217
256
|
key={selectKey}
|
|
218
257
|
options={batches as any[]}
|
|
219
258
|
onChange={(val) => {
|
|
220
|
-
if(val) handleSelectExisting(val as string | number);
|
|
259
|
+
if (val) handleSelectExisting(val as string | number);
|
|
221
260
|
}}
|
|
222
261
|
placeholder="Select Existing Batch..."
|
|
223
|
-
option={{
|
|
224
|
-
|
|
225
|
-
<div className="flex
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
262
|
+
option={{
|
|
263
|
+
label: 'batch_number', value: 'id', renderOption: (opt: any) => (
|
|
264
|
+
<div className="flex flex-col w-full py-0.5 gap-0.5">
|
|
265
|
+
<div className="flex justify-between items-center w-full gap-2">
|
|
266
|
+
<span className="truncate font-medium">{opt.batch_number}</span>
|
|
267
|
+
{opt.qty_available !== undefined && (
|
|
268
|
+
<span className="text-[10px] font-medium text-foreground-subtle bg-surface-hover px-1.5 py-0.5 rounded whitespace-nowrap shrink-0">
|
|
269
|
+
{Number(opt.qty_available).toFixed(2)} in stock
|
|
270
|
+
</span>
|
|
271
|
+
)}
|
|
272
|
+
</div>
|
|
273
|
+
{opt.expiry_date && (
|
|
274
|
+
<span className="text-[10px] text-foreground-subtle">
|
|
275
|
+
Exp: {opt.expiry_date.substring(0, 10)}
|
|
230
276
|
</span>
|
|
231
277
|
)}
|
|
232
278
|
</div>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Exp: {opt.expiry_date.substring(0, 10)}
|
|
236
|
-
</span>
|
|
237
|
-
)}
|
|
238
|
-
</div>
|
|
239
|
-
) }}
|
|
279
|
+
)
|
|
280
|
+
}}
|
|
240
281
|
/>
|
|
241
282
|
</div>
|
|
242
283
|
</div>
|
|
@@ -265,93 +306,105 @@ export function BatchSelectionModal({
|
|
|
265
306
|
rows.map(row => {
|
|
266
307
|
const batchInfo = batches.find(b => String(b.id) === String(row.batch_id));
|
|
267
308
|
const displayQtyAvailable = row.qty_available !== undefined ? row.qty_available : batchInfo?.qty_available;
|
|
268
|
-
|
|
309
|
+
|
|
269
310
|
return (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
)}
|
|
282
|
-
</TCell>
|
|
283
|
-
<TCell label="Mfg Batch#">
|
|
284
|
-
{row.isNew ? (
|
|
285
|
-
<input
|
|
286
|
-
value={row.supplier_reference}
|
|
287
|
-
onChange={(e) => updateRow(row.uid, 'supplier_reference', e.target.value)}
|
|
288
|
-
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
289
|
-
/>
|
|
290
|
-
) : (
|
|
291
|
-
<div className="text-sm text-gray-500">{row.supplier_reference || '—'}</div>
|
|
292
|
-
)}
|
|
293
|
-
</TCell>
|
|
294
|
-
<TCell label="Mfg Date">
|
|
295
|
-
{row.isNew ? (
|
|
296
|
-
<input
|
|
297
|
-
type="date"
|
|
298
|
-
value={row.manufacturing_date}
|
|
299
|
-
onChange={(e) => updateRow(row.uid, 'manufacturing_date', e.target.value)}
|
|
300
|
-
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
301
|
-
/>
|
|
302
|
-
) : (
|
|
303
|
-
<div className="text-sm text-gray-500">{row.manufacturing_date || '—'}</div>
|
|
304
|
-
)}
|
|
305
|
-
</TCell>
|
|
306
|
-
<TCell label="Expiry Date">
|
|
307
|
-
{row.isNew ? (
|
|
308
|
-
<input
|
|
309
|
-
type="date"
|
|
310
|
-
value={row.expiry_date}
|
|
311
|
-
onChange={(e) => updateRow(row.uid, 'expiry_date', e.target.value)}
|
|
312
|
-
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
313
|
-
/>
|
|
314
|
-
) : (
|
|
315
|
-
<div className="text-sm text-gray-500">{row.expiry_date ? new Date(row.expiry_date).toLocaleDateString() : '—'}</div>
|
|
316
|
-
)}
|
|
317
|
-
</TCell>
|
|
318
|
-
<TCell label="Selling Price">
|
|
319
|
-
{row.isNew ? (
|
|
320
|
-
<input
|
|
321
|
-
type="number"
|
|
322
|
-
value={row.selling_price}
|
|
323
|
-
onChange={(e) => updateRow(row.uid, 'selling_price', e.target.value)}
|
|
324
|
-
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
325
|
-
/>
|
|
326
|
-
) : (
|
|
327
|
-
<div className="text-sm text-gray-500">{row.selling_price || '—'}</div>
|
|
328
|
-
)}
|
|
329
|
-
</TCell>
|
|
330
|
-
<TCell label="Quantity">
|
|
331
|
-
<div className="flex flex-col items-end">
|
|
332
|
-
<input
|
|
333
|
-
type="number"
|
|
334
|
-
min="0"
|
|
335
|
-
max={!allowCreate && displayQtyAvailable !== undefined ? displayQtyAvailable : undefined}
|
|
336
|
-
value={row.quantity}
|
|
337
|
-
onChange={(e) => updateRow(row.uid, 'quantity', e.target.value)}
|
|
338
|
-
placeholder="0"
|
|
339
|
-
className="w-full h-8 px-2 text-[13px] text-right font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
340
|
-
/>
|
|
341
|
-
{!row.isNew && displayQtyAvailable !== undefined && (
|
|
342
|
-
<span className="text-[10px] text-gray-400 mt-0.5">Avail: {Number(displayQtyAvailable).toFixed(4)}</span>
|
|
311
|
+
<TRow key={row.uid}>
|
|
312
|
+
<TCell label="Batch Number">
|
|
313
|
+
{row.isNew ? (
|
|
314
|
+
<input
|
|
315
|
+
value={row.batch_number}
|
|
316
|
+
onChange={(e) => updateRow(row.uid, 'batch_number', e.target.value)}
|
|
317
|
+
placeholder="Auto generate if empty"
|
|
318
|
+
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
319
|
+
/>
|
|
320
|
+
) : (
|
|
321
|
+
<div className="font-mono text-sm font-medium">{row.batch_number}</div>
|
|
343
322
|
)}
|
|
344
|
-
</
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
323
|
+
</TCell>
|
|
324
|
+
<TCell label="Mfg Batch#">
|
|
325
|
+
{row.isNew ? (
|
|
326
|
+
<input
|
|
327
|
+
value={row.supplier_reference}
|
|
328
|
+
onChange={(e) => updateRow(row.uid, 'supplier_reference', e.target.value)}
|
|
329
|
+
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
330
|
+
/>
|
|
331
|
+
) : (
|
|
332
|
+
<div className="text-sm text-gray-500">{row.supplier_reference || '—'}</div>
|
|
333
|
+
)}
|
|
334
|
+
</TCell>
|
|
335
|
+
<TCell label="Mfg Date">
|
|
336
|
+
{row.isNew ? (
|
|
337
|
+
<input
|
|
338
|
+
type="date"
|
|
339
|
+
value={row.manufacturing_date}
|
|
340
|
+
onChange={(e) => updateRow(row.uid, 'manufacturing_date', e.target.value)}
|
|
341
|
+
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
342
|
+
/>
|
|
343
|
+
) : (
|
|
344
|
+
<div className="text-sm text-gray-500">{row.manufacturing_date || '—'}</div>
|
|
345
|
+
)}
|
|
346
|
+
</TCell>
|
|
347
|
+
<TCell label="Expiry Date">
|
|
348
|
+
{row.isNew ? (
|
|
349
|
+
<input
|
|
350
|
+
type="date"
|
|
351
|
+
value={row.expiry_date}
|
|
352
|
+
onChange={(e) => updateRow(row.uid, 'expiry_date', e.target.value)}
|
|
353
|
+
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
354
|
+
/>
|
|
355
|
+
) : (
|
|
356
|
+
<div className="text-sm text-gray-500">{row.expiry_date ? new Date(row.expiry_date).toLocaleDateString() : '—'}</div>
|
|
357
|
+
)}
|
|
358
|
+
</TCell>
|
|
359
|
+
<TCell label="Selling Price">
|
|
360
|
+
{row.isNew ? (
|
|
361
|
+
<input
|
|
362
|
+
type="number"
|
|
363
|
+
value={row.selling_price}
|
|
364
|
+
onChange={(e) => updateRow(row.uid, 'selling_price', e.target.value)}
|
|
365
|
+
className="w-full h-8 px-2 text-[13px] font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
366
|
+
/>
|
|
367
|
+
) : (
|
|
368
|
+
<div className="text-sm text-gray-500">{row.selling_price || '—'}</div>
|
|
369
|
+
)}
|
|
370
|
+
</TCell>
|
|
371
|
+
<TCell label="Quantity">
|
|
372
|
+
<div className="flex flex-col items-end">
|
|
373
|
+
<input
|
|
374
|
+
type="number"
|
|
375
|
+
min="0"
|
|
376
|
+
max={!allowCreate && displayQtyAvailable !== undefined ? displayQtyAvailable : undefined}
|
|
377
|
+
value={row.quantity}
|
|
378
|
+
onChange={(e) => updateRow(row.uid, 'quantity', e.target.value)}
|
|
379
|
+
placeholder="0"
|
|
380
|
+
className="w-full h-8 px-2 text-[13px] text-right font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
381
|
+
/>
|
|
382
|
+
{!row.isNew && displayQtyAvailable !== undefined && (
|
|
383
|
+
<span className="text-[10px] text-gray-400 mt-0.5">Avail: {Number(displayQtyAvailable).toFixed(4)}</span>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
</TCell>
|
|
387
|
+
<TCell label="">
|
|
388
|
+
<div className="flex justify-center items-center gap-1 w-full">
|
|
389
|
+
{row.isNew && (
|
|
390
|
+
<button
|
|
391
|
+
onClick={() => saveBatchToDB(row)}
|
|
392
|
+
className="p-1 text-primary-600 hover:text-primary-700 rounded-md hover:bg-primary-50 transition-colors"
|
|
393
|
+
title="Save Batch"
|
|
394
|
+
>
|
|
395
|
+
<Save size={16} />
|
|
396
|
+
</button>
|
|
397
|
+
)}
|
|
398
|
+
<button
|
|
399
|
+
onClick={() => removeRow(row.uid)}
|
|
400
|
+
className="p-1 text-gray-400 hover:text-danger-500 rounded-md hover:bg-danger-50 transition-colors flex justify-center"
|
|
401
|
+
title="Remove"
|
|
402
|
+
>
|
|
403
|
+
<X size={16} />
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
</TCell>
|
|
407
|
+
</TRow>
|
|
355
408
|
);
|
|
356
409
|
})
|
|
357
410
|
) : (
|
|
@@ -75,6 +75,7 @@ export function ProductSelectionPanel({
|
|
|
75
75
|
try {
|
|
76
76
|
const params: Record<string, string | number> = { search: value, limit: 20, type: config.type };
|
|
77
77
|
if (warehouseId) params.warehouse_id = warehouseId;
|
|
78
|
+
if (config.allowPhantomBOM) params.allow_phantom_bom = 1;
|
|
78
79
|
|
|
79
80
|
if (['sales', 'quotation', 'pos'].includes(config.type)) {
|
|
80
81
|
params.apply_sales_prices = 1;
|
|
@@ -261,7 +262,7 @@ export function ProductSelectionPanel({
|
|
|
261
262
|
let discAmt = line.discount_amount;
|
|
262
263
|
let discPercent = line.discount_percent;
|
|
263
264
|
|
|
264
|
-
if (field === 'quantity') qty = Math.max(0
|
|
265
|
+
if (field === 'quantity') qty = Math.max(0, num);
|
|
265
266
|
if (field === 'unit_price') price = Math.max(0, num);
|
|
266
267
|
if (field === 'discount_amount') {
|
|
267
268
|
const maxDiscount = price;
|
|
@@ -637,7 +638,8 @@ export function ProductSelectionPanel({
|
|
|
637
638
|
<AsyncSearchableSelect
|
|
638
639
|
controlClassName="!min-h-8 h-8 !py-1 text-sm"
|
|
639
640
|
placeholder="Select Batch..."
|
|
640
|
-
loadOptions={async (search) => {
|
|
641
|
+
loadOptions={async (search, page) => {
|
|
642
|
+
if (page > 1) return []; // Backend is unpaginated, don't repeat on scroll
|
|
641
643
|
const params: any = {};
|
|
642
644
|
if (line.variant_id) params.variant_id = line.variant_id;
|
|
643
645
|
if (warehouseId) params.warehouse_id = warehouseId;
|
|
@@ -728,7 +730,7 @@ export function ProductSelectionPanel({
|
|
|
728
730
|
})()}
|
|
729
731
|
</div>
|
|
730
732
|
)}
|
|
731
|
-
{Number(line.unit_price) === 0 && (
|
|
733
|
+
{Number(line.unit_price) === 0 && ['purchase', 'grn', 'quotation', 'sales', 'pos'].includes(config.type) && (
|
|
732
734
|
<div className="mt-2 flex items-center gap-2">
|
|
733
735
|
<label className={cn("flex items-center gap-1.5", !!(line.purchase_order_line_id || line.sales_order_line_id || line.sales_quotation_line_id) ? "cursor-not-allowed" : "cursor-pointer")}>
|
|
734
736
|
<input
|
|
@@ -793,7 +795,7 @@ export function ProductSelectionPanel({
|
|
|
793
795
|
<TCell label="Qty" className="py-2.5 text-center">
|
|
794
796
|
<input
|
|
795
797
|
type="number"
|
|
796
|
-
value={line.quantity}
|
|
798
|
+
value={line.quantity === 0 ? '' : line.quantity}
|
|
797
799
|
onChange={(e) => updateLineInput(line.uid, 'quantity', e.target.value)}
|
|
798
800
|
className="w-20 h-10 px-2 text-[13px] text-center font-medium bg-surface-1 border border-border-subtle rounded-md focus:border-primary/50 focus:outline-none transition-colors"
|
|
799
801
|
min={0.001}
|
|
@@ -63,7 +63,7 @@ export function ProductTransactionScreen({
|
|
|
63
63
|
const variantPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price';
|
|
64
64
|
const actualVariant = l.item.variants?.find((v: any) => v.id === l.variant_id);
|
|
65
65
|
const batchPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'selling_price';
|
|
66
|
-
const batchPrice = ['sales', 'quotation'].includes(config.type) && l.batches?.[0] ? (l.batches[0] as any)[batchPriceField] : undefined;
|
|
66
|
+
const batchPrice = ['sales', 'quotation', 'pos'].includes(config.type) && l.batches?.[0] ? (l.batches[0] as any)[batchPriceField] : undefined;
|
|
67
67
|
const basePrice = (batchPrice !== undefined && batchPrice !== null)
|
|
68
68
|
? Number(batchPrice)
|
|
69
69
|
: (actualVariant
|
|
@@ -261,7 +261,7 @@ export function ProductTransactionScreen({
|
|
|
261
261
|
const variantPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price';
|
|
262
262
|
const actualVariant = updatedLine.item.variants?.find((v: any) => v.id === updatedLine.variant_id);
|
|
263
263
|
const batchPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'selling_price';
|
|
264
|
-
const batchPrice = ['sales', 'quotation'].includes(config.type) && updatedLine.batches?.[0] ? (updatedLine.batches[0] as any)[batchPriceField] : undefined;
|
|
264
|
+
const batchPrice = ['sales', 'quotation', 'pos'].includes(config.type) && updatedLine.batches?.[0] ? (updatedLine.batches[0] as any)[batchPriceField] : undefined;
|
|
265
265
|
const basePrice = (batchPrice !== undefined && batchPrice !== null)
|
|
266
266
|
? Number(batchPrice)
|
|
267
267
|
: (actualVariant
|
|
@@ -585,7 +585,7 @@ export function ProductTransactionScreen({
|
|
|
585
585
|
const variantPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'sale_price';
|
|
586
586
|
const actualVariant = l.item.variants?.find((v: any) => v.id === l.variant_id);
|
|
587
587
|
const batchPriceField = config.priceField === 'default_cost_price' ? 'cost_price' : 'selling_price';
|
|
588
|
-
const batchPrice = ['sales', 'quotation'].includes(config.type) && l.batches?.[0] ? (l.batches[0] as any)[batchPriceField] : undefined;
|
|
588
|
+
const batchPrice = ['sales', 'quotation', 'pos'].includes(config.type) && l.batches?.[0] ? (l.batches[0] as any)[batchPriceField] : undefined;
|
|
589
589
|
const basePrice = (batchPrice !== undefined && batchPrice !== null)
|
|
590
590
|
? Number(batchPrice)
|
|
591
591
|
: (actualVariant
|
|
@@ -171,6 +171,8 @@ export interface TransactionScreenConfig {
|
|
|
171
171
|
disablePartySelection?: boolean;
|
|
172
172
|
/** Disable warehouse selection (e.g. for revisions) */
|
|
173
173
|
disableWarehouseSelection?: boolean;
|
|
174
|
+
/** Allow phantom BOMs to be selected (e.g. for stock decrease) */
|
|
175
|
+
allowPhantomBOM?: boolean;
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
// ── Module Presets ──
|
|
@@ -81,6 +81,9 @@ export default function ImageUploadComponent({
|
|
|
81
81
|
|
|
82
82
|
const getAuthHeaders = (): Record<string, string> => {
|
|
83
83
|
const headers: Record<string, string> = {};
|
|
84
|
+
if (typeof window !== "undefined") {
|
|
85
|
+
headers["X-Tenant-Domain"] = window.location.hostname;
|
|
86
|
+
}
|
|
84
87
|
// Add org header
|
|
85
88
|
try {
|
|
86
89
|
const orgRaw = typeof window !== "undefined" ? localStorage.getItem("selected_organization") : null;
|
|
@@ -60,7 +60,15 @@ export default function DirectTransactionDetailModal({
|
|
|
60
60
|
</div>
|
|
61
61
|
<div className="space-y-1">
|
|
62
62
|
<p className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">
|
|
63
|
-
|
|
63
|
+
Account
|
|
64
|
+
</p>
|
|
65
|
+
<p className="font-medium text-gray-900">
|
|
66
|
+
{transaction.account?.name || "-"}
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
<div className="space-y-1">
|
|
70
|
+
<p className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">
|
|
71
|
+
Paid Via
|
|
64
72
|
</p>
|
|
65
73
|
<p className="font-medium text-gray-900">
|
|
66
74
|
{transaction.payment_method === "cash"
|
package/src/index.tsx
CHANGED
|
@@ -11,6 +11,7 @@ export * from './base-components/ChipInput';
|
|
|
11
11
|
export * from './base-components/Divider';
|
|
12
12
|
export * from './base-components/Dropdown';
|
|
13
13
|
export * from './base-components/EmptyState';
|
|
14
|
+
export * from './base-components/FormattedNumberInput';
|
|
14
15
|
export * from './base-components/HintIcon';
|
|
15
16
|
export * from './base-components/RecordCount';
|
|
16
17
|
export * from './base-components/DateLabel';
|
|
@@ -49,6 +50,7 @@ export * from './common-components/pickers/EntityPickerModal';
|
|
|
49
50
|
export * from './common-components/pickers/UomGroupPicker';
|
|
50
51
|
export * from './common-components/pickers/UomPicker';
|
|
51
52
|
export * from './common-components/pickers/PartyPicker';
|
|
53
|
+
export * from './common-components/pickers/QuickPartyAddModal';
|
|
52
54
|
export * from './common-components/pickers/WarehousePicker';
|
|
53
55
|
|
|
54
56
|
// Item Wizard
|