diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 4deca4086..e0cee6f63 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -18,9 +18,9 @@ import { createLoyaltyPointEntry, filterPricingRules, getAddedLPWithGrandTotal, - getApplicableCouponCodesName, getExchangeRate, getNumberSeries, + removeUnusedCoupons, getPricingRulesConflicts, removeLoyaltyPoint, roundFreeItemQty, @@ -41,7 +41,7 @@ import { TaxSummary } from '../TaxSummary/TaxSummary'; import { ReturnDocItem } from 'models/inventory/types'; import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types'; import { PricingRule } from '../PricingRule/PricingRule'; -import { ApplicableCouponCodes, ApplicablePricingRules } from './types'; +import { ApplicablePricingRules } from './types'; import { PricingRuleDetail } from '../PricingRuleDetail/PricingRuleDetail'; import { LoyaltyProgram } from '../LoyaltyProgram/LoyaltyProgram'; import { AppliedCouponCodes } from '../AppliedCouponCodes/AppliedCouponCodes'; @@ -758,7 +758,8 @@ export abstract class Invoice extends Transactional { } const pricingRule = await this.getPricingRule(); - if (!pricingRule) { + + if (!pricingRule || !pricingRule.length) { return false; } @@ -1074,30 +1075,7 @@ export abstract class Invoice extends Transactional { this.clearFreeItems(); } - if (!this.coupons?.length) { - return; - } - - const applicableCouponCodes = await Promise.all( - this.coupons?.map(async (coupon) => { - return await getApplicableCouponCodesName( - coupon.coupons as string, - this as SalesInvoice - ); - }) - ); - - const flattedApplicableCouponCodes = applicableCouponCodes?.flat(); - - const couponCodeDoc = (await this.fyo.doc.getDoc( - ModelNameEnum.CouponCode, - this.coupons[0].coupons - )) as CouponCode; - - couponCodeDoc.removeUnusedCoupons( - flattedApplicableCouponCodes as ApplicableCouponCodes[], - this as SalesInvoice - ); + await removeUnusedCoupons(this as SalesInvoice); } async beforeCancel(): Promise { diff --git a/models/baseModels/tests/testCouponCodes.spec.ts b/models/baseModels/tests/testCouponCodes.spec.ts index a7d26956a..0d229a653 100644 --- a/models/baseModels/tests/testCouponCodes.spec.ts +++ b/models/baseModels/tests/testCouponCodes.spec.ts @@ -157,7 +157,7 @@ test('disabled coupon codes is not applied', async (t) => { await sinv.runFormulas(); - t.equal(sinv.pricingRuleDetail?.length, undefined); + t.equal(sinv.pricingRuleDetail?.length, 0); }); test('Coupon code not created: coupons min amount must be lesser than coupons max.', async (t) => { diff --git a/models/baseModels/tests/testPricingRule.spec.ts b/models/baseModels/tests/testPricingRule.spec.ts index 515c6e993..a95172ed7 100644 --- a/models/baseModels/tests/testPricingRule.spec.ts +++ b/models/baseModels/tests/testPricingRule.spec.ts @@ -146,7 +146,7 @@ test('pricing rule is not applied when item qty is < min qty', async (t) => { await sinv.append('items', { item: itemMap.Jacket.name, quantity: 3 }); await sinv.runFormulas(); - t.equal(sinv.pricingRuleDetail?.length, undefined); + t.equal(sinv.pricingRuleDetail?.length, 0); }); test('pricing rule is not applied when item qty is > max qty', async (t) => { @@ -158,7 +158,7 @@ test('pricing rule is not applied when item qty is > max qty', async (t) => { await sinv.append('items', { item: itemMap.Jacket.name, quantity: 10 }); await sinv.runFormulas(); - t.equal(sinv.pricingRuleDetail?.length, undefined); + t.equal(sinv.pricingRuleDetail?.length, 0); }); test('pricing rule is applied when filtered by min and max amount', async (t) => { @@ -200,11 +200,7 @@ test('Pricing Rule is not applied when item amount is < min amount', async (t) }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('Pricing Rule is not applied when item amount is > max amount', async (t) => { @@ -220,11 +216,7 @@ test('Pricing Rule is not applied when item amount is > max amount', async (t) = }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('Pricing Rule is not applied when sinvDate < validFrom date', async (t) => { @@ -240,11 +232,7 @@ test('Pricing Rule is not applied when sinvDate < validFrom date', async (t) => }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('Pricing Rule is not applied when sinvDate > validFrom date', async (t) => { @@ -260,11 +248,7 @@ test('Pricing Rule is not applied when sinvDate > validFrom date', async (t) => }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('Pricing Rule is applied when filtered by qty, amount and dates', async (t) => { @@ -320,11 +304,7 @@ test('Pricing Rule is not applied when qty condition is false, rest is true', as }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('Pricing Rule is not applied when amount condition is false, rest is true', async (t) => { @@ -340,11 +320,7 @@ test('Pricing Rule is not applied when amount condition is false, rest is true', }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('Pricing Rule is not applied when validity condition is false, rest is true', async (t) => { @@ -360,11 +336,7 @@ test('Pricing Rule is not applied when validity condition is false, rest is true }); await sinv.runFormulas(); - t.equal( - sinv.pricingRuleDetail?.length, - undefined, - 'Pricing Rule is not applied' - ); + t.equal(sinv.pricingRuleDetail?.length, 0, 'Pricing Rule is not applied'); }); test('create two pricing rules, Highest priority pricing rule is applied', async (t) => { diff --git a/models/helpers.ts b/models/helpers.ts index 942179a1d..3d85a58d0 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -12,7 +12,10 @@ import { import { Fyo, t } from 'fyo'; import { InvoiceStatus, ModelNameEnum } from './types'; -import { ApplicablePricingRules } from './baseModels/Invoice/types'; +import { + ApplicableCouponCodes, + ApplicablePricingRules, +} from './baseModels/Invoice/types'; import { AppliedCouponCodes } from './baseModels/AppliedCouponCodes/AppliedCouponCodes'; import { CollectionRulesItems } from './baseModels/CollectionRulesItems/CollectionRulesItems'; import { CouponCode } from './baseModels/CouponCode/CouponCode'; @@ -764,19 +767,6 @@ export function getLoyaltyProgramTier( return loyaltyProgramTier; } -export async function updatePricingRuleItem(doc: SalesInvoice) { - const pricingRule = (await getPricingRule(doc)) as ApplicablePricingRules[]; - - let appliedPricingRuleCount = doc?.pricingRuleDetail?.length; - - if (appliedPricingRuleCount !== pricingRule?.length) { - appliedPricingRuleCount = pricingRule?.length; - - await doc?.appendPricingRuleDetail(pricingRule); - await doc?.applyProductDiscount(); - } -} - export async function removeLoyaltyPoint(doc: Doc) { if (!doc.loyaltyProgram) { return; @@ -1058,6 +1048,33 @@ export function canApplyCouponCode( return true; } +export async function removeUnusedCoupons(sinvDoc: SalesInvoice) { + if (!sinvDoc.coupons?.length) { + return; + } + + const applicableCouponCodes = await Promise.all( + sinvDoc.coupons?.map(async (coupon) => { + return await getApplicableCouponCodesName( + coupon.coupons as string, + sinvDoc + ); + }) + ); + + const flattedApplicableCouponCodes = applicableCouponCodes?.flat(); + + const couponCodeDoc = (await sinvDoc.fyo.doc.getDoc( + ModelNameEnum.CouponCode, + sinvDoc.coupons[0].coupons + )) as CouponCode; + + couponCodeDoc.removeUnusedCoupons( + flattedApplicableCouponCodes as ApplicableCouponCodes[], + sinvDoc + ); +} + export async function getApplicableCouponCodesName( couponName: string, sinvDoc: SalesInvoice @@ -1200,30 +1217,6 @@ export function removeFreeItems(sinvDoc: SalesInvoice) { } } -export async function updatePricingRule(sinvDoc: SalesInvoice) { - const applicablePricingRuleNames = await getPricingRule(sinvDoc); - - if (!applicablePricingRuleNames || !applicablePricingRuleNames.length) { - sinvDoc.pricingRuleDetail = undefined; - sinvDoc.isPricingRuleApplied = false; - removeFreeItems(sinvDoc); - return; - } - - const appliedPricingRuleCount = sinvDoc?.items?.filter( - (val) => val.isFreeItem - ).length; - - setTimeout(() => { - void (async () => { - if (appliedPricingRuleCount !== applicablePricingRuleNames?.length) { - await sinvDoc.appendPricingRuleDetail(applicablePricingRuleNames); - await sinvDoc.applyProductDiscount(); - } - })(); - }, 1); -} - export function getPricingRulesConflicts( pricingRules: PricingRule[] ): undefined | boolean { diff --git a/src/components/POS/Classic/SelectedItemRow.vue b/src/components/POS/Classic/SelectedItemRow.vue index d26fb9510..818d4cb55 100644 --- a/src/components/POS/Classic/SelectedItemRow.vue +++ b/src/components/POS/Classic/SelectedItemRow.vue @@ -275,9 +275,6 @@ import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoic import { Money } from 'pesa'; import { DiscountType } from '../types'; import { validateSerialNumberCount } from 'src/utils/pos'; -import { getPricingRule } from 'models/helpers'; -import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; -import { ApplicablePricingRules } from 'models/baseModels/Invoice/types'; export default defineComponent({ name: 'SelectedItemRow', @@ -285,7 +282,7 @@ export default defineComponent({ props: { row: { type: SalesInvoiceItem, required: true }, }, - emits: ['runSinvFormulas', 'setItemSerialNumbers', 'addItem'], + emits: ['runSinvFormulas', 'applyPricingRule'], setup() { return { isDiscountingEnabled: inject('isDiscountingEnabled') as boolean, @@ -363,7 +360,7 @@ export default defineComponent({ this.row.set('quantity', quantity); if (!this.row.isFreeItem) { - await this.updatePricingRuleItem(); + this.$emit('applyPricingRule'); this.$emit('runSinvFormulas'); } }, @@ -371,22 +368,7 @@ export default defineComponent({ this.row.parentdoc?.remove('items', row?.idx as number); if (!row.isFreeItem) { - await this.updatePricingRuleItem(); - } - }, - async updatePricingRuleItem() { - const pricingRule = (await getPricingRule( - this.row.parentdoc as SalesInvoice - )) as ApplicablePricingRules[]; - - let appliedPricingRuleCount = - this.row.parentdoc?.pricingRuleDetail?.length; - - if (appliedPricingRuleCount !== pricingRule?.length) { - appliedPricingRuleCount = pricingRule?.length; - - await this.row.parentdoc?.appendPricingRuleDetail(pricingRule); - await this.row.parentdoc?.applyProductDiscount(); + this.$emit('applyPricingRule'); } }, }, diff --git a/src/components/POS/Classic/SelectedItemTable.vue b/src/components/POS/Classic/SelectedItemTable.vue index 70822ba59..8eb992269 100644 --- a/src/components/POS/Classic/SelectedItemTable.vue +++ b/src/components/POS/Classic/SelectedItemTable.vue @@ -54,6 +54,7 @@ @@ -93,6 +94,7 @@ export default defineComponent({ isExapanded: false, }; }, + emits: ['applyPricingRule'], computed: { ratio() { return [0.1, 1, 0.8, 0.8, 0.8, 0.8, 0.2]; diff --git a/src/components/POS/Modern/ModernPOSSelectedItemRow.vue b/src/components/POS/Modern/ModernPOSSelectedItemRow.vue index f44071e5a..111e418c1 100644 --- a/src/components/POS/Modern/ModernPOSSelectedItemRow.vue +++ b/src/components/POS/Modern/ModernPOSSelectedItemRow.vue @@ -248,8 +248,6 @@ import { defineComponent } from 'vue'; import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem'; import { Money } from 'pesa'; import { validateSerialNumberCount } from 'src/utils/pos'; -import { updatePricingRuleItem } from 'models/helpers'; -import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; export default defineComponent({ name: 'ModernPOSSelectedItemRow', @@ -257,7 +255,7 @@ export default defineComponent({ props: { row: { type: SalesInvoiceItem, required: true }, }, - emits: ['toggleModal', 'runSinvFormulas', 'selectedRow'], + emits: ['toggleModal', 'runSinvFormulas', 'selectedRow', 'applyPricingRule'], setup() { return { @@ -331,7 +329,7 @@ export default defineComponent({ this.row.parentdoc?.remove('items', row?.idx as number); if (!row.isFreeItem) { - await updatePricingRuleItem(this.row.parentdoc as SalesInvoice); + this.$emit('applyPricingRule'); } }, }, diff --git a/src/components/POS/Modern/ModernPOSSelectedItemTable.vue b/src/components/POS/Modern/ModernPOSSelectedItemTable.vue index 07b5c1f2c..28aa2d2f6 100644 --- a/src/components/POS/Modern/ModernPOSSelectedItemTable.vue +++ b/src/components/POS/Modern/ModernPOSSelectedItemTable.vue @@ -82,7 +82,7 @@ export default defineComponent({ isExapanded: false, }; }, - emits: ['toggleModal', 'selectedRow'], + emits: ['toggleModal', 'selectedRow', 'applyPricingRule'], computed: { ratio() { return [0.1, 0.8, 0.4, 0.8, 0.8, 0.3]; @@ -141,6 +141,9 @@ export default defineComponent({ selectedItemRow(row: SalesInvoiceItem, field: string) { this.$emit('selectedRow', row, field); }, + emitApplyPricingRule() { + this.$emit('applyPricingRule'); + }, isNumeric, }, }); diff --git a/src/components/POS/types.ts b/src/components/POS/types.ts index e377208e1..e15fe7768 100644 --- a/src/components/POS/types.ts +++ b/src/components/POS/types.ts @@ -24,6 +24,7 @@ export type PosEmits = | 'setCashAmount' | 'setCouponsCount' | 'routeToSinvList' + | 'applyPricingRule' | 'setTransferRefNo' | 'setLoyaltyPoints' | 'setTransferAmount' diff --git a/src/pages/POS/ClassicPOS.vue b/src/pages/POS/ClassicPOS.vue index ffc6f547d..68527b76a 100644 --- a/src/pages/POS/ClassicPOS.vue +++ b/src/pages/POS/ClassicPOS.vue @@ -30,6 +30,7 @@ @@ -39,7 +40,6 @@ @toggle-modal="emitEvent('toggleModal', 'Payment')" @set-cash-amount="(amount) => emitEvent('setCashAmount', amount)" @set-transfer-ref-no="(ref) => emitEvent('setTransferRefNo', ref)" - @set-coupons-count="(count) => emitEvent('setCouponsCount', count)" @set-transfer-amount="(amount) => emitEvent('setTransferAmount', amount)" @set-transfer-clearance-date=" (date) => emitEvent('setTransferClearanceDate', date) @@ -153,7 +153,9 @@ @change="(value:string) => $emit('setCustomer',value)" /> - +
coup.coupons !== coupon?.coupons - ); + this.sinvDoc?.items?.map((item: InvoiceItem) => { + item.itemDiscountAmount = this.fyo.pesa(0); + item.itemDiscountPercent = 0; + item.setItemDiscountAmount = false; + }); + + await coupon?.parentdoc?.remove('coupons', coupon.idx as number); - await updatePricingRule(this.sinvDoc); - this.$emit('setCouponsCount', this.sinvDoc.coupons?.length); + this.$emit('applyPricingRule'); + this.$emit('setCouponsCount', this.coupons?.length); }, cancelApplyCouponCode() { this.couponCode = ''; diff --git a/src/pages/POS/KeyboardModal.vue b/src/pages/POS/KeyboardModal.vue index 7ae8e9dae..eb9d705b6 100644 --- a/src/pages/POS/KeyboardModal.vue +++ b/src/pages/POS/KeyboardModal.vue @@ -316,7 +316,6 @@ import { defineComponent, inject } from 'vue'; import Button from 'src/components/Button.vue'; import Float from 'src/components/Controls/Float.vue'; import Currency from 'src/components/Controls/Currency.vue'; -import { updatePricingRuleItem } from 'models/helpers'; import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice'; import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem'; import { ValidationError } from 'fyo/utils/errors'; @@ -338,7 +337,7 @@ export default defineComponent({ default: '', }, }, - emits: ['toggleModal'], + emits: ['toggleModal', 'applyPricingRule'], setup() { return { sinvDoc: inject('sinvDoc') as SalesInvoice, @@ -460,7 +459,7 @@ export default defineComponent({ } if (this.selectedItemField === 'quantity') { - await updatePricingRuleItem(this.sinvDoc); + this.$emit('applyPricingRule'); } } @@ -471,6 +470,10 @@ export default defineComponent({ type: 'error', message: this.t`${error as string}`, }); + + if (this.selectedItemField === 'quantity') { + this.$emit('applyPricingRule'); + } } }, async deleteLast() { diff --git a/src/pages/POS/ModernPOS.vue b/src/pages/POS/ModernPOS.vue index 5cf91cadd..b14d73a3c 100644 --- a/src/pages/POS/ModernPOS.vue +++ b/src/pages/POS/ModernPOS.vue @@ -30,6 +30,7 @@ @@ -39,7 +40,6 @@ @toggle-modal="emitEvent('toggleModal', 'Payment')" @set-cash-amount="(amount) => emitEvent('setCashAmount', amount)" @set-transfer-ref-no="(ref) => emitEvent('setTransferRefNo', ref)" - @set-coupons-count="(count) => emitEvent('setCouponsCount', count)" @set-transfer-amount="(amount) => emitEvent('setTransferAmount', amount)" @set-transfer-clearance-date=" (date) => emitEvent('setTransferClearanceDate', date) @@ -61,6 +61,7 @@ :selected-item-field="selectedItemField" :selected-item-row="(selectedItemRow as SalesInvoiceItem)" @toggle-modal="emitEvent('toggleModal', 'Keyboard')" + @apply-pricing-rule="emitEvent('applyPricingRule')" />
@@ -91,6 +92,7 @@
@@ -397,6 +399,7 @@ export default defineComponent({ 'routeToSinvList', 'setLoyaltyPoints', 'setTransferRefNo', + 'applyPricingRule', 'saveInvoiceAction', 'createTransaction', 'setTransferAmount', diff --git a/src/pages/POS/POS.vue b/src/pages/POS/POS.vue index 0d4c7eb0c..c2025b076 100644 --- a/src/pages/POS/POS.vue +++ b/src/pages/POS/POS.vue @@ -43,6 +43,7 @@ @route-to-sinv-list="routeToSinvList" @set-loyalty-points="setLoyaltyPoints" @set-transfer-ref-no="setTransferRefNo" + @apply-pricing-rule="applyPricingRule" @create-transaction="createTransaction" @save-invoice-action="saveInvoiceAction" @set-transfer-amount="setTransferAmount" @@ -81,6 +82,7 @@ @set-cash-amount="setCashAmount" @set-coupons-count="setCouponsCount" @route-to-sinv-list="routeToSinvList" + @apply-pricing-rule="applyPricingRule" @set-loyalty-points="setLoyaltyPoints" @set-transfer-ref-no="setTransferRefNo" @create-transaction="createTransaction" @@ -215,6 +217,10 @@ export default defineComponent({ watch: { sinvDoc: { handler() { + if (this.sinvDoc.coupons?.length) { + this.setCouponsCount(this.sinvDoc.coupons?.length); + } + this.updateValues(); }, deep: true, @@ -617,26 +623,12 @@ export default defineComponent({ this.sinvDoc.isPricingRuleApplied = false; removeFreeItems(this.sinvDoc as SalesInvoice); + await this.sinvDoc.applyProductDiscount(); return; } - const appliedPricingRuleCount = this.sinvDoc?.items?.filter( - (val) => val.isFreeItem - ).length; - - const recursivePricingRules = hasPricingRules?.filter( - (val) => val.pricingRule.isRecursive - ); - - setTimeout(async () => { - if ( - appliedPricingRuleCount !== hasPricingRules?.length || - recursivePricingRules - ) { - await this.sinvDoc.appendPricingRuleDetail(hasPricingRules); - await this.sinvDoc.applyProductDiscount(); - } - }, 1); + await this.sinvDoc.appendPricingRuleDetail(hasPricingRules); + await this.sinvDoc.applyProductDiscount(); }, async routeToSinvList() { if (!this.sinvDoc.items?.length) { diff --git a/src/pages/POS/PaymentModal.vue b/src/pages/POS/PaymentModal.vue index 2befe5df7..155686483 100644 --- a/src/pages/POS/PaymentModal.vue +++ b/src/pages/POS/PaymentModal.vue @@ -224,7 +224,6 @@ export default defineComponent({ 'setTransferClearanceDate', 'setTransferRefNo', 'toggleModal', - 'setCouponsCount', ], setup() { return { @@ -323,7 +322,6 @@ export default defineComponent({ }, submitTransaction() { this.$emit('createTransaction'); - this.$emit('setCouponsCount', 0); }, }, });