Skip to content

Commit edfcb2b

Browse files
Ahmed Osamaclaude
andcommitted
fix: enable automatic offer discovery when customer changes
Remove guards that prevented offer reapplication when no offers are currently applied. This allows the system to automatically discover and apply eligible offers when switching to a different customer, even if the cart doesn't have any active offers yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 272be8c commit edfcb2b

2 files changed

Lines changed: 74 additions & 18 deletions

File tree

POS/src/pages/POSSale.vue

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,13 @@ const offerReapplyTimer = ref(null)
651651
// Performance: Cache previous cart state to avoid unnecessary reapplications
652652
let previousCartHash = ''
653653
654+
// Helper function to compute cart hash
655+
function computeCartHash() {
656+
return cartStore.invoiceItems.map(i =>
657+
`${i.item_code}-${i.quantity}-${i.rate}-${i.discount_percentage || 0}-${i.discount_amount || 0}-${i.uom || ''}-${i.warehouse || ''}`
658+
).join('|')
659+
}
660+
654661
// Promotion dialog
655662
const showPromotionManagement = ref(false)
656663
@@ -749,37 +756,72 @@ watch(() => shiftStore.hasOpenShift, value => {
749756
}
750757
})
751758
752-
// Watch for cart changes to re-apply offers (optimized - only watch length and defer expensive calculations)
753-
// Performance: Only recalculate hash if cart length changed, then check if content actually changed
759+
// Watch for cart changes to re-apply offers
760+
// Comprehensive watcher that detects all cart changes including:
761+
// - Items added/removed (length changes)
762+
// - Quantity changes
763+
// - Rate/price changes
764+
// - Discount changes
765+
// - Item properties that affect offers
754766
watch(
755-
() => cartStore.invoiceItems.length,
756-
() => {
767+
() => computeCartHash(),
768+
(newHash) => {
757769
// Only proceed if there are applied offers
758770
if (cartStore.appliedOffers.length === 0) {
759771
return
760772
}
761773
762-
// Calculate hash only when length changes
763-
const currentHash = cartStore.invoiceItems.map(i =>
764-
`${i.item_code}-${i.quantity}-${i.rate}-${i.discount_percentage || 0}-${i.discount_amount || 0}`
765-
).join(',')
766-
767774
// Skip if cart content hasn't actually changed
768-
if (currentHash === previousCartHash) {
775+
if (newHash === previousCartHash) {
769776
return
770777
}
771778
772-
previousCartHash = currentHash
779+
previousCartHash = newHash
773780
774781
// Clear existing timer to prevent multiple API calls
775782
if (offerReapplyTimer.value) {
776783
clearTimeout(offerReapplyTimer.value)
777784
}
778785
779-
// Set new timer - reapply offers after 800ms of no changes (increased for better performance)
786+
// Set new timer - reapply offers after 500ms of no changes
780787
offerReapplyTimer.value = setTimeout(async () => {
781788
await cartStore.reapplyOffer(shiftStore.currentProfile)
782-
}, 800)
789+
}, 500)
790+
}
791+
)
792+
793+
// Watch for customer changes - customer affects which offers are applicable
794+
watch(
795+
() => cartStore.customer,
796+
(newCustomer, oldCustomer) => {
797+
const newCustomerName = newCustomer?.name || newCustomer
798+
const oldCustomerName = oldCustomer?.name || oldCustomer
799+
800+
// Only reapply if customer actually changed
801+
if (newCustomerName !== oldCustomerName) {
802+
// Clear existing timer
803+
if (offerReapplyTimer.value) {
804+
clearTimeout(offerReapplyTimer.value)
805+
}
806+
807+
// Reapply offers immediately when customer changes
808+
// This will discover newly eligible offers even if cart has no current offers
809+
offerReapplyTimer.value = setTimeout(async () => {
810+
await cartStore.reapplyOffer(shiftStore.currentProfile)
811+
}, 300)
812+
}
813+
},
814+
{ deep: true }
815+
)
816+
817+
// Watch for applied offers changes - handle when offers are added/removed
818+
watch(
819+
() => cartStore.appliedOffers.length,
820+
() => {
821+
// When offers are added or removed, update the cart hash to reflect new state
822+
if (cartStore.invoiceItems.length > 0) {
823+
previousCartHash = computeCartHash()
824+
}
783825
}
784826
)
785827
@@ -998,6 +1040,8 @@ async function handlePaymentCompleted(paymentData) {
9981040
uiStore.showSuccess(`OFFLINE-${Date.now()}`, cartStore.grandTotal)
9991041
uiStore.showPaymentDialog = false
10001042
cartStore.clearCart()
1043+
// Reset cart hash after successful payment
1044+
previousCartHash = ''
10011045
10021046
toast.create({
10031047
title: "Saved Offline",
@@ -1014,6 +1058,8 @@ async function handlePaymentCompleted(paymentData) {
10141058
10151059
uiStore.showPaymentDialog = false
10161060
cartStore.clearCart()
1061+
// Reset cart hash after successful payment
1062+
previousCartHash = ''
10171063
10181064
if (itemsSelectorRef.value) {
10191065
itemsSelectorRef.value.loadItems()
@@ -1082,6 +1128,8 @@ function handleClearCart() {
10821128
10831129
function confirmClearCart() {
10841130
cartStore.clearCart()
1131+
// Reset cart hash when cart is cleared
1132+
previousCartHash = ''
10851133
uiStore.showClearCartDialog = false
10861134
toast.create({
10871135
title: "Cart Cleared",
@@ -1214,6 +1262,8 @@ async function handleSaveDraft() {
12141262
)
12151263
if (success) {
12161264
cartStore.clearCart()
1265+
// Reset cart hash when cart is saved as draft and cleared
1266+
previousCartHash = ''
12171267
}
12181268
}
12191269
@@ -1230,6 +1280,9 @@ async function handleLoadDraft(draft) {
12301280
await cartStore.reapplyOffer(shiftStore.currentProfile)
12311281
}
12321282
1283+
// Initialize cart hash for the loaded cart so watchers work correctly
1284+
previousCartHash = computeCartHash()
1285+
12331286
uiStore.showDraftDialog = false
12341287
} catch (error) {
12351288
// Error handled in store
@@ -1322,6 +1375,9 @@ async function handleEditOfflineInvoice(invoice) {
13221375
}
13231376
}
13241377
1378+
// Initialize cart hash for the loaded cart so watchers work correctly
1379+
previousCartHash = computeCartHash()
1380+
13251381
await offlineStore.deleteOfflineInvoice(invoice.id)
13261382
13271383
toast.create({

POS/src/stores/posCart.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -404,16 +404,16 @@ export const usePOSCartStore = defineStore('posCart', () => {
404404
}
405405

406406
async function reapplyOffer(currentProfile) {
407+
// Clear offers if cart is empty
407408
if (invoiceItems.value.length === 0 && appliedOffers.value.length) {
408409
appliedOffers.value = []
409410
return
410411
}
411412

412-
if (
413-
invoiceItems.value.length > 0 &&
414-
appliedOffers.value.length > 0 &&
415-
!suppressOfferReapply.value
416-
) {
413+
// Reapply or discover offers when cart has items
414+
// - If appliedOffers.length > 0: re-validates existing offers
415+
// - If appliedOffers.length === 0: discovers new eligible offers
416+
if (invoiceItems.value.length > 0 && !suppressOfferReapply.value) {
417417
try {
418418
const invoiceData = buildInvoiceDataForOffers(currentProfile)
419419
const offerNames = getAppliedOfferCodes()

0 commit comments

Comments
 (0)