Loading…
STC-CRM
Customer Relationship Management
Welcome!
This account is not linked to any workspace yet.
πŸ‘‘ Set up new workspace (I'm the owner)
Create your business workspace β€” you'll manage retailers, ledger, and team
πŸ’‘ Field staff: your login is created by the admin from the Team page. Please contact your admin if you can't sign in.
πŸ“Š Dashboard
Today πŸ’° Received Today: β‚Ή0 πŸ“€ Given Today: β‚Ή0 ⏳ Pending: 0 πŸ“ Today's entries: 0 πŸ‘€ β€”
πŸͺ
Total Retailers
0
β€”
⚠️
Total Due
β‚Ή0
0 retailers pending
⏳
Approval Pending
0
β‚Ή0 pending
βœ…
Paid This Month
β‚Ή0
0 payments
πŸ”„
Refile Due
0
β€”
⚠️ Top Balance (Highest balances)
πŸ† Team Leaderboard (this month)
⚑ Quick Actions
βž•
Add Retailer
πŸ’°
Payment Received
πŸ“¦
Stock Given
βœ…
Approvals
πŸ“’
My Entries
πŸ’¬
WhatsApp
πŸ“… Recent Activity
πŸ“ˆ Sale vs Receive Graph
πŸ“ˆ Last 7 Days
πŸ“Έ Recent Screenshots
πŸ“¦ Stock Given β€” Retailers
πŸ’° Payments Received β€” Retailers
πŸͺ Retailer Directory
πŸ’° Ledger / Balance Overview
πŸ“Š Due Aging β€” how old is your money?
πŸ“’ Retailer-wise Ledger
Retailer Total Given πŸ“€ Total Received πŸ’° Balance ⚠️ Pending ⏳ Days Overdue Actions
πŸ“¦ Stock Inventory β€” E-Top & SIM
πŸ“² E-Top In-Hand
β‚Ή0
β€”
πŸ“‡ SIM Stock Value
β‚Ή0
β€”
πŸ“€ Given This Month
β‚Ή0
E-Top + SIM combined
βž• Add Stock Purchase (from company)
πŸ“œ Purchase History
DateTypeAmountNoteAction
πŸ“… Monthly Report
πŸ† Top 10 Retailers (by received)
πŸ‘₯ Staff-wise Collection
πŸ“ˆ Day-wise Activity
πŸ“œ Ledger β€” All Transactions
Transaction History
Date Retailer Type Amount Mode Entry By Status Proof Notes
βœ… Approvals β€” FSE Entries
Field staff have submitted these payment entries. Review the screenshot or cash mention and approve β€” only approved entries count in the ledger.
πŸ•“ Recent Decisions
πŸ“’ My Entries
Your submitted entries β€” they become final once the admin approves them.
DateRetailerTypeAmountModeProofStatus
πŸ‘₯ Team β€” Staff & Managers
βž• Create New Field Staff
Share this password with the field staff β€” they can change it later
πŸ’‘ A new login will be created and this person will be able to sign in immediately as Field Staff (limited access β€” no admin settings).
πŸ‘€ Team Members
πŸ”„ Refile Tracker
πŸ”„ Refile Schedule
Retailer Last Refile Next Refile Due Days Remaining Plan Action
πŸ“Έ Payment Screenshots
Saved Screenshots
πŸ’¬ WhatsApp Integration
πŸ“¨ Message Templates
πŸ“€ Manual Send
⏰ Auto Reminders
πŸ”” Reminder Rules
πŸ’°
Due Payment Reminder
Auto send when payment is overdue by set days
days
πŸ”„
Refile Due Reminder
Alert before refile date expires
days before
πŸŽ‰
Payment Received Confirmation
Auto send confirmation when payment recorded
πŸ“…
Weekly Summary
Send weekly dues summary to self
⚑
Balance Low Alert
Alert when balance drops below threshold
β‚Ή
πŸ“‹ Reminder Log
⚠️ This check runs only while this page is open in the browser β€” if you close the tab or shut down your PC, reminders pause. They will catch up next time you open the app.
βš™οΈ Settings
Business Info
Default Refile Cycle
Data Management
☁️ Data is stored securely in database β€” login from any device to see the same data. CSV is for viewing in Excel. JSON backup does not include ledger transactions or screenshots (those live in separate cloud subcollections) β€” restore always from a JSON backup.
πŸš€ App Performance
ℹ️ Cache clear karne se data nahi jata β€” sirf app ki local copy (HTML/CSS/JS) delete hoti hai. Login ke baad sab data wapas aata hai cloud se. Agar app slow lage ya koi button kaam na kare to ye try karo.
⌘ Esc
↑↓ navigate ↡ select Ctrl+K toggle
🎨 Choose Your Theme
Your choice is saved on this device
'); try { const doc = await mediaCollection().doc(mediaId).get(); if (!doc.exists) { w.document.body.innerHTML = 'Image not found'; return; } const data = doc.data().data; w.document.body.innerHTML = ``; } catch (e) { w.document.body.innerHTML = 'Error loading image'; } } function viewPaymentSS(payId) { const p = DB.get('payments').find(x => x.id == payId); if (p?.screenshots?.length) viewSS(p.screenshots[0].mediaId); } // ══════════════════════════════════════════ // REMINDER CONFIG // ══════════════════════════════════════════ function saveReminderConfig() { const cfg = { dueDays: document.getElementById('due-days').value, refileDays: document.getElementById('refile-days').value, confirm: document.getElementById('tog-confirm').checked, weekly: document.getElementById('tog-weekly').checked, lowBalance: document.getElementById('low-balance').value, due: document.getElementById('tog-due').checked, refile: document.getElementById('tog-refile').checked, lowBal: document.getElementById('tog-lowbal').checked }; DB.setObj('reminderConfig', cfg); showToast('βœ… Reminder config saved!', 'success'); } function logActivity(msg, type) { if (myRole !== 'admin') return; // FSE ki activity txns se track hoti hai const log = DB.get('activityLog'); log.push({ msg, type, date: new Date().toISOString() }); DB.set('activityLog', log.slice(-100)); } function logReminderActivity(msg, type, status) { reminderLog = DB.get('reminderLog'); reminderLog.push({ msg, type, status, date: new Date().toISOString() }); DB.set('reminderLog', reminderLog.slice(-100)); } // ══════════════════════════════════════════ // SETTINGS // ══════════════════════════════════════════ function saveSettings() { DB.setObj('settings', { bizName: document.getElementById('biz-name').value, bizOwner: document.getElementById('biz-owner').value, bizPhone: document.getElementById('biz-phone').value, bizCity: document.getElementById('biz-city').value, refileCycle: document.getElementById('refile-cycle').value, paymentTerms: document.getElementById('payment-terms').value }); showToast('βœ… Settings saved!', 'success'); } function exportData() { retailers = DB.get('retailers'); let csv = 'Retailer Name,Retailer ID,Phone,Total Given,Total Received,Balance,Pending,Refile Plan,Last Refile,Area\n'; retailers.forEach(r => { const L = ledgerOf(r.id); csv += `"${r.name}","${r.retailerCode || ''}","${r.phone}",${Math.round(L.given)},${Math.round(L.received)},${Math.round(L.baki)},${Math.round(L.pendingIn + L.pendingOut)},${r.plan || 30},"${r.lastRefile || ''}","${r.area || ''}"\n`; }); csv += '\n\nDate,Retailer,Type,Amount,Mode,Status,Entry By,Reference,Note\n'; [...txns].reverse().forEach(t => { const r = retailers.find(x => x.id == t.retailerId); csv += `"${t.date || ''}","${r?.name || ''}","${t.type === 'given' ? 'Given' : 'Received'}",${t.amount},"${t.mode || ''}","${t.status}","${t.createdByName || ''}","${t.ref || ''}","${(t.note || '').replace(/"/g, "'")}"\n`; }); const a = document.createElement('a'); a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csv); a.download = 'STC_CM_Retailers_' + new Date().toISOString().split('T')[0] + '.csv'; a.click(); showToast('βœ… Data exported!', 'success'); } const BACKUP_KEYS = ['retailers','payments','screenshots','templates','reminderLog','activityLog']; const BACKUP_OBJ_KEYS = ['waConfig','settings','reminderConfig']; function markBackupDone() { try { localStorage.setItem('stc_last_backup', Date.now()); } catch(_) {} } function checkBackupReminder() { if (myRole !== 'admin') return; try { const last = parseInt(localStorage.getItem('stc_last_backup') || '0'); const days = (Date.now() - last) / 86400000; if (days > 7) { setTimeout(() => { showUndoSnackCustom( last ? `⏰ Last backup ${Math.floor(days)} days ago β€” take a backup!` : '⏰ No backup yet β€” protect your data!', 'πŸ’Ύ Backup Now', () => { navigate('settings'); setTimeout(exportBackup, 400); } ); }, 3500); } } catch(_) {} } function showUndoSnackCustom(msg, btnLabel, onClick) { document.getElementById('undo-snack')?.remove(); const bar = document.createElement('div'); bar.id = 'undo-snack'; bar.innerHTML = `${msg}`; document.body.appendChild(bar); document.getElementById('undo-btn').onclick = () => { bar.remove(); onClick(); }; document.getElementById('undo-x').onclick = () => bar.remove(); setTimeout(() => bar.remove(), 12000); } function exportBackup() { markBackupDone(); const backup = { _type: 'stc-cm-backup', _version: 1, exportedAt: new Date().toISOString(), data: {}, obj: {} }; BACKUP_KEYS.forEach(k => backup.data[k] = DB.get(k)); BACKUP_OBJ_KEYS.forEach(k => backup.obj[k] = DB.getObj(k)); const blob = new Blob([JSON.stringify(backup, null, 2)], { type: 'application/json' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'STC_CM_Backup_' + new Date().toISOString().split('T')[0] + '.json'; a.click(); showToast('βœ… Full backup downloaded!', 'success'); } function importData() { document.getElementById('backup-file-input').click(); } function importBackup(e) { const file = e.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = () => { let backup; try { backup = JSON.parse(reader.result); } catch (err) { showToast('❌ Invalid backup file β€” JSON parse fail hua', 'error'); e.target.value = ''; return; } if (backup._type !== 'stc-cm-backup' || !backup.data) { showToast('❌ This does not look like a valid backup file for this app', 'error'); e.target.value = ''; return; } if (!confirm('⚠️ Ye current data ko overwrite kar dega (backup date: ' + (backup.exportedAt ? new Date(backup.exportedAt).toLocaleString('en-IN') : 'unknown') + '). Continue?')) { e.target.value = ''; return; } BACKUP_KEYS.forEach(k => { if (backup.data[k] !== undefined) DB.set(k, backup.data[k]); }); BACKUP_OBJ_KEYS.forEach(k => { if (backup.obj && backup.obj[k] !== undefined) DB.setObj(k, backup.obj[k]); }); retailers = DB.get('retailers'); payments = DB.get('payments'); e.target.value = ''; showToast('βœ… Backup restore ho gaya!', 'success'); renderDashboard(); updateBadges(); }; reader.onerror = () => showToast('❌ Could not read the file', 'error'); reader.readAsText(file); } async function clearAllData() { if (myRole !== 'admin') return; if (!confirm('⚠️ All data will be deleted (ledger, screenshots, receipts). This cannot be undone. Confirm?')) return; ['retailers','payments','screenshots','templates','reminderLog','activityLog'].forEach(k => DB.set(k, [])); try { for (const col of [mediaCollection(), txnsCol()]) { let snap = await col.limit(400).get(); while (!snap.empty) { const batch = fsDB.batch(); snap.forEach(d => batch.delete(d.ref)); await batch.commit(); snap = await col.limit(400).get(); } } } catch (e) { showToast('⚠️ Some old data could not be deleted: ' + e.message, 'warning'); } showToast('πŸ—‘ All data cleared!', 'warning'); renderDashboard(); updateBadges(); } // ══════════════════════════════════════════ // SEARCH // ══════════════════════════════════════════ function globalSearch(q) { const raw = (q || '').trim(); const retailSearch = document.getElementById('ret-filter'); if (retailSearch) retailSearch.value = raw; if (!raw) { renderPage(currentPage); return; } if (currentPage !== 'retailers') { navigate('retailers'); return; } filterRetailers(raw); } // ══════════════════════════════════════════ // MODAL HELPERS // ══════════════════════════════════════════ function openModal(name) { if (name === 'recordPayment') populatePaymentDropdown(); document.getElementById('modal-' + name).classList.add('open'); } function closeModal(name) { document.getElementById('modal-' + name).classList.remove('open'); } // Close on overlay click document.querySelectorAll('.modal-overlay').forEach(el => { el.addEventListener('click', e => { if (e.target === el) el.classList.remove('open'); }); }); window.addEventListener('resize', () => { if (window.innerWidth > 768) closeMobileNav(); }); // ══════════════════════════════════════════ // TOAST // ══════════════════════════════════════════ function showToast(msg, type) { const tc = document.getElementById('toast-container'); const t = document.createElement('div'); t.className = 'toast ' + (type || ''); t.innerHTML = msg; tc.appendChild(t); setTimeout(() => t.remove(), 3000); } // ══════════════════════════════════════════ // AUTO REMINDER CHECKER (runs on load + every 60s) // ══════════════════════════════════════════ function checkAutoReminders() { if (myRole !== 'admin') return; const cfg = DB.getObj('reminderConfig'); if (!cfg.due && !cfg.refile) return; retailers = DB.get('retailers'); waConfig = DB.getObj('waConfig'); const waReady = !!(waConfig.token && waConfig.phoneId); const now = new Date(); retailers.forEach(r => { if (cfg.due) { const due = getRetailerDue(r.id); const days = getDueDays(r.id); if (due > 0 && days >= parseInt(cfg.dueDays || 3)) { // check if we already reminded today const logKey = 'reminded_' + r.id + '_' + now.toDateString(); if (!localStorage.getItem(logKey)) { if (waReady) { // Actually send via WhatsApp Cloud API (only possible while this tab is open) sendWAMessage(r, 'due', due); } else { logReminderActivity(`Due reminder for ${r.name} (β‚Ή${due}) β€” WhatsApp API not configured, please send manually`, 'AUTO', 'needs-manual'); } localStorage.setItem(logKey, '1'); } } } if (cfg.refile) { const next = getNextRefile(r); if (next) { const refileDays = Math.ceil((next - now) / 86400000); if (refileDays >= 0 && refileDays <= parseInt(cfg.refileDays || 2)) { const logKey = 'refile_' + r.id + '_' + now.toDateString(); if (!localStorage.getItem(logKey)) { if (waReady) { sendWAMessage(r, 'refile', 0); } else { logReminderActivity(`Refile alert for ${r.name} (${refileDays}d left) β€” WhatsApp API not configured, please send manually`, 'AUTO', 'needs-manual'); } localStorage.setItem(logKey, '1'); } } } } }); } // ══════════════════════════════════════════ // INIT β€” now handled by auth.onAuthStateChanged() above. // App only renders after a successful, verified login. // ══════════════════════════════════════════ // ══════════════════════════════════════════ // STC AI SIDEBAR // ══════════════════════════════════════════ const CF_WORKER_URL = atob('aHR0cHM6Ly9yb3VuZC11bml0LWQ=')+atob('ZGJkLmFzaGlzaC1taXNocmEzMDI=')+atob('LndvcmtlcnMuZGV2'); function toggleSidebarAI() { const panel = document.getElementById('sidebar-ai-panel'); if (!panel) return; const isOpen = panel.classList.contains('open'); panel.classList.toggle('open'); if (!isOpen) { setTimeout(() => { const inp = document.getElementById('sidebar-ai-input'); if (inp) inp.focus(); }, 100); } } function addSidebarMsg(role, text) { const box = document.getElementById('sidebar-ai-msgs'); if (!box) return null; const div = document.createElement('div'); div.className = role === 'user' ? 'ai-msg-user' : 'ai-msg-bot'; div.innerHTML = text.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\n/g, '
'); box.appendChild(div); box.scrollTop = 9999; return div; } function getSidebarContext() { const rList = (typeof retailers !== 'undefined' ? retailers : DB.get('retailers')) || []; const totalDue = rList.reduce((s, r) => s + Math.max(0, ledgerOf ? ledgerOf(r.id).baki : 0), 0); const top = rList .map(r => ({ name: r.name, due: ledgerOf ? ledgerOf(r.id).baki : 0 })) .filter(r => r.due > 0).sort((a, b) => b.due - a.due).slice(0, 5) .map(r => r.name + ': β‚Ή' + Math.round(r.due).toLocaleString('en-IN')).join(', '); const today = new Date().toISOString().slice(0, 10); const todayTxns = (typeof txns !== 'undefined' ? txns : []).filter(t => (t.date || '').slice(0, 10) === today || (t.createdAt || '').slice(0, 10) === today ); const todayIn = todayTxns.filter(t => t.type === 'received' && t.status === 'approved') .reduce((s, t) => s + parseFloat(t.amount || 0), 0); const pending = (typeof txns !== 'undefined' ? txns : []).filter(t => t.status === 'pending').length; return `You are "STC CRM Assistant" β€” AI built into STC CRM for Shree Trading Company, Sultanpur UP. IDENTITY: Always say you are "STC CRM Assistant". Never reveal Groq, LLaMA, Meta or any AI company name. ROLE: ${myRole === 'fse' ? 'Field Staff (can collect payments, view retailers)' : 'Admin (full access)'} DATA: ${rList.length} retailers | Total due: β‚Ή${Math.round(totalDue).toLocaleString('en-IN')} | Top dues: ${top || 'None'} | Today received: β‚Ή${Math.round(todayIn).toLocaleString('en-IN')} | Pending: ${pending} COMMANDS: If user says "receive/collect X from NAME" respond with JSON: {"action":"entry","type":"received","retailerName":"NAME","amount":X} If user says "open PAGE" respond with JSON: {"action":"navigate","page":"dashboard/retailers/balance/approvals"} Answer in simple English. Be brief.`; } async function sendSidebarAI() { const inp = document.getElementById('sidebar-ai-input'); if (!inp) return; const q = inp.value.trim(); if (!q) return; inp.value = ''; addSidebarMsg('user', q); const typing = addSidebarMsg('bot', '● ● ●'); try { const res = await fetch(CF_WORKER_URL + '/ai', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: [ { role: 'system', content: getSidebarContext() }, { role: 'user', content: q } ], max_tokens: 400 }) }); if (!res.ok) throw new Error('Error ' + res.status); const data = await res.json(); const reply = data.choices?.[0]?.message?.content || 'No response'; // Check for action command const actionMatch = reply.match(/\{\s*"action"\s*:[^}]+\}/); if (actionMatch) { try { const cmd = JSON.parse(actionMatch[0]); const display = reply.replace(actionMatch[0], '').trim() || 'Command ready:'; if (typing) typing.innerHTML = display.replace(/\*\*(.*?)\*\*/g,'$1').replace(/\n/g,'
'); if (cmd.action === 'navigate' && typeof navigate === 'function') { setTimeout(() => navigate(cmd.page || 'dashboard'), 500); } else if (cmd.action === 'entry') { const rList2 = (typeof retailers !== 'undefined' ? retailers : DB.get('retailers')) || []; const q2 = (cmd.retailerName || '').toLowerCase(); const r = rList2.find(x => x.name.toLowerCase().includes(q2) || q2.includes(x.name.toLowerCase().split(' ')[0])); const amt = parseFloat(cmd.amount) || 0; if (r && amt > 0) { // Show confirm in chat const confirmDiv = addSidebarMsg('bot', ''); if (confirmDiv) { confirmDiv.innerHTML = `Confirm entry?
πŸ’° Receive β‚Ή${amt.toLocaleString('en-IN')} from ${r.name}
`; } } else { if (typing) typing.innerHTML = '❌ Retailer "' + (cmd.retailerName || '') + '" not found.'; } } } catch(_) { if (typing) typing.innerHTML = reply.replace(/\*\*(.*?)\*\*/g,'$1').replace(/\n/g,'
'); } } else { if (typing) typing.innerHTML = reply.replace(/\*\*(.*?)\*\*/g,'$1').replace(/\n/g,'
'); } } catch(e) { if (typing) typing.innerHTML = '❌ ' + e.message; } } async function executeSidebarEntry(retailerId, type, amount, btn) { btn.disabled = true; btn.textContent = '⏳'; const card = btn.closest('.ai-msg-bot'); try { const txnData = { retailerId: String(retailerId), type, amount: parseFloat(amount), mode: 'Cash', note: 'AI entry', date: new Date().toISOString().split('T')[0], screenshots: [], status: myRole === 'admin' ? 'approved' : 'pending', createdBy: currentUser.uid, createdByName: myName || currentUser.email, createdByRole: myRole, createdAt: new Date().toISOString(), ...(myRole === 'admin' ? { decidedBy: currentUser.uid, decidedByName: myName, decidedAt: new Date().toISOString() } : {}) }; await fsDB.collection('users').doc(orgId).collection('txns').add(txnData); if (card) card.innerHTML = 'βœ… Entry saved! β‚Ή' + parseFloat(amount).toLocaleString('en-IN') + (myRole === 'fse' ? '
Pending admin approval' : ''); if (typeof showToast === 'function') showToast('βœ… Entry saved!', 'success'); } catch(e) { if (card) card.innerHTML = '❌ Failed: ' + e.message; } } // Voice for sidebar let sidebarVoiceActive = false, sidebarRecog = null; function toggleSidebarVoice() { const btn = document.getElementById('sidebar-ai-voice-btn'); if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { addSidebarMsg('bot', '⚠️ Voice not supported. Use Chrome.'); return; } if (sidebarVoiceActive) { if (sidebarRecog) sidebarRecog.stop(); return; } const SR = window.SpeechRecognition || window.webkitSpeechRecognition; sidebarRecog = new SR(); sidebarRecog.lang = 'en-IN'; sidebarRecog.onstart = () => { sidebarVoiceActive = true; if(btn) btn.textContent='πŸ”΄'; }; sidebarRecog.onresult = (e) => { const text = e.results[0][0].transcript; const inp = document.getElementById('sidebar-ai-input'); if (inp) { inp.value = text; sendSidebarAI(); } }; sidebarRecog.onend = () => { sidebarVoiceActive = false; if(btn) btn.textContent='🎀'; }; sidebarRecog.onerror = () => { sidebarVoiceActive = false; if(btn) btn.textContent='🎀'; }; sidebarRecog.start(); } // ══════════════════════════════════════════ // DAILY CLOSING SUMMARY // ══════════════════════════════════════════ let currentDetailRetailerId = ''; function todayTxnsList() { const today = new Date().toISOString().slice(0, 10); return txns.filter(t => (t.date || '').slice(0, 10) === today || (t.createdAt || '').slice(0, 10) === today); } function showDailySummary() { const now = new Date(); const list = todayTxnsList(); const ok = list.filter(t => t.status === 'approved'); const pend = list.filter(t => t.status === 'pending'); const recv = ok.filter(t => t.type === 'received'); const given = ok.filter(t => t.type === 'given'); const tIn = recv.reduce((s, t) => s + parseFloat(t.amount || 0), 0); const tOut = given.reduce((s, t) => s + parseFloat(t.amount || 0), 0); const net = tIn - tOut; const byStaff = {}; ok.forEach(t => { const n = t.createdByName || 'Admin'; if (!byStaff[n]) byStaff[n] = { r: 0, g: 0 }; if (t.type === 'received') byStaff[n].r += parseFloat(t.amount || 0); else byStaff[n].g += parseFloat(t.amount || 0); }); const staffRows = Object.entries(byStaff).map(([n, v]) => `${n}β‚Ή${Math.round(v.r).toLocaleString('en-IN')}β‚Ή${Math.round(v.g).toLocaleString('en-IN')}` ).join(''); document.getElementById('ds-title').textContent = 'πŸ“‹ Closing Summary β€” ' + now.toLocaleDateString('en-IN'); document.getElementById('ds-body').innerHTML = `
${now.toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
πŸ’° RECEIVED
β‚Ή${Math.round(tIn).toLocaleString('en-IN')}
${recv.length} entries
πŸ“€ GIVEN
β‚Ή${Math.round(tOut).toLocaleString('en-IN')}
${given.length} entries
πŸ“Š NET
β‚Ή${Math.round(Math.abs(net)).toLocaleString('en-IN')}
${net >= 0 ? 'in hand' : 'given out'}
${pend.length ? `
⏳ ${pend.length} entries pending approval (β‚Ή${Math.round(pend.reduce((s, t) => s + parseFloat(t.amount || 0), 0)).toLocaleString('en-IN')})
` : ''} ${staffRows ? `
πŸ‘₯ By Staff Member
${staffRows}
NameReceivedGiven
` : '
No approved entries today
'} `; openModal('dailySummary'); } function shareDailySummary() { const now = new Date(); const list = todayTxnsList(); const ok = list.filter(t => t.status === 'approved'); const tIn = ok.filter(t => t.type === 'received').reduce((s, t) => s + parseFloat(t.amount || 0), 0); const tOut = ok.filter(t => t.type === 'given').reduce((s, t) => s + parseFloat(t.amount || 0), 0); const pend = list.filter(t => t.status === 'pending').length; const msg = `πŸ“‹ *STC CRM β€” Daily Summary*\nπŸ“… ${now.toLocaleDateString('en-IN', { weekday: 'long', day: 'numeric', month: 'long' })}\n\nπŸ’° Received: *β‚Ή${Math.round(tIn).toLocaleString('en-IN')}*\nπŸ“€ Given: *β‚Ή${Math.round(tOut).toLocaleString('en-IN')}*\nπŸ“Š Net: *β‚Ή${Math.round(Math.abs(tIn - tOut)).toLocaleString('en-IN')}* ${tIn >= tOut ? 'in hand' : 'given out'}\n${pend ? `⏳ Pending approvals: ${pend}\n` : ''}\n_STC Sultanpur_`; window.open('https://wa.me/?text=' + encodeURIComponent(msg), '_blank'); } // ══════════════════════════════════════════ // RETAILER STATEMENT PDF // ══════════════════════════════════════════ let currentStmtRetailerId = ''; function openStatementPdf(id) { const r = DB.get('retailers').find(x => String(x.id) === String(id)); if (!r) return; currentStmtRetailerId = String(id); const L = ledgerOf(r.id); document.getElementById('stmt-pdf-title').textContent = 'πŸ“„ Statement β€” ' + r.name; document.getElementById('stmt-pdf-body').innerHTML = `
${r.name}
PRM: ${r.retailerCode || 'β€”'} | πŸ“± ${r.phone || 'β€”'} | πŸ“ ${r.area || 'β€”'}
Generated: ${new Date().toLocaleDateString('en-IN', { day: 'numeric', month: 'long', year: 'numeric' })}
TOTAL GIVEN
${fmtMoney(L.given)}
TOTAL RECEIVED
${fmtMoney(L.received)}
${L.baki > 0 ? 'BALANCE DUE' : L.baki < 0 ? 'ADVANCE' : 'STATUS'}
${L.baki !== 0 ? fmtMoney(Math.abs(L.baki)) : 'Clear βœ…'}
${buildStatement(r)}
STC CRM β€’ Shree Trading Company, Sultanpur β€’ Computer-generated statement
`; openModal('statementPdf'); } function printStatement() { const body = document.getElementById('stmt-pdf-body').innerHTML; const title = document.getElementById('stmt-pdf-title').textContent; const w = window.open('', '_blank', 'width=760,height=900'); w.document.write(`${title}

${title.replace('πŸ“„ ', '')}

${body}