From 2c4a8be72651a06cee5787945f6e4cf3dd0a7820 Mon Sep 17 00:00:00 2001 From: AbleKSaju <126228406+AbleKSaju@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:50:06 +0530 Subject: [PATCH 1/2] fix(party): display total outstanding amount --- src/components/StatusPill.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/StatusPill.vue b/src/components/StatusPill.vue index 50db6039..a3ca0719 100644 --- a/src/components/StatusPill.vue +++ b/src/components/StatusPill.vue @@ -40,6 +40,14 @@ export default defineComponent({ return this.t`Partly Paid ${outstandingPayment}`; } + if (this.status === 'Outstanding') { + const outstandingPayment = this.fyo.format( + this.doc.outstandingAmount as Money, + 'Currency' + ); + return this.t`Unpaid ${outstandingPayment}`; + } + return { Draft: this.t`Draft`, Cancelled: this.t`Cancelled`, From 1fd40c42027a91161c2d3cbd8eae074982aecbbd Mon Sep 17 00:00:00 2001 From: AbleKSaju <126228406+AbleKSaju@users.noreply.github.com> Date: Thu, 26 Jun 2025 09:53:08 +0530 Subject: [PATCH 2/2] fix: include converted loyalty points in grand total --- models/baseModels/Invoice/Invoice.ts | 31 +++++++++- .../baseModels/SalesInvoice/SalesInvoice.ts | 23 +++++++- models/inventory/StockMovementItem.ts | 14 ++++- .../POS/Classic/SelectedItemRow.vue | 3 + src/pages/POS/POS.vue | 58 ------------------- templates/Business-POS.template.html | 2 +- 6 files changed, 64 insertions(+), 67 deletions(-) diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index b1a3454d..59624588 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -1,4 +1,4 @@ -import { Fyo } from 'fyo'; +import { Fyo, t } from 'fyo'; import { DocValueMap } from 'fyo/core/types'; import { Doc } from 'fyo/model/doc'; import { @@ -193,6 +193,21 @@ export abstract class Invoice extends Transactional { await this._validatePricingRule(); } + async beforeSubmit() { + const partyDoc = (await this.fyo.doc.getDoc( + ModelNameEnum.Party, + this.party + )) as Party; + + if ((this.loyaltyPoints as number) > (partyDoc?.loyaltyPoints || 0)) { + throw new ValidationError( + t`${this.party as string} only has ${ + partyDoc.loyaltyPoints as number + } points` + ); + } + } + async afterSubmit() { await super.afterSubmit(); if (this.isReturn) { @@ -446,6 +461,9 @@ export abstract class Invoice extends Transactional { const totalDiscount = this.getTotalDiscount(); if (!this.taxes!.length) { + if (this.redeemLoyaltyPoints) { + return this.getLPAddedBaseGrandTotal(); + } return (this.netTotal as Money).sub(totalDiscount); } @@ -880,6 +898,10 @@ export abstract class Invoice extends Transactional { this.loyaltyPoints as number ); + if (this.isReturn) { + return this.grandTotal; + } + return this.initialGrandTotal?.sub(totalLotaltyAmount); } formulas: FormulaMap = { @@ -937,7 +959,10 @@ export abstract class Invoice extends Transactional { }, netTotal: { formula: () => this.getSum('items', 'amount', false) }, taxes: { formula: async () => await this.getTaxSummary() }, - grandTotal: { formula: () => this.getGrandTotal() }, + grandTotal: { + formula: async () => await this.getGrandTotal(), + dependsOn: ['loyaltyPoints'], + }, baseGrandTotal: { formula: () => (this.grandTotal as Money).mul(this.exchangeRate! ?? 1), dependsOn: ['grandTotal', 'exchangeRate'], @@ -955,7 +980,7 @@ export abstract class Invoice extends Transactional { if (sinvreturnedDoc.outstandingAmount?.isZero()) { return this.grandTotal?.abs(); } else { - const totalPaid = this.grandTotal + const totalPaid = sinvreturnedDoc.grandTotal ?.abs() .sub(sinvreturnedDoc.outstandingAmount as Money); diff --git a/models/baseModels/SalesInvoice/SalesInvoice.ts b/models/baseModels/SalesInvoice/SalesInvoice.ts index 55a62476..aa4dca8f 100644 --- a/models/baseModels/SalesInvoice/SalesInvoice.ts +++ b/models/baseModels/SalesInvoice/SalesInvoice.ts @@ -13,6 +13,8 @@ import { LoyaltyProgram } from '../LoyaltyProgram/LoyaltyProgram'; import { DocValue } from 'fyo/core/types'; import { Party } from '../Party/Party'; import { ValidationError } from 'fyo/utils/errors'; +import { Money } from 'pesa'; +import { Doc } from 'fyo/model/doc'; export class SalesInvoice extends Invoice { items?: SalesInvoiceItem[]; @@ -81,7 +83,7 @@ export class SalesInvoice extends Invoice { validations: ValidationMap = { loyaltyPoints: async (value: DocValue) => { - if (!this.redeemLoyaltyPoints || this.isSubmitted) { + if (!this.redeemLoyaltyPoints || this.isSubmitted || this.isReturn) { return; } @@ -116,7 +118,24 @@ export class SalesInvoice extends Invoice { ((loyaltyProgramDoc?.conversionFactor as number) || 0); if (!this.isReturn) { - if (this.grandTotal?.lt(loyaltyPoint)) { + const totalDiscount = this.getTotalDiscount(); + let baseGrandTotal; + + if (!this.taxes!.length) { + baseGrandTotal = (this.netTotal as Money).sub(totalDiscount); + } else { + baseGrandTotal = ((this.taxes ?? []) as Doc[]) + .map((doc) => doc.amount as Money) + .reduce((a, b) => { + if (this.isReturn) { + return a.abs().add(b.abs()).neg(); + } + return a.add(b.abs()); + }, (this.netTotal as Money).abs()) + .sub(totalDiscount); + } + + if (baseGrandTotal?.lt(loyaltyPoint)) { throw new ValidationError( t`no need ${value as number} points to purchase this item` ); diff --git a/models/inventory/StockMovementItem.ts b/models/inventory/StockMovementItem.ts index 17e641e0..6fdcbe05 100644 --- a/models/inventory/StockMovementItem.ts +++ b/models/inventory/StockMovementItem.ts @@ -231,10 +231,18 @@ export class StockMovementItem extends TransferItem { ); } }, - batch: () => { - if (this.fyo.singles.InventorySettings?.enableBatches && !this.batch) { + batch: async () => { + if (!this.item || !this.batch) return; + + const batchDoc = await this.fyo.doc.getDoc( + ModelNameEnum.Batch, + this.batch + ); + if (!batchDoc) return; + + if (batchDoc.item !== this.item) { throw new ValidationError( - t`Batch is required for Item ${this.item as string}` + t`Batch ${this.batch} does not belong to Item ${this.item}` ); } }, diff --git a/src/components/POS/Classic/SelectedItemRow.vue b/src/components/POS/Classic/SelectedItemRow.vue index 19f4d874..cb540f43 100644 --- a/src/components/POS/Classic/SelectedItemRow.vue +++ b/src/components/POS/Classic/SelectedItemRow.vue @@ -305,6 +305,9 @@ export default defineComponent({ }, async mounted() { + this.$watch('row.quantity', (newVal: number) => { + this.setQuantity(newVal); + }); const posProfileName = this.fyo.singles.POSSettings?.posProfile; if (posProfileName) { diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index 87c768f3..472eecfd 100644 --- a/src/pages/POS/POS.vue +++ b/src/pages/POS/POS.vue @@ -146,7 +146,6 @@ import { getItemQtyMap, getPricingRule, removeFreeItems, - getAddedLPWithGrandTotal, getItemRateFromPriceList, } from 'models/helpers'; import { @@ -611,19 +610,6 @@ export default defineComponent({ async setLoyaltyPoints(value: number) { this.appliedLoyaltyPoints = value; await this.sinvDoc.set('redeemLoyaltyPoints', true); - - const totalLotaltyAmount = await getAddedLPWithGrandTotal( - this.fyo, - this.loyaltyProgram, - value - ); - - const total = totalLotaltyAmount - .sub(this.sinvDoc.baseGrandTotal as Money) - .abs(); - - this.sinvDoc.grandTotal = total; - this.sinvDoc.outstandingAmount = total; }, async selectedInvoiceName(doc: SalesInvoice) { const salesInvoiceDoc = (await this.fyo.doc.getDoc( @@ -704,50 +690,6 @@ export default defineComponent({ )) as number; if (item.hasBatch) { - const isTrackItem = - this.fyo.singles.POSSettings?.itemVisibility == 'Inventory Items'; - - for (const invItem of existingItems) { - const itemQty = invItem.quantity ?? 0; - - if (!isTrackItem) { - invItem.quantity = quantity - ? (invItem.quantity as number) + quantity - : (invItem.quantity as number) + 1; - } else { - const qtyInBatch = - this.itemQtyMap[invItem.item as string][ - invItem.batch as string - ] ?? 0; - - if (itemQty < qtyInBatch) { - invItem.quantity = quantity - ? (invItem.quantity as number) + quantity - : (invItem.quantity as number) + 1; - invItem.rate = item.rate as Money; - } - await this.applyPricingRule(); - await this.sinvDoc.runFormulas(); - await validateQty( - this.sinvDoc as SalesInvoice, - item as Item, - existingItems as InvoiceItem[] - ); - - return; - } - - await this.applyPricingRule(); - await this.sinvDoc.runFormulas(); - await validateQty( - this.sinvDoc as SalesInvoice, - item as Item, - existingItems as InvoiceItem[] - ); - - return; - } - await this.sinvDoc.append('items', { rate: item.rate as Money, item: item.name, diff --git a/templates/Business-POS.template.html b/templates/Business-POS.template.html index 2df8e66c..357ebe7b 100644 --- a/templates/Business-POS.template.html +++ b/templates/Business-POS.template.html @@ -1,4 +1,4 @@ -
+