@apptimate/ui 6.3.0 → 6.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.
@@ -172,7 +172,7 @@ export function ProductTransactionScreen({
172
172
  (
173
173
  item: TransactionItem,
174
174
  variant?: any,
175
- extra?: { quantity?: number; batches?: any[]; serial_numbers?: string[] }
175
+ extra?: { quantity?: number; batches?: any[]; serial_numbers?: string[]; serial_number_objects?: {id: number, serial_number: string}[] }
176
176
  ) => {
177
177
  setLineItems((prevLineItems) => {
178
178
  const actualVariant = variant || (item.variants && item.variants.length > 0 ? item.variants[0] : null);
@@ -222,6 +222,7 @@ export function ProductTransactionScreen({
222
222
  variant_id: resolvedVariantId,
223
223
  batches: extra?.batches || [],
224
224
  serial_numbers: extra?.serial_numbers || [],
225
+ serial_number_objects: extra?.serial_number_objects || [],
225
226
  quantity: qty,
226
227
  unit_price: unitPrice,
227
228
  discount_percent: discountPercent,
@@ -337,12 +338,34 @@ export function ProductTransactionScreen({
337
338
  const handleSubmit = useCallback(async () => {
338
339
  if (lineItems.length === 0) return;
339
340
 
341
+ let finalPayments = payments;
342
+ let actualAmountTendered = payments.reduce((sum, p) => sum + (Number(p.amount) || 0), 0);
343
+
340
344
  if (config.requireFullPayment) {
341
- const totalPayments = payments.reduce((sum, p) => sum + (Number(p.amount) || 0), 0);
342
- if (Math.abs(totalPayments - grandTotal) > 0.01) {
343
- toast.error('Total payments must equal the Grand Total (Use Credit mode to pay later).');
345
+ if (actualAmountTendered < grandTotal - 0.01) {
346
+ toast.error('Total payments must equal or exceed the Grand Total (Use Credit mode to pay later).');
344
347
  return;
345
348
  }
349
+
350
+ const excess = actualAmountTendered - grandTotal;
351
+ if (excess > 0.01) {
352
+ const hasNonCash = payments.some(p => p.mode_code !== 'cash' && Number(p.amount) > 0);
353
+ if (hasNonCash) {
354
+ toast.error('Overpayment (Change Due) is not supported when using multiple payment methods or non-cash modes.');
355
+ return;
356
+ }
357
+
358
+ let remainingExcess = excess;
359
+ finalPayments = payments.map(p => {
360
+ if (p.mode_code === 'cash' && remainingExcess > 0) {
361
+ const amt = Number(p.amount) || 0;
362
+ const reduction = Math.min(amt, remainingExcess);
363
+ remainingExcess -= reduction;
364
+ return { ...p, amount: amt - reduction };
365
+ }
366
+ return p;
367
+ });
368
+ }
346
369
  }
347
370
 
348
371
  if (config.showWarehouse && !warehouseId) {
@@ -350,6 +373,17 @@ export function ProductTransactionScreen({
350
373
  return;
351
374
  }
352
375
 
376
+ if (config.type === 'pos') {
377
+ const isCredit = (p: any) => p.mode_code === "credit";
378
+ const totalPaid = payments.reduce((sum, p) => sum + (isCredit(p) ? 0 : (Number(p.amount) || 0)), 0);
379
+ const balance = grandTotal - totalPaid;
380
+
381
+ if (balance > 0.01 && !partyId) {
382
+ toast.error('Customer must be selected for outstanding balances or credit payments.');
383
+ return;
384
+ }
385
+ }
386
+
353
387
  for (const line of lineItems) {
354
388
  if (line.item.tracking_type === 'batch') {
355
389
  const requiresBatch = !config.disableTrackingSelection || ['sales', 'quotation'].includes(config.type);
@@ -429,7 +463,8 @@ export function ProductTransactionScreen({
429
463
  line_total: l.line_total,
430
464
  }];
431
465
  }),
432
- payments: config.showPayment ? payments : undefined,
466
+ payments: config.showPayment ? finalPayments : undefined,
467
+ amount_tendered: config.showPayment ? actualAmountTendered : undefined,
433
468
  subtotal,
434
469
  discount_total: discountTotal,
435
470
  grand_total: grandTotal,
@@ -456,7 +491,15 @@ export function ProductTransactionScreen({
456
491
  }, [lineItems, config, partyId, warehouseId, transactionDate, expectedDeliveryDate, notes, payments, subtotal, discountTotal, grandTotal, onSubmit, defaultWarehouseId, initialData]);
457
492
 
458
493
  return (
459
- <div className="flex flex-col h-full">
494
+ <form
495
+ onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}
496
+ onKeyDown={(e) => {
497
+ if (e.key === 'Enter' && (e.target as HTMLElement).tagName !== 'TEXTAREA' && (e.target as HTMLElement).tagName !== 'BUTTON') {
498
+ e.preventDefault();
499
+ }
500
+ }}
501
+ className="flex flex-col h-full"
502
+ >
460
503
  {/* ── Two-Panel Layout ── */}
461
504
  <div className="flex-1 flex gap-5 min-h-0">
462
505
  {/* Left: Product Selection (75%) */}
@@ -627,6 +670,6 @@ export function ProductTransactionScreen({
627
670
  </ModalFooter>
628
671
  </Modal>
629
672
  )}
630
- </div>
673
+ </form>
631
674
  );
632
675
  }
@@ -262,6 +262,7 @@ export const TRANSACTION_PRESETS: Record<TransactionType, TransactionScreenConfi
262
262
  allowCreateBatchAndSerial: false,
263
263
  allowEditPrice: true,
264
264
  showStock: true,
265
+ requireFullPayment: true,
265
266
  },
266
267
  opening_stock: {
267
268
  type: 'opening_stock',
@@ -350,6 +351,7 @@ export interface TransactionPayload {
350
351
  line_total: number;
351
352
  }>;
352
353
  payments?: PaymentEntry[];
354
+ amount_tendered?: number;
353
355
  subtotal: number;
354
356
  discount_total: number;
355
357
  grand_total: number;