Skip to content

Commit 7fcb824

Browse files
committed
Add print checkbox for comments, global item frequency, rate change notifications, UI improvements
1 parent 69fd249 commit 7fcb824

7 files changed

Lines changed: 211 additions & 66 deletions

File tree

www/js/firebase/firestore-service.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,57 @@ const FirebaseService = {
935935
console.error('Users listener error:', error);
936936
});
937937
unsubscribeFunctions.push(unsubUsers);
938+
939+
// Listen to notifications for current user (rate changes, etc.)
940+
if (AppState.currentUser?.uid) {
941+
// Listen to global item frequency changes to update dropdown order in real-time
942+
const unsubItemFrequency = getDb().collection(col('itemFrequency')).doc('global').onSnapshot(doc => {
943+
if (doc.exists && window.app?.billing) {
944+
// Update the billing manager's item frequency data
945+
window.app.billing.billingManager?.itemFrequency &&
946+
Object.assign(window.app.billing.billingManager.itemFrequency, doc.data());
947+
948+
// Reload dropdowns to reflect new order
949+
if (window.app.billing.loadItemsDropdown) {
950+
window.app.billing.loadItemsDropdown();
951+
}
952+
if (window.app.billing.loadSaleItemsDropdown) {
953+
window.app.billing.loadSaleItemsDropdown();
954+
}
955+
if (window.app.sales?.loadItemsDropdown) {
956+
window.app.sales.loadItemsDropdown();
957+
}
958+
}
959+
}, error => {
960+
console.error('Item frequency listener error:', error);
961+
});
962+
unsubscribeFunctions.push(unsubItemFrequency);
963+
964+
const unsubNotifications = getDb().collection(col('notifications'))
965+
.where('userId', '==', AppState.currentUser.uid)
966+
.where('read', '==', false)
967+
.orderBy('timestamp', 'desc')
968+
.limit(10)
969+
.onSnapshot(snapshot => {
970+
snapshot.docChanges().forEach(change => {
971+
if (change.type === 'added') {
972+
const notification = change.doc.data();
973+
// Show toast for rate change notifications
974+
if (notification.type === 'rate_change') {
975+
const itemName = notification.itemHindiName || notification.itemName;
976+
const message = `${itemName} ${notification.rateTypeLabel} rate: ₹${notification.oldRate} → ₹${notification.newRate} (by ${notification.changedBy})`;
977+
UIManager.showToast(message, 5000);
978+
979+
// Mark as read after showing
980+
getDb().collection(col('notifications')).doc(change.doc.id).update({ read: true });
981+
}
982+
}
983+
});
984+
}, error => {
985+
console.error('Notifications listener error:', error);
986+
});
987+
unsubscribeFunctions.push(unsubNotifications);
988+
}
938989
},
939990

940991
/**
@@ -975,6 +1026,75 @@ const FirebaseService = {
9751026
}
9761027
},
9771028

1029+
/**
1030+
* Notifies all users when an item rate is changed.
1031+
* Creates notification documents for all users in the notifications collection.
1032+
* Note: Wholesale rate changes only notify owners/managers (staff can't see wholesale rates)
1033+
* @async
1034+
* @param {Object} item - The item with changed rate
1035+
* @param {string} rateType - Type of rate ('purchase', 'sale', 'wholesale')
1036+
* @param {number} oldRate - Previous rate value
1037+
* @param {number} newRate - New rate value
1038+
* @returns {Promise<void>}
1039+
*/
1040+
async notifyRateChange(item, rateType, oldRate, newRate) {
1041+
try {
1042+
// Get users based on rate type
1043+
// Wholesale rates are only visible to owners/managers, so don't notify staff
1044+
let usersQuery;
1045+
if (rateType === 'wholesale') {
1046+
// Only notify owners and managers for wholesale rate changes
1047+
const ownersSnapshot = await getDb().collection(col('users')).where('role', '==', 'owner').get();
1048+
const managersSnapshot = await getDb().collection(col('users')).where('role', '==', 'manager').get();
1049+
const userIds = [
1050+
...ownersSnapshot.docs.map(doc => doc.id),
1051+
...managersSnapshot.docs.map(doc => doc.id)
1052+
];
1053+
usersQuery = { docs: userIds.map(id => ({ id })) };
1054+
} else {
1055+
// Notify all users for purchase and sale rate changes
1056+
usersQuery = await getDb().collection(col('users')).get();
1057+
}
1058+
1059+
const userIds = usersQuery.docs ? usersQuery.docs.map(doc => doc.id) : [];
1060+
1061+
const rateTypeLabels = {
1062+
'purchase': 'Purchase',
1063+
'sale': 'Sale',
1064+
'wholesale': 'Wholesale'
1065+
};
1066+
1067+
const notification = {
1068+
type: 'rate_change',
1069+
itemId: item.id,
1070+
itemName: item.name,
1071+
itemHindiName: item.hindiName || '',
1072+
rateType: rateType,
1073+
rateTypeLabel: rateTypeLabels[rateType] || rateType,
1074+
oldRate: oldRate,
1075+
newRate: newRate,
1076+
changedBy: AppState.userName || 'Unknown',
1077+
changedByUserId: AppState.currentUser?.uid,
1078+
timestamp: firebase.firestore.FieldValue.serverTimestamp(),
1079+
read: false
1080+
};
1081+
1082+
// Notify all relevant users except the one who made the change
1083+
for (const userId of userIds) {
1084+
if (userId !== AppState.currentUser?.uid) {
1085+
await getDb().collection(col('notifications')).add({
1086+
...notification,
1087+
userId: userId
1088+
});
1089+
}
1090+
}
1091+
1092+
console.log(`Rate change notification sent: ${item.name} ${rateType} rate: ₹${oldRate} → ₹${newRate}`);
1093+
} catch (error) {
1094+
console.error('Error notifying rate change:', error);
1095+
}
1096+
},
1097+
9781098
/**
9791099
* Loads withdrawals from Firestore ordered by timestamp (secondary method).
9801100
* @async

www/js/modules/billing.js

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,13 @@ const BillingManager = {
155155
// -------------------- ITEM FREQUENCY --------------------
156156

157157
/**
158-
* Load item frequency data from Firebase
158+
* Load item frequency data from Firebase (global, shared across all users)
159159
* @async
160160
*/
161161
async loadItemFrequency() {
162162
try {
163-
const userId = AppState.currentUser?.uid;
164-
if (!userId) return;
165-
166-
const doc = await db.collection(window.getCollection ? window.getCollection('itemFrequency') : 'itemFrequency').doc(userId).get();
163+
// Use a global document instead of per-user to ensure all users see same order
164+
const doc = await db.collection(window.getCollection ? window.getCollection('itemFrequency') : 'itemFrequency').doc('global').get();
167165
if (doc.exists) {
168166
this.itemFrequency = doc.data();
169167
} else {
@@ -176,16 +174,13 @@ const BillingManager = {
176174
},
177175

178176
/**
179-
* Update item frequency after a transaction
177+
* Update item frequency after a transaction (global, shared across all users)
180178
* @async
181179
* @param {Array<Object>} items - Items used in the transaction
182180
* @param {'purchase'|'sale'} [mode='purchase'] - The transaction mode
183181
*/
184182
async updateItemFrequency(items, mode = 'purchase') {
185183
try {
186-
const userId = AppState.currentUser?.uid;
187-
if (!userId) return;
188-
189184
const now = Date.now();
190185

191186
items.forEach(item => {
@@ -215,7 +210,8 @@ const BillingManager = {
215210
itemData.effectiveScore = itemData.score * decayFactor;
216211
});
217212

218-
await db.collection(window.getCollection ? window.getCollection('itemFrequency') : 'itemFrequency').doc(userId).set(this.itemFrequency);
213+
// Save to global document instead of per-user
214+
await db.collection(window.getCollection ? window.getCollection('itemFrequency') : 'itemFrequency').doc('global').set(this.itemFrequency);
219215
} catch (error) {
220216
console.error('Failed to update item frequency:', error);
221217
}

www/js/modules/items.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,17 @@ const ItemsManager = {
194194
// Update purchase rate (AUTO-SAVE with debounce)
195195
updateRate(itemIndex, rateIndex, value) {
196196
clearTimeout(this.updateTimers[`rate_${itemIndex}_${rateIndex}`]);
197-
AppState.items[itemIndex].rates[rateIndex] = Number(value);
197+
const oldRate = AppState.items[itemIndex].rates[rateIndex] || 0;
198+
const newRate = Number(value);
199+
AppState.items[itemIndex].rates[rateIndex] = newRate;
198200

199201
this.updateTimers[`rate_${itemIndex}_${rateIndex}`] = setTimeout(async () => {
200202
try {
201203
await FirebaseService.saveItem(AppState.items[itemIndex]);
204+
// Notify all users about rate change
205+
if (oldRate !== newRate && newRate > 0) {
206+
await FirebaseService.notifyRateChange(AppState.items[itemIndex], 'purchase', oldRate, newRate);
207+
}
202208
} catch (error) {
203209
console.error('Error updating rate:', error);
204210
}
@@ -228,11 +234,17 @@ const ItemsManager = {
228234
if (!AppState.items[itemIndex].saleRates) {
229235
AppState.items[itemIndex].saleRates = [];
230236
}
231-
AppState.items[itemIndex].saleRates[rateIndex] = Number(value);
237+
const oldRate = AppState.items[itemIndex].saleRates[rateIndex] || 0;
238+
const newRate = Number(value);
239+
AppState.items[itemIndex].saleRates[rateIndex] = newRate;
232240

233241
this.updateTimers[`salerate_${itemIndex}_${rateIndex}`] = setTimeout(async () => {
234242
try {
235243
await FirebaseService.saveItem(AppState.items[itemIndex]);
244+
// Notify all users about rate change
245+
if (oldRate !== newRate && newRate > 0) {
246+
await FirebaseService.notifyRateChange(AppState.items[itemIndex], 'sale', oldRate, newRate);
247+
}
236248
} catch (error) {
237249
console.error('Error updating sale rate:', error);
238250
}
@@ -264,11 +276,17 @@ const ItemsManager = {
264276
if (!AppState.items[itemIndex].wholesaleRates) {
265277
AppState.items[itemIndex].wholesaleRates = [];
266278
}
267-
AppState.items[itemIndex].wholesaleRates[rateIndex] = Number(value);
279+
const oldRate = AppState.items[itemIndex].wholesaleRates[rateIndex] || 0;
280+
const newRate = Number(value);
281+
AppState.items[itemIndex].wholesaleRates[rateIndex] = newRate;
268282

269283
this.updateTimers[`wholesalerate_${itemIndex}_${rateIndex}`] = setTimeout(async () => {
270284
try {
271285
await FirebaseService.saveItem(AppState.items[itemIndex]);
286+
// Notify all users about rate change
287+
if (oldRate !== newRate && newRate > 0) {
288+
await FirebaseService.notifyRateChange(AppState.items[itemIndex], 'wholesale', oldRate, newRate);
289+
}
272290
} catch (error) {
273291
console.error('Error updating wholesale rate:', error);
274292
}

www/js/modules/purchase.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ const PurchaseManager = {
574574
if (el) el.value = id === 'manualLaborCharges' ? '0' : '';
575575
});
576576

577-
['onlineCheckbox', 'cashCheckbox', 'dueCheckbox'].forEach(id => {
577+
['onlineCheckbox', 'cashCheckbox', 'dueCheckbox', 'billPrintComments'].forEach(id => {
578578
const el = document.getElementById(id);
579579
if (el) el.checked = false;
580580
});

www/js/modules/retail-sale.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ const RetailSaleManager = {
542542
if (el) el.value = '';
543543
});
544544

545-
['saleOnlineCheckbox', 'saleCashCheckbox', 'saleDueCheckbox'].forEach(id => {
545+
['saleOnlineCheckbox', 'saleCashCheckbox', 'saleDueCheckbox', 'salePrintComments'].forEach(id => {
546546
const el = document.getElementById(id);
547547
if (el) el.checked = false;
548548
});

www/js/services/printer.js

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -367,32 +367,57 @@ class BluetoothPrinterManager {
367367
y += config.spacing.line;
368368
}
369369

370-
// Comments (टिप्पणी) - Only print if printComments flag is true and comments exist
370+
// Comments - Only print if printComments flag is true and comments exist
371371
if (billData.printComments && billData.comments && billData.comments.trim()) {
372372
y += 8; // Gap before comments
373373
ctx.font = `${config.fonts.body.size}px Arial`;
374-
ctx.fillText('नोट:', config.padding.left, y);
375-
y += config.spacing.line;
376374

377-
// Word wrap comments to fit width
378-
const maxWidth = config.width - (config.padding.left * 2) - 10;
379-
const words = billData.comments.trim().split(' ');
380-
let line = '';
375+
// Print "Note: <comment>" on the same line, with word wrap if needed
376+
const noteLabel = 'Note: ';
377+
const labelWidth = ctx.measureText(noteLabel).width;
378+
const maxWidth = config.width - config.padding.left - 10;
379+
const commentText = billData.comments.trim();
381380

382-
for (const word of words) {
383-
const testLine = line + (line ? ' ' : '') + word;
384-
const testWidth = ctx.measureText(testLine).width;
385-
if (testWidth > maxWidth && line) {
386-
ctx.fillText(line, config.padding.left + 5, y);
387-
y += config.spacing.line - 4;
388-
line = word;
389-
} else {
390-
line = testLine;
391-
}
392-
}
393-
if (line) {
394-
ctx.fillText(line, config.padding.left + 5, y);
381+
// Check if it fits on one line
382+
const fullText = noteLabel + commentText;
383+
if (ctx.measureText(fullText).width <= maxWidth) {
384+
// Fits on one line
385+
ctx.fillText(fullText, config.padding.left, y);
395386
y += config.spacing.line;
387+
} else {
388+
// Need word wrap - print label first, then wrap remaining text
389+
ctx.fillText(noteLabel, config.padding.left, y);
390+
const remainingWidth = maxWidth - labelWidth;
391+
const words = commentText.split(' ');
392+
let line = '';
393+
let firstLine = true;
394+
395+
for (const word of words) {
396+
const testLine = line + (line ? ' ' : '') + word;
397+
const currentMaxWidth = firstLine ? remainingWidth : maxWidth;
398+
const testWidth = ctx.measureText(testLine).width;
399+
400+
if (testWidth > currentMaxWidth && line) {
401+
if (firstLine) {
402+
ctx.fillText(line, config.padding.left + labelWidth, y);
403+
firstLine = false;
404+
} else {
405+
ctx.fillText(line, config.padding.left, y);
406+
}
407+
y += config.spacing.line - 4;
408+
line = word;
409+
} else {
410+
line = testLine;
411+
}
412+
}
413+
if (line) {
414+
if (firstLine) {
415+
ctx.fillText(line, config.padding.left + labelWidth, y);
416+
} else {
417+
ctx.fillText(line, config.padding.left, y);
418+
}
419+
y += config.spacing.line;
420+
}
396421
}
397422
}
398423

0 commit comments

Comments
 (0)