❌ Invalid Username or Password
👁
Created by : SARAN
Menu
Dashboard
New Bill
Invoices
Inventory
Customers
Reports
Credits
Quotation
Services
GSTIN: 33DMIPK7820K1ZT
📞 9786386262
Natham, Dindigul-624401
✦ Created by – SARAN ✦
Dashboard
Loading...
💰
Today Sales
₹0
Current day
📅
This Month
₹0
Monthly total
🧾
Total Invoices
0
0 customers
💳
Total Credit Due
₹0
0 pending
⚠️
Low Stock
0
items

Recent Invoices

InvoiceCustomerDateAmountAction

Top Products

Low Stock Alert

All stocked.

New GST Invoice

Customer Details

Bill Items

Product / Service
Qty
Unit Price (₹)
Total
Taxable Value₹0.00
CGST (9%)₹0.00
SGST (9%)₹0.00
GRAND TOTAL₹0.00

Invoice Details

GST ON

Invoices

to
Invoice #CustomerPhoneDatePaymentTaxableGSTTotalAction

Inventory

ProductCategoryHSNBuy ₹Sell ₹MarginStockAction

Customers

NamePhoneGSTINAddressInvoicesTotal SpendLast Purchase

Reports

Monthly Sales

Top 10 Products by Revenue

💳 Credit / Due Management

Total Due: ₹0
Invoice #CustomerPhoneDateBill AmountPaidDueStatusAction

🔧 Service Management

Completed
0
services
In Progress
0
services
📋
Pending
0
services
💰
Total Revenue
₹0
from services
Job #CustomerPhoneDevice TypeComplaintStatusAmountDateAction

📋 Quotation

Customer Details

Quotation Items

Product / Service
Qty
Unit Price (₹)
Total
Taxable Value₹0.00
CGST (9%)₹0.00
SGST (9%)₹0.00
GRAND TOTAL₹0.00

Quotation Details

GST ON
`; } // ═══ PROFESSIONAL GST PRINT ═══ function printInv(inv){ const gstOff=(inv.gst_type==='none'||inv.gst_rate===0||inv.gst_amt===0); const isCSGST=!gstOff&&inv.gst_type==='cgst_sgst';const half=inv.gst_amt/2; const gstRate=inv.gst_rate||0;const halfRate=gstRate/2; // Pre-calculate QR for UPI const upiQRHtml=inv.payment_mode==='UPI'?(()=>{try{const qrUrl=makeQRDataURL('upi://pay?pa=boomisunthari@oksbi&pn=Chandran%20Computers',120);return '
Pay via UPI
UPI ID: boomisunthari@oksbi
Scan QR code with any UPI app
(GPay, PhonePe, Paytm, etc.)
Amount: ₹'+fmt(Math.round(inv.grand_total))+'
';}catch(e){return '';}})():''; // HSN summary const hsnMap={}; (inv.items||[]).forEach(it=>{const h=it.hsn||'';if(!hsnMap[h])hsnMap[h]={taxable:0};hsnMap[h].taxable+=it.total;}); const hsnRows=Object.entries(hsnMap).map(([hsn,d])=>{const ta=d.taxable*gstRate/100;const half2=ta/2;return`${hsn||'-'}${fmt(d.taxable)}${isCSGST?`${halfRate}%${fmt(half2)}${halfRate}%${fmt(half2)}`:`${gstRate}%${fmt(ta)}`}${fmt(ta)}`;}).join(''); document.getElementById('pz').innerHTML=`

CHANDRAN COMPUTERS

Gurunathan Kovil Street, Thelungar Street, Natham, Dindigul - 624401

Ph: 9786386262  |  GSTIN: 33DMIPK7820K1ZT

State: Tamil Nadu  |  State Code: 33

GST TAX INVOICE

Invoice No: ${inv.invoice_no}

Date: ${inv.date}

Payment: ${inv.payment_mode||'CASH'}

${inv.notes?`

Ref: ${inv.notes}

`:''}
Original For Recipient Subject to Natham Jurisdiction Computer & CCTV Accessories
Bill To / Buyer Details
${inv.customer_name||'Walk-in Customer'}
${inv.customer_address||''}
${inv.customer_phone?'Ph: '+inv.customer_phone:''} ${inv.customer_gstin?`
GSTIN: ${inv.customer_gstin}`:''}
State: Tamil Nadu, Code: 33
Seller / Consignor
Chandran Computers (Natham)
Gurunathan Kovil Street,
Thelungar Street, Natham,
Dindigul - 624401
GSTIN: 33DMIPK7820K1ZT
State: Tamil Nadu, Code: 33
#
Description of Goods & Services
HSN/SAC
Qty
Rate
Per
Amount
${(inv.items||[]).map((it,i)=>`
${i+1}
${it.product_name}
HSN: ${it.hsn||'N/A'}
${it.hsn||'-'}
${it.qty}
₹${fmt(it.unit_price)}
NOS
₹${fmt(it.total)}
`).join('')}
${isCSGST?`CGST Output @ ${halfRate}% + SGST Output @ ${halfRate}%`:`IGST Output @ ${gstRate}%`}
Round Off: ${((inv.sub+inv.gst_amt)-Math.round(inv.sub+inv.gst_amt)>=0?'+':'')}${((inv.sub+inv.gst_amt)-Math.round(inv.sub+inv.gst_amt)).toFixed(2)}
Subtotal
₹ ${fmt(inv.sub)}
Amount Chargeable (in words): ${numToWords(Math.round(inv.grand_total))}     Total Qty: ${(inv.items||[]).reduce((a,i)=>a+i.qty,0)} NOS
Tax Summary (HSN/SAC Wise)
${isCSGST?'':''} ${hsnRows} ${isCSGST?``:``}
HSN/SACTaxable ValueCGST RateCGST AmtSGST RateSGST AmtIGST RateIGST AmtTotal Tax
Total${fmt(inv.sub)}${fmt(half)}${fmt(half)}${fmt(inv.gst_amt)}${fmt(inv.gst_amt)}
Tax Amount (in words): ${numToWords(Math.round(inv.gst_amt))}
Taxable Value₹ ${fmt(inv.sub)}
${gstOff?'':isCSGST?`
CGST (${halfRate}%)₹ ${fmt(half)}
SGST (${halfRate}%)₹ ${fmt(half)}
`:`
IGST (${gstRate}%)₹ ${fmt(inv.gst_amt)}
`}
Grand Total₹ ${fmt(Math.round(inv.grand_total))}
${inv.payment_mode==='CREDIT'?`
Advance Paid₹ ${fmt(inv.paid_amount||0)}
⚠ Balance Due₹ ${fmt(inv.due_amount||0)}
`:''}
E. & O.E.
${inv.payment_mode==='CREDIT'?`
⚠ CREDIT / DUE INVOICE
Payment History: ${(inv.payment_log||[]).map(p=>p.date+' — ₹'+p.amount.toFixed(2)+' ('+p.mode+')').join(' | ')||'No payments yet'}
Paid: ₹${fmt(inv.paid_amount||0)}
Due: ₹${fmt(inv.due_amount||0)}
`:''} ${upiQRHtml}
For CHANDRAN COMPUTERS (NATHAM)
Authorised Signatory
`; setTimeout(()=>window.print(),300); } // ═══ INVOICES ═══ async function renderInvoices(){ const invs=await dbAll('invoices');const q=(document.getElementById('inv-srch').value||'').toLowerCase(); const fr=document.getElementById('inv-from').value;const to2=document.getElementById('inv-to').value; let list=[...invs].sort((a,b)=>b.id-a.id); if(q)list=list.filter(i=>(i.customer_name||'').toLowerCase().includes(q)||(i.invoice_no||'').toLowerCase().includes(q)); if(fr)list=list.filter(i=>i.date>=fr);if(to2)list=list.filter(i=>i.date<=to2); document.getElementById('inv-tbody').innerHTML=list.length?list.map(i=>` ${i.invoice_no} ${i.customer_name||'Walk-in'} ${i.customer_phone||'-'} ${i.date}${i.time?'
'+i.time+'':''} ${i.payment_mode||'CASH'} ₹${fmt(i.sub)} ₹${fmt(i.gst_amt)} ₹${fmt(i.grand_total)} `).join(''):'No invoices found'; } async function delInv(id){if(!confirm('Delete this invoice? Cannot be undone.'))return;await dbDel('invoices',id);toast('Invoice deleted');renderInvoices();updStat();} // ═══ VIEW INVOICE ═══ function viewInv(inv){ const gstOff=(inv.gst_type==='none'||inv.gst_rate===0||inv.gst_amt===0); const isCSGST=!gstOff&&inv.gst_type==='cgst_sgst';const half=inv.gst_amt/2;const gstRate=inv.gst_rate||0;const halfRate=gstRate/2; document.getElementById('mc2').innerHTML=`

Invoice ${inv.invoice_no}

Customer
${inv.customer_name||'Walk-in'}
${inv.customer_phone||''}
${inv.customer_address||''}
${inv.customer_gstin?`
GSTIN: ${inv.customer_gstin}
`:''}
Invoice Details
No: ${inv.invoice_no}
Date: ${inv.date}${inv.time?'  '+inv.time+'':''}
Payment: ${inv.payment_mode||'CASH'}
${(inv.items||[]).map((it,i)=>``).join('')}
#ProductQtyRateAmount
${i+1}${it.product_name}
HSN: ${it.hsn||'-'}
${it.qty}₹${fmt(it.unit_price)}₹${fmt(it.total)}
Taxable Value₹${fmt(inv.sub)}
${gstOff?'':isCSGST?`
CGST (${halfRate}%)₹${fmt(half)}
SGST (${halfRate}%)₹${fmt(half)}
`:`
IGST (${gstRate}%)₹${fmt(inv.gst_amt)}
`}
Grand Total₹${fmt(Math.round(inv.grand_total))}
`; document.getElementById('mov').classList.add('open'); } // ═══ EDIT INVOICE ═══ let _editItems=[]; async function editInv(inv){ const allP=await dbAll('products'); _editItems=JSON.parse(JSON.stringify(inv.items||[])); function renderEditRows(){ return _editItems.map((it,i)=>`
HSN: ${it.hsn||'-'}
₹${fmt(it.total)} `).join(''); } function calcEditTotal(){ const sub=_editItems.reduce((a,it)=>a+it.total,0); const gr=inv.gst_rate||18;const ga=sub*gr/100; document.getElementById('ei-sub').textContent='₹'+fmt(sub); document.getElementById('ei-gst').textContent='₹'+fmt(ga); document.getElementById('ei-grand').textContent='₹'+fmt(Math.round(sub+ga)); document.getElementById('ei-rows').innerHTML=renderEditRows(); } window.eiProdChange=(i,pid)=>{ const p=allP.find(x=>x.id==pid);if(!p)return; _editItems[i].product_name=p.name;_editItems[i].hsn=p.hsn||''; _editItems[i].unit_price=p.sell_price;_editItems[i].total=_editItems[i].qty*p.sell_price; calcEditTotal(); }; window.eiQtyChange=(i,v)=>{ _editItems[i].qty=parseInt(v)||1;_editItems[i].total=_editItems[i].qty*_editItems[i].unit_price; calcEditTotal(); }; window.eiPriceChange=(i,v)=>{ _editItems[i].unit_price=parseFloat(v)||0;_editItems[i].total=_editItems[i].qty*_editItems[i].unit_price; calcEditTotal(); }; window.eiDelRow=(i)=>{ if(_editItems.length<=1){toast('Minimum 1 item வேணும்','e');return;} _editItems.splice(i,1);calcEditTotal(); }; window.eiAddRow=()=>{ const p=allP[0];if(!p)return; _editItems.push({product_name:p.name,hsn:p.hsn||'',qty:1,unit_price:p.sell_price,total:p.sell_price}); calcEditTotal(); }; document.getElementById('mc2').innerHTML=`

✏️ Edit Invoice ${inv.invoice_no}

Items / Products
${renderEditRows()}
Product Qty Rate ₹ Amount
Subtotal₹${fmt(inv.sub)}
GST (${inv.gst_rate}%)₹${fmt(inv.gst_amt)}
Grand Total₹${fmt(Math.round(inv.grand_total))}
`; document.getElementById('mov').classList.add('open'); } async function saveEditInv(id,gstRate,gstType){ const invs=await dbAll('invoices'); const inv=invs.find(i=>i.id===id); if(!inv){toast('Invoice not found','e');return;} inv.customer_name=document.getElementById('ei-name').value.trim(); inv.customer_phone=document.getElementById('ei-phone').value.trim(); inv.customer_address=document.getElementById('ei-addr').value.trim(); inv.customer_gstin=document.getElementById('ei-gstin').value.trim(); inv.payment_mode=document.getElementById('ei-pay').value; inv.date=document.getElementById('ei-date').value; inv.notes=document.getElementById('ei-notes').value.trim(); inv.items=_editItems; inv.sub=_editItems.reduce((a,it)=>a+it.total,0); inv.gst_amt=inv.sub*gstRate/100; inv.grand_total=inv.sub+inv.gst_amt; await dbPut('invoices',inv); toast('Invoice updated successfully! ✅ Changes saved.'); closeM();renderInvoices();updStat(); } // ═══ ADVANCE FIELD TOGGLE ═══ function toggleAdvanceField(){ const mode=document.getElementById('bi-pay').value; const field=document.getElementById('advance-field'); const modeField=document.getElementById('advance-mode-field'); if(mode==='CREDIT'){field.style.display='';modeField.style.display='';} else{field.style.display='none';modeField.style.display='none';document.getElementById('bi-advance').value='';} } // ═══ CREDITS ═══ async function renderCredits(){ const invs=await dbAll('invoices'); const q=(document.getElementById('cr-srch').value||'').toLowerCase(); const filter=document.getElementById('cr-filter').value; let credits=invs.filter(i=>i.payment_mode==='CREDIT'); if(q)credits=credits.filter(i=>(i.customer_name||'').toLowerCase().includes(q)||(i.customer_phone||'').includes(q)); if(filter==='pending')credits=credits.filter(i=>i.credit_status==='pending'); else if(filter==='partial')credits=credits.filter(i=>i.credit_status==='partial'); else if(filter==='cleared')credits=credits.filter(i=>i.credit_status==='cleared'); credits.sort((a,b)=>b.id-a.id); const totalDue=credits.reduce((a,i)=>a+(i.due_amount||0),0); document.getElementById('cr-total-due').textContent='₹'+fmt(totalDue); document.getElementById('cr-tbody').innerHTML=credits.length?credits.map(i=>{ const paid=i.paid_amount||0; const due=i.due_amount!==undefined?i.due_amount:i.grand_total; const st=i.credit_status||'pending'; const stBadge=st==='cleared'?'✓ Cleared':st==='partial'?'⟳ Partial':'⏳ Pending'; const pj=JSON.stringify(i).replace(/"/g,'"'); return` ${i.invoice_no} ${i.customer_name||'Walk-in'} ${i.customer_phone||'-'} ${i.date}${i.time?'
'+i.time+'':''} ₹${fmt(i.grand_total)} ₹${fmt(paid)} ₹${fmt(due)} ${stBadge} ${due>0?``:''} `; }).join(''):'No credit invoices found'; } function livePayCalc(maxDue){ const amt=parseFloat(document.getElementById('pay-amt').value)||0; const rem=Math.max(maxDue-amt,0); const afterEl=document.getElementById('live-after-pay2'); const dueEl=document.getElementById('live-balance-due2'); if(afterEl) afterEl.textContent='₹'+fmt(rem); if(dueEl) dueEl.style.color=rem<0.01?'#065f46':'#c81e1e'; } function receivePayment(inv){ const due=inv.due_amount!==undefined?inv.due_amount:inv.grand_total; const payHist=(inv.payment_log||[]); const histRows=payHist.length?payHist.map(p=>`${p.date}₹${fmt(p.amount)}${p.mode||'-'}${p.note||''}`).join(''):'No payments yet'; document.getElementById('mc2').innerHTML=`

💵 Receive Payment — ${inv.invoice_no}

Customer${inv.customer_name||'Walk-in'} ${inv.customer_phone?'('+inv.customer_phone+')':''}
Bill Total₹${fmt(inv.grand_total)}
Already Paid₹${fmt(inv.paid_amount||0)}
Balance Due₹${fmt(due)}
After This Payment₹0.00
${payHist.length?`
📋 Payment History
${histRows}
DateAmountModeNote
`:''}
${inv.customer_phone?``:''}
`; document.getElementById('mov').classList.add('open'); } async function confirmPayment(invId,maxDue){ const amt=parseFloat(document.getElementById('pay-amt').value)||0; if(amt<=0||amt>maxDue+0.01){toast('Valid amount enter பண்ணுங்க','e');return;} const invs=await dbAll('invoices'); const inv=invs.find(i=>i.id===invId); if(!inv){toast('Invoice not found','e');return;} const prevPaid=inv.paid_amount||0; const newPaid=prevPaid+amt; const newDue=inv.grand_total-newPaid; const payMode=document.getElementById('pay-mode').value; const payNote=document.getElementById('pay-note').value; inv.paid_amount=newPaid; inv.due_amount=newDue<0.01?0:newDue; inv.credit_status=newDue<0.01?'cleared':'partial'; inv.last_payment_date=new Date().toISOString().split('T')[0]; inv.last_payment_mode=payMode; inv.payment_notes=(inv.payment_notes||'')+(payNote?' | '+payNote:''); if(!inv.payment_log)inv.payment_log=[]; inv.payment_log.push({date:new Date().toISOString().split('T')[0],amount:amt,mode:payMode,note:payNote}); await dbPut('invoices',inv); toast(inv.credit_status==='cleared'?'✅ Full payment received! Credit cleared!':'💵 ₹'+fmt(amt)+' received. Due: ₹'+fmt(inv.due_amount)); closeM(); // Show receipt print option if(confirm('Payment recorded! Receipt print பண்ணணுமா?')){printPaymentReceipt(inv,amt,payMode,payNote);} renderCredits();loadDash(); } function printPaymentReceipt(inv,amt,mode,note){ const receiptHtml=`Payment Receipt

Chandran Computers

Natham, Dindigul

GST Billing System

Payment Receipt
Receipt Date${new Date().toLocaleDateString('en-IN')}
Invoice No${inv.invoice_no}
Customer${inv.customer_name||'Walk-in'}
${inv.customer_phone?`
Phone${inv.customer_phone}
`:''}
Invoice Total₹${fmt(inv.grand_total)}
Total Paid (before)₹${fmt(inv.paid_amount-amt)}
Amount Received₹${fmt(amt)}
Payment Mode${mode}
${note?`
Notes${note}
`:''}
${inv.due_amount>0?'Balance Due: ₹'+fmt(inv.due_amount):'✅ Fully Cleared'}
Thank you for your payment!
Chandran Computers — ${new Date().toLocaleString('en-IN')}

`; const w=window.open('','_blank','width=400,height=600'); w.document.write(receiptHtml);w.document.close(); } function printCreditStatement(inv){ const due=inv.due_amount||0; const paid=inv.paid_amount||0; const payLog=inv.payment_log||[]; const payRows=payLog.length?payLog.map(p=>` ${p.date} ₹${fmt(p.amount)} ${p.mode||'-'} ${p.note||''} `).join(''):'No payments recorded'; const items=inv.items||[]; const itemRows=items.map(it=>` ${it.product_name} ${it.qty} ₹${fmt(it.unit_price)} ₹${fmt(it.total)} `).join(''); const statusColor=due<=0?'#065f46':'#c81e1e'; const statusBg=due<=0?'#dcfce7':'#fee2e2'; const statusText=due<=0?'✅ FULLY CLEARED':'⚠️ BALANCE DUE: ₹'+fmt(due); const w=window.open('','_blank','width=680,height=800'); w.document.write(`Credit Statement — ${inv.invoice_no}

Chandran Computers

Natham, Dindigul — GSTIN: 33DMIPK7820K1ZT

📞 9786386262

Credit Statement
Invoice No${inv.invoice_no}
Invoice Date${inv.date}
Customer${inv.customer_name||'Walk-in'}
Phone${inv.customer_phone||'-'}
${inv.customer_address?`
Address${inv.customer_address}
`:''}
📦 Items Purchased
${itemRows}
Product / ServiceQtyUnit PriceTotal
Taxable Value₹${fmt(inv.sub)}
${inv.gst_rate>0?`
GST (${inv.gst_rate}%)₹${fmt(inv.gst_amt)}
`:''}
Grand Total₹${fmt(inv.grand_total)}
Total Paid₹${fmt(paid)}
Balance Due₹${fmt(due)}
💳 Payment History
${payRows}
DateAmountModeNotes
${statusText}
Printed on ${new Date().toLocaleString('en-IN')}  |  Chandran Computers, Natham

`); w.document.close(); // Auto-save to server -> credits_pdf/ try { const crHtml = w.document.documentElement ? w.document.documentElement.outerHTML : ''; fetch(API+'/api.php/api/save-credit-pdf', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ invoice_no: inv.invoice_no, html: '' + crHtml + '' }) }).then(r=>r.json()).then(d=>{ if(d.ok) toast('Credit statement saved to server! ('+d.file+')', 's'); }).catch(()=>{}); } catch(e) {} } function sendWhatsAppReminder(inv){ const due=inv.due_amount||0; const paid=inv.paid_amount||0; const isCleared=due<=0; const payHistLines=(inv.payment_log||[]).map(p=>` 📅 ${p.date} — ₹${fmt(p.amount)} (${p.mode})`).join('\n'); const msg=isCleared? `🏪 *Chandran Computers, Natham* வணக்கம் ${inv.customer_name} அவர்களே, 🎉 *Full Payment Received! Thank You!* 📄 Invoice No: *${inv.invoice_no}* 📅 Invoice Date: ${inv.date} 💰 Total Amount: *₹${fmt(inv.grand_total)}* 💳 *Payment History:* ${payHistLines||' ✅ Full payment received'} ✅ *Balance: ₹0.00 — Fully Cleared!* உங்கள் நம்பிக்கைக்கு மிக்க நன்றி! 🙏 Chandran Computers 📞 9786386262` : `🏪 *Chandran Computers, Natham* வணக்கம் ${inv.customer_name} அவர்களே, தங்களுக்கு ஒரு payment reminder: 📄 Invoice No: *${inv.invoice_no}* 📅 Date: ${inv.date} 💰 Total Amount: *₹${fmt(inv.grand_total)}* ✅ Paid: ₹${fmt(paid)} 🔴 *Balance Due: ₹${fmt(due)}* ${payHistLines?`💳 *Payment History:*\n${payHistLines}\n\n`:''}தயவுசெய்து மேற்கண்ட தொகையை விரைவில் செலுத்துமாறு கேட்டுக்கொள்கிறோம். நன்றி! 🙏 Chandran Computers 📞 9786386262`; if(!inv.customer_phone){ // No phone — show message to copy manually const box=document.createElement('div'); box.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;border:2px solid #25d366;border-radius:12px;padding:20px;z-index:9999;max-width:380px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,0.2)'; box.innerHTML=`
📲 WhatsApp Message
${msg.replace(/\*/g,'')}
⚠ Phone number இல்ல — manually copy பண்ணி அனுப்புங்க
`; document.body.appendChild(box); return; } const phone=inv.customer_phone.replace(/[^0-9]/g,''); const intlPhone=phone.startsWith('91')?phone:'91'+phone; window.open('https://wa.me/'+intlPhone+'?text='+encodeURIComponent(msg),'_blank'); } async function exportCSV(){ const invs=await dbAll('invoices'); // Build PDF with products, customer name & address const sorted=invs.sort((a,b)=>a.id-b.id); let rows=''; sorted.forEach((inv,idx)=>{ const items=(inv.items||[]).map(it=>`${it.product_name}${it.qty}₹${fmt(it.unit_price)}₹${fmt(it.total)}`).join(''); rows+=`
Invoice: ${inv.invoice_no||'-'}
Date: ${inv.date||'-'}  |  Payment: ${inv.payment_mode||'-'}
CUSTOMER
${inv.customer_name||'Walk-in'}
${inv.customer_address||'-'}
${inv.customer_phone?`
Ph: ${inv.customer_phone}
`:''}
${items}
Product Qty Unit Price Total
Grand Total: ₹${fmt(inv.grand_total||0)}
`; }); const dateStr=new Date().toLocaleDateString('en-IN').replace(/\//g,'-'); const html=`Chandran Computers - Invoice Export
CHANDRAN COMPUTERS
Invoice Export Report — ${new Date().toLocaleDateString('en-IN')}
Total Invoices: ${sorted.length}
${rows||'

No invoices found.

'}
Generated by Chandran GST Billing System
`; // Save to server invoices_csv folder try{ const fname='Invoice_Report_'+dateStr+'.html'; await fetch(API+'/api.php/api/save-csv-pdf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({filename:fname,html})}); toast('Report saved to invoices_csv folder!'); }catch(e){toast('Could not save to server','e');} // Also open print dialog const w=window.open('','_blank');w.document.write(html);w.document.close(); setTimeout(()=>{w.print();},500); } async function saveInvToCSV(inv){ const invHtml=genInvHTML(inv); const fname=(inv.invoice_no||'INV')+'_'+inv.date+'.html'; try{ const r=await fetch(API+'/api.php/api/save-csv-pdf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({filename:fname,html:invHtml})}); const d=await r.json(); if(d.ok){toast('💾 Saved to invoices_csv: '+d.file);} else{toast('Save failed','e');} }catch(e){toast('Server error — is server running?','e');} } async function deleteAllProducts(){ if(!confirm('⚠️ Delete ALL products? This cannot be undone!'))return; if(!confirm('Are you sure? All '+((await dbAll('products')).length)+' products will be deleted permanently!'))return; const prods=await dbAll('products'); for(const p of prods){await dbDel('products',p.id);} // Also sync to server try{const res=await fetch(API+'/api.php/api/products');const sp=await res.json();for(const p of sp){await fetch(API+'/api.php/api/products/'+p.id,{method:'DELETE'});}}catch(e){} toast('All products deleted!','e');renderProducts();updStat(); } // ═══ INVENTORY ═══ const CATS=['All','Computer Accessories','CCTV Camera','CCTV Accessories','Networking','Storage','Printer Accessories','Service','Other']; let curCat='All'; function buildPills(){const el=document.getElementById('cpills');if(el.children.length)return;el.innerHTML=CATS.map(c=>``).join('');} function setCat(cat,el){curCat=cat;document.querySelectorAll('.cp').forEach(p=>p.classList.remove('active'));el.classList.add('active');renderProducts();} async function renderProducts(){ buildPills();let prods=await dbAll('products'); const q=(document.getElementById('prod-srch').value||'').toLowerCase(); if(curCat!=='All')prods=prods.filter(p=>p.category===curCat); if(q)prods=prods.filter(p=>p.name.toLowerCase().includes(q)||(p.category||'').toLowerCase().includes(q)); prods.sort((a,b)=>a.category.localeCompare(b.category)||a.name.localeCompare(b.name)); const CC={'Computer Accessories':'bb','CCTV Camera':'bp','CCTV Accessories':'bb','Networking':'ba','Storage':'bg','Printer Accessories':'bp','Service':'bg'}; function getProdIcon(name,cat){ const n=(name||'').toLowerCase(); if(n.includes('bullet')||n.includes('dome')||n.includes('ip camera')||n.includes('cctv camera')) return '📷'; if(n.includes('dvr')) return '📼'; if(n.includes('nvr')) return '🗄️'; if(n.includes('bnc')) return '🔩'; if(n.includes('hdmi')||n.includes('vga')) return '🔌'; if(n.includes('ethernet')||n.includes('cat6')||n.includes('lan cable')) return '🌐'; if(n.includes('cctv cable')||n.includes('3+1')) return '🔗'; if(n.includes('cable')) return '🔗'; if(n.includes('power supply')) return '⚡'; if(n.includes('ups')) return '🔋'; if(n.includes('power strip')) return '🔌'; if(n.includes('bracket')||n.includes('mount')) return '🪛'; if(n.includes('hdd')||n.includes('hard')) return '💽'; if(n.includes('ssd')) return '💾'; if(n.includes('pendrive')||n.includes('pen drive')) return '🖲️'; if(n.includes('ram')||n.includes('memory')) return '🧠'; if(n.includes('monitor')||n.includes('display')) return '🖥️'; if(n.includes('keyboard')&&n.includes('mouse')) return '⌨️'; if(n.includes('keyboard')) return '⌨️'; if(n.includes('mouse')) return '🖱️'; if(n.includes('usb hub')||n.includes('hub')) return '🔌'; if(n.includes('router')||n.includes('wifi')) return '📡'; if(n.includes('switch')||n.includes('poe')) return '🔀'; if(n.includes('toner')||n.includes('cartridge')||n.includes('ink')) return '🖨️'; if(n.includes('cool')||n.includes('fan')||n.includes('pad')) return '❄️'; if(n.includes('install')||n.includes('labour')) return '🔧'; if(n.includes('packing')||n.includes('courier')) return '📦'; if(n.includes('service')||n.includes('charge')) return '🛠️'; if(cat==='CCTV Camera') return '📷'; if(cat==='CCTV Accessories') return '🔧'; if(cat==='Computer Accessories') return '💻'; if(cat==='Networking') return '🌐'; if(cat==='Storage') return '💽'; if(cat==='Printer Accessories') return '🖨️'; if(cat==='Service') return '🛠️'; return '📦'; } document.getElementById('prod-tbody').innerHTML=prods.length?prods.map(p=>{ const mg=p.buy_price>0?Math.round((p.sell_price-p.buy_price)/p.buy_price*100):0; const sc=p.stock===0?'br':p.stock<=5?'ba':'bg'; const cc=CC[p.category]||'bb'; const pj=JSON.stringify(p).replace(/"/g,'"'); const icon=getProdIcon(p.name,p.category); return`
${icon} ${p.name}
${p.category} ${p.hsn||'-'} ₹${fmt(p.buy_price)} ₹${fmt(p.sell_price)} ${mg}% ${p.stock} `}).join(''):'No products found'; } function prodModal(title,p,onSave){ document.getElementById('mc2').innerHTML=`

${title}

`; window._ms=()=>onSave({name:document.getElementById('mp-n').value.trim(),category:document.getElementById('mp-c').value,hsn:document.getElementById('mp-h').value,buy_price:parseFloat(document.getElementById('mp-b').value)||0,sell_price:parseFloat(document.getElementById('mp-s').value)||0,stock:parseInt(document.getElementById('mp-q').value)||0}); document.getElementById('mov').classList.add('open'); } function openAddProd(){prodModal('Add New Product',{},async d=>{if(!d.name){toast('Name required','e');return;}await dbAdd('products',d);toast('Product added successfully!');renderProducts();updStat();});} function openEditProd(p){prodModal('Edit Product',p,async d=>{if(!d.name){toast('Name required','e');return;}await dbPut('products',{...d,id:p.id});toast('Product updated!');renderProducts();});} async function delProd(id){if(!confirm('Delete this product?'))return;await dbDel('products',id);toast('Product deleted');renderProducts();updStat();} function closeM(){document.getElementById('mov').classList.remove('open');} function submitM(){window._ms&&window._ms();closeM();} document.getElementById('mov').addEventListener('click',e=>{if(e.target===document.getElementById('mov'))closeM();}); // ═══ CUSTOMERS ═══ async function renderCustomers(q=''){ const invs=await dbAll('invoices');const map={}; invs.forEach(i=>{const k=i.customer_phone||i.customer_name;if(!k)return;if(!map[k])map[k]={name:i.customer_name,phone:i.customer_phone||'-',gstin:i.customer_gstin||'-',address:i.customer_address||'-',cnt:0,total:0,last:''};map[k].cnt++;map[k].total+=i.grand_total;if(i.date>map[k].last)map[k].last=i.date;}); let list=Object.values(map);if(q)list=list.filter(c=>(c.name||'').toLowerCase().includes(q.toLowerCase())||(c.phone||'').includes(q)); document.getElementById('cust-tbody').innerHTML=list.length?list.map(c=>`${c.name||'-'}${c.phone}${c.gstin}${c.address}${c.cnt}₹${fmt(c.total)}${c.last||'-'}`).join(''):'No customers yet. Save invoices first.'; } // ═══ REPORTS ═══ async function renderReports(){ const invs=await dbAll('invoices'); const todayS=invs.filter(i=>i.date===tod()).reduce((a,i)=>a+i.grand_total,0); const monS=invs.filter(i=>i.date.startsWith(tmon())).reduce((a,i)=>a+i.grand_total,0); const totalR=invs.reduce((a,i)=>a+i.grand_total,0); const custs=new Set(invs.map(i=>i.customer_phone||i.customer_name)).size; document.getElementById('rep-m').innerHTML=`
Today
₹${fmtN(todayS)}
This Month
₹${fmtN(monS)}
Total Revenue
₹${fmtN(totalR)}
Customers
${custs}
`; const mMap={};invs.forEach(i=>{const m=i.date.slice(0,7);mMap[m]=(mMap[m]||0)+i.grand_total;}); const months=Object.entries(mMap).sort((a,b)=>a[0].localeCompare(b[0])).slice(-6); const maxV=Math.max(...months.map(m=>m[1]),1); document.getElementById('month-chart').innerHTML=months.length?months.map(([m,v])=>`
${m}
₹${fmtN(v)}
`).join(''):'
No data yet
'; const sm={};invs.forEach(inv=>(inv.items||[]).forEach(it=>{if(!sm[it.product_name])sm[it.product_name]={qty:0,rev:0};sm[it.product_name].qty+=it.qty;sm[it.product_name].rev+=it.total;})); const top10=Object.entries(sm).sort((a,b)=>b[1].rev-a[1].rev).slice(0,10); document.getElementById('top-rpt').innerHTML=top10.length?top10.map(([n,d],i)=>`
${i+1}${n}${d.qty} nos₹${fmtN(d.rev)}
`).join(''):'
No sales yet
'; } // ═══ BACKUP / RESTORE ═══ async function backupData(){const prods=await dbAll('products'),invs=await dbAll('invoices'),meta=await dbAll('meta');const data=JSON.stringify({products:prods,invoices:invs,meta,backup_date:new Date().toISOString()},null,2);const a=document.createElement('a');a.href=URL.createObjectURL(new Blob([data],{type:'application/json'}));a.download='Chandran_Backup_'+tod()+'.json';a.click();toast('Backup downloaded!');} async function restoreData(input){const file=input.files[0];if(!file)return;const text=await file.text();try{const data=JSON.parse(text);if(!confirm('Restore backup from '+data.backup_date+'?\nThis will update all data!'))return;for(const p of data.products||[])await dbPut('products',p);for(const i of data.invoices||[])await dbPut('invoices',i);for(const m of data.meta||[])await dbPut('meta',m);toast('Data restored!');updStat();loadDash();}catch(e){toast('Invalid backup file!','e');}input.value='';} // ═══ LOGIN PARTICLES ═══ function spawnParticles(){ const lp=document.getElementById('login-page'); if(!lp)return; const colors=['rgba(139,92,246,0.7)','rgba(236,72,153,0.7)','rgba(6,182,212,0.7)','rgba(167,139,250,0.5)']; for(let i=0;i<18;i++){ const p=document.createElement('div'); p.className='lp-particle'; const size=Math.random()*4+1; p.style.cssText=`width:${size}px;height:${size}px;left:${Math.random()*100}%;background:${colors[Math.floor(Math.random()*colors.length)]};animation-duration:${Math.random()*8+5}s;animation-delay:${Math.random()*5}s;box-shadow:0 0 6px currentColor`; lp.appendChild(p); } } spawnParticles(); // ═══ LOGIN ═══ function togglePassEye(){ const p=document.getElementById('lb-pass'); const eye=document.getElementById('eye-icon'); if(p.type==='password'){p.type='text';eye.textContent='🙈';} else{p.type='password';eye.textContent='👁';} } function doLogin(){ const u=(document.getElementById('lb-user').value||'').trim(); const pw=(document.getElementById('lb-pass').value||''); const err=document.getElementById('lb-err'); const validUser=(u==='Chan'||u==='chan'||u==='CHAN'); const validPass=(pw==='Chan@6262'||pw==='Chan6262'||pw==='chan6262'); if(validUser && validPass){ const lp=document.getElementById('login-page'); lp.style.transition='opacity .6s ease'; lp.style.opacity='0'; err.style.display='none'; // Bring this window to front immediately window.focus(); document.getElementById('lb-btn').blur(); setTimeout(()=>{ lp.style.display='none'; window.focus(); document.getElementById('page-dashboard').focus&&document.getElementById('page-dashboard').focus(); },600); // Log login fetch(API+'/api.php/api/login-log',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,datetime:new Date().toLocaleString('en-IN'),status:'SUCCESS',browser:navigator.userAgent.split(')')[0].split('(')[1]||'Unknown'})}).catch(()=>{}); } else { err.style.display='block'; document.getElementById('lb-user').style.borderColor='rgba(220,38,38,0.7)'; document.getElementById('lb-pass').style.borderColor='rgba(220,38,38,0.7)'; setTimeout(()=>{ document.getElementById('lb-user').style.borderColor=''; document.getElementById('lb-pass').style.borderColor=''; },1500); // Log failed fetch(API+'/api.php/api/login-log',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u||'(empty)',datetime:new Date().toLocaleString('en-IN'),status:'FAILED',browser:navigator.userAgent.split(')')[0].split('(')[1]||'Unknown'})}).catch(()=>{}); } } // ═══════════════════════════════════════ // QUOTATION MODULE // ═══════════════════════════════════════ let qtAllP = []; async function nextQtNo(){ try{ const r = await fetch(API+'/api.php/api/qt-counter'); const d = await r.json(); return d.quotation_no; } catch(e){ // fallback if server not updated yet const qts = await qtDbAll(); const num = qts.length ? Math.max(...qts.map(q=>parseInt((q.quotation_no||'QT1000').replace('QT',''))||1000))+1 : 1001; return 'QT'+num; } } async function qtDbAll(){ try{const r=await fetch(API+'/api.php/api/quotations');return await r.json();}catch(e){return [];} } async function qtDbAdd(obj){ const r=await fetch(API+'/api.php/api/quotations',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(obj)}); return await r.json(); } async function qtDbDel(id){ await fetch(API+'/api.php/api/quotations/'+id,{method:'DELETE'}); } async function initQuotation(){ qtAllP = await dbAll('products'); const t = tod(); document.getElementById('qt-date').value = t; // Default valid until = 7 days from today const vd = new Date(); vd.setDate(vd.getDate()+7); document.getElementById('qt-valid').value = vd.toISOString().slice(0,10); document.getElementById('qt-no').value = await nextQtNo(); if(!document.getElementById('qt-rows').children.length) qtAddRow(); showQuotationForm(); } function showQuotationForm(){ document.getElementById('qt-form-section').style.display=''; document.getElementById('qt-list-section').style.display='none'; } function showQuotationList(){ document.getElementById('qt-form-section').style.display='none'; document.getElementById('qt-list-section').style.display=''; renderQuotations(); } function qtAddRow(){ const div = document.createElement('div'); div.className = 'bir'; div.style.cssText = 'display:grid;grid-template-columns:2.5fr 70px 115px 100px 32px;gap:7px;margin-bottom:6px;align-items:center'; const datalist = 'qt-prod-dl'; if(!document.getElementById(datalist)){ const dl=document.createElement('datalist');dl.id=datalist; qtAllP.forEach(p=>{const o=document.createElement('option');o.value=p.name;dl.appendChild(o);}); document.body.appendChild(dl); } div.innerHTML=` ₹0.00 `; document.getElementById('qt-rows').appendChild(div); qtCalc(); } function qtFillPrice(inp){ const p = qtAllP.find(x=>x.name===inp.value); if(p){ const row = inp.closest('.bir'); const inputs = row.querySelectorAll('input'); if(!inputs[1].value) inputs[1].value=1; inputs[2].value = p.sell_price; qtCalc(); } } function qtCalc(){ const rows = document.querySelectorAll('#qt-rows .bir'); let sub=0; rows.forEach(r=>{ const ins=r.querySelectorAll('input'); const qty=parseFloat(ins[1].value)||0, up=parseFloat(ins[2].value)||0; const tot=qty*up; sub+=tot; r.querySelectorAll('span')[0].textContent='₹'+tot.toFixed(2); }); const gstOn=document.getElementById('qt-gst-toggle').checked; const gr=gstOn?(parseFloat(document.getElementById('qt-gst').value)||18):0; const gsttype=gstOn?document.getElementById('qt-gsttype').value:'none'; const ga=sub*gr/100; document.getElementById('qt-sub').textContent='₹'+sub.toFixed(2); const isCGST=gsttype==='cgst_sgst'; document.getElementById('qt-tr-cgst').style.display=isCGST?'':'none'; document.getElementById('qt-tr-sgst').style.display=isCGST?'':'none'; document.getElementById('qt-tr-igst').style.display=(gstOn&&!isCGST)?'':'none'; if(isCGST){ document.getElementById('qt-cgst').textContent='₹'+(ga/2).toFixed(2); document.getElementById('qt-sgst').textContent='₹'+(ga/2).toFixed(2); document.querySelector('[id="qt-tr-cgst"] span').textContent='CGST ('+(gr/2)+'%)'; document.querySelector('[id="qt-tr-sgst"] span').textContent='SGST ('+(gr/2)+'%)'; } else { document.getElementById('qt-igst').textContent='₹'+ga.toFixed(2); document.querySelector('[id="qt-tr-igst"] span').textContent='IGST ('+gr+'%)'; } document.getElementById('qt-grand').textContent='₹'+(sub+ga).toFixed(2); } function qtToggleGST(){ const on=document.getElementById('qt-gst-toggle').checked; document.getElementById('qt-gst-slider').style.background=on?'#22c55e':'#d1d5db'; document.getElementById('qt-gst-label').textContent=on?'GST ON':'GST OFF'; document.getElementById('qt-gst-label').style.color=on?'#22c55e':'#6b7280'; document.getElementById('qt-gsttype-field').style.display=on?'':'none'; document.getElementById('qt-gstrate-field').style.display=on?'':'none'; qtCalc(); } async function qtAutofill(name){ if(name.length<2)return; const qts=await qtDbAll(); const m=qts.find(q=>q.customer_name&&q.customer_name.toLowerCase().startsWith(name.toLowerCase())); if(m){ if(!document.getElementById('qt-phone').value) document.getElementById('qt-phone').value=m.customer_phone||''; if(!document.getElementById('qt-addr').value) document.getElementById('qt-addr').value=m.customer_address||''; if(!document.getElementById('qt-gstin').value) document.getElementById('qt-gstin').value=m.customer_gstin||''; } } async function qtAutofillPhone(ph){ if(ph.length<10)return; const invs=await dbAll('invoices'); const m=invs.find(i=>i.customer_phone===ph); if(m){ if(!document.getElementById('qt-name').value) document.getElementById('qt-name').value=m.customer_name||''; if(!document.getElementById('qt-addr').value) document.getElementById('qt-addr').value=m.customer_address||''; if(!document.getElementById('qt-gstin').value) document.getElementById('qt-gstin').value=m.customer_gstin||''; } } function qtClear(){ document.getElementById('qt-name').value=''; document.getElementById('qt-phone').value=''; document.getElementById('qt-addr').value=''; document.getElementById('qt-gstin').value=''; document.getElementById('qt-notes').value=''; document.getElementById('qt-rows').innerHTML=''; qtAddRow(); qtCalc(); } async function saveQuotation(andPrint){ const cname=(document.getElementById('qt-name').value||'').trim(); if(!cname){toast('Customer name enter பண்ணுங்க','e');return;} const rows=document.querySelectorAll('#qt-rows .bir'); const items=[]; rows.forEach(r=>{ const ins=r.querySelectorAll('input'); const nm=ins[0].value.trim(),qty=parseFloat(ins[1].value)||0,up=parseFloat(ins[2].value)||0; if(nm&&qty>0)items.push({product_name:nm,qty,unit_price:up,total:qty*up,hsn:qtAllP.find(p=>p.name===nm)?.hsn||''}); }); if(!items.length){toast('Minimum oru item add பண்ணுங்க','e');return;} const gstOn=document.getElementById('qt-gst-toggle').checked; const gr=gstOn?(parseFloat(document.getElementById('qt-gst').value)||18):0; const gsttype=gstOn?document.getElementById('qt-gsttype').value:'none'; const sub=items.reduce((a,i)=>a+i.total,0); const ga=sub*gr/100; const grandTotal=sub+ga; const qt={ quotation_no:document.getElementById('qt-no').value, customer_name:cname, customer_phone:document.getElementById('qt-phone').value, customer_address:document.getElementById('qt-addr').value, customer_gstin:document.getElementById('qt-gstin').value, date:document.getElementById('qt-date').value, valid_until:document.getElementById('qt-valid').value, gst_rate:gr, gst_type:gsttype, gst_amt:ga, items, sub, grand_total:grandTotal, notes:document.getElementById('qt-notes').value, created_at:new Date().toISOString() }; const saved=await qtDbAdd(qt); qt.id=saved.id; // Save HTML to server try{ const qtHtml=buildQtHTML(qt); await fetch(API+'/api.php/api/save-pdf',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({invoice_no:qt.quotation_no,html:qtHtml})}); }catch(e){} toast('Quotation '+qt.quotation_no+' saved! ₹'+fmt(qt.grand_total)); if(andPrint)printQt(qt); qtClear(); document.getElementById('qt-no').value=await nextQtNo(); } function buildQtHTML(qt){ const gstOff=(qt.gst_type==='none'||qt.gst_rate===0||qt.gst_amt===0); const isCSGST=!gstOff&&qt.gst_type==='cgst_sgst'; const half=qt.gst_amt/2; const gr=qt.gst_rate||0; const halfRate=gr/2; const hsnMap={}; (qt.items||[]).forEach(it=>{const h=it.hsn||'';if(!hsnMap[h])hsnMap[h]={taxable:0};hsnMap[h].taxable+=it.total;}); const hsnRows=Object.entries(hsnMap).map(([hsn,d])=>{ const ta=d.taxable*gr/100;const half2=ta/2; return`${hsn||'-'}${fmt(d.taxable)}${isCSGST?`${halfRate}%${fmt(half2)}${halfRate}%${fmt(half2)}`:`${gr}%${fmt(ta)}`}${fmt(ta)}`; }).join(''); return`Quotation ${qt.quotation_no}

CHANDRAN COMPUTERS

Computer & CCTV | Natham, Dindigul-624401

Ph: 9786386262 | GSTIN: 33DMIPK7820K1ZT

QUOTATION

Quot No: ${qt.quotation_no}

Date: ${qt.date}

${qt.valid_until?`
Valid Until: ${qt.valid_until}
`:''}

Bill To

${qt.customer_name||''}

${qt.customer_address?`

${qt.customer_address}

`:''} ${qt.customer_phone?`

Ph: ${qt.customer_phone}

`:''} ${qt.customer_gstin?`

GSTIN: ${qt.customer_gstin}

`:''}
${(qt.items||[]).map((it,i)=>``).join('')}
#DescriptionHSNQtyUnit PriceAmount
${i+1}${it.product_name}${it.hsn||'-'}${it.qty}₹${fmt(it.unit_price)}₹${fmt(it.total)}
${!gstOff?`
${isCSGST?'':''}${hsnRows}
HSNTaxableCGST%CGSTSGST%SGSTIGST%IGSTTotal Tax
`:'
'}
Taxable₹${fmt(qt.sub)}
${!gstOff?(isCSGST?`
CGST (${halfRate}%)₹${fmt(half)}
SGST (${halfRate}%)₹${fmt(half)}
`:`
IGST (${gr}%)₹${fmt(qt.gst_amt)}
`):''}
GRAND TOTAL₹${fmt(qt.grand_total)}
${qt.notes?`
Terms / Notes: ${qt.notes}
`:''}
`; } function printQt(qt){ const w=window.open('','_blank'); const qtHtmlFull=buildQtHTML(qt); w.document.write(qtHtmlFull); w.document.close(); setTimeout(()=>w.print(),600); // Auto-save to server -> quotations_pdf/ try { fetch(API+'/api.php/api/save-quotation-pdf', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ quotation_no: qt.quotation_no, html: qtHtmlFull }) }).then(r=>r.json()).then(d=>{ if(d.ok) toast('Quotation saved to server! ('+d.file+')', 's'); }).catch(()=>{}); } catch(e) {} } async function renderQuotations(){ const qts=await qtDbAll(); const q=(document.getElementById('qt-srch').value||'').toLowerCase(); const filtered=qts.filter(x=>(x.customer_name||'').toLowerCase().includes(q)||(x.quotation_no||'').toLowerCase().includes(q)); filtered.sort((a,b)=>new Date(b.created_at)-new Date(a.created_at)); const tbody=document.getElementById('qt-tbody'); if(!filtered.length){tbody.innerHTML='Quotation எதுவும் இல்ல. புது Quotation create பண்ணுங்க!';return;} tbody.innerHTML=filtered.map(q=>` ${q.quotation_no} ${q.customer_name||'-'} ${q.customer_phone||'-'} ${q.date||'-'} ${q.valid_until||'-'} ₹${fmt(q.grand_total)} `).join(''); } function viewQt(qt){ const w=window.open('','_blank'); w.document.write(buildQtHTML(qt)); w.document.close(); } async function deleteQt(id){ if(!confirm('இந்த Quotation delete பண்ணணுமா?'))return; await qtDbDel(id); toast('Quotation deleted'); renderQuotations(); } async function convertQtToInvoice(qt){ if(!confirm('இந்த Quotation-ஐ Invoice-ஆ மாத்தணுமா?\n\nCustomer: '+qt.customer_name+'\nAmount: ₹'+fmt(qt.grand_total)))return; // Switch to billing page and pre-fill nav('billing'); await new Promise(r=>setTimeout(r,400)); // Fill customer document.getElementById('bc-name').value=qt.customer_name||''; document.getElementById('bc-phone').value=qt.customer_phone||''; document.getElementById('bc-addr').value=qt.customer_address||''; document.getElementById('bc-gstin').value=qt.customer_gstin||''; document.getElementById('bi-notes').value='Ref: '+qt.quotation_no+(qt.notes?' | '+qt.notes:''); // Fill GST if(qt.gst_rate===0||qt.gst_type==='none'){ const tog=document.getElementById('gst-toggle');if(tog)tog.checked=false;toggleGST&&toggleGST(); } else { const tog=document.getElementById('gst-toggle');if(tog)tog.checked=true; const gtsel=document.getElementById('bi-gsttype');if(gtsel)gtsel.value=qt.gst_type; const gri=document.getElementById('bi-gst');if(gri)gri.value=qt.gst_rate; } // Fill items document.getElementById('bill-rows').innerHTML=''; for(const it of (qt.items||[])){ addRow(); const rows=document.querySelectorAll('#bill-rows .bir'); const last=rows[rows.length-1]; const ins=last.querySelectorAll('input'); ins[0].value=it.product_name; ins[1].value=it.qty; ins[2].value=it.unit_price; } calcT&&calcT(); toast('Quotation '+qt.quotation_no+' → Invoice-ஆ மாத்தப்பட்டது! Check பண்ணி Save பண்ணுங்க 👆'); } // ═══════════════════════════════════════ // ═══════════════════════════════════════ // SERVICES MODULE // ═══════════════════════════════════════ const SVC_STORE = 'services'; let svcCatFilter = 'all'; // ── Services: Server API (chandran_db.json ல save ஆகும்) ── async function svcDbAll() { try { const r = await fetch(API + '/api/services'); return await r.json(); } catch(e) { return []; } } async function svcDbPut(item) { try { if (item._isNew) { delete item._isNew; const r = await fetch(API + '/api/services', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item) }); const saved = await r.json(); // Update local item id and job_no with server-assigned values item.id = saved.id; item.job_no = saved.job_no || saved.id; } else { await fetch(API + '/api/services/' + encodeURIComponent(item.id), { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item) }); } } catch(e) { console.error('svcDbPut error', e); } } async function svcDbDelete(id) { try { await fetch(API + '/api/services/' + encodeURIComponent(id), { method: 'DELETE' }); } catch(e) {} } async function svcGetAll() { return await svcDbAll(); } function svcNextId() { // Server assigns the real ID — this is just a temp placeholder return '__new__' + Date.now(); } function filterServices(cat, btn) { svcCatFilter = cat; document.querySelectorAll('#page-services .cp').forEach(b => b.classList.remove('active')); if (btn) btn.classList.add('active'); else { const id = 'svc-cat-' + (cat === 'all' ? 'all' : cat); const el = document.getElementById(id); if (el) el.classList.add('active'); } renderServices(); } async function renderServices() { const all = await svcGetAll(); const q = (document.getElementById('svc-srch').value || '').toLowerCase(); let list = all; if (svcCatFilter !== 'all') list = list.filter(s => s.category === svcCatFilter); if (q) list = list.filter(s => (s.customer_name||'').toLowerCase().includes(q) || (s.customer_phone||'').includes(q) || (s.device||'').toLowerCase().includes(q) || (s.complaint||'').toLowerCase().includes(q) ); list.sort((a, b) => { // Sort by date desc, fallback to id string if (a.date && b.date && a.date !== b.date) return b.date.localeCompare(a.date); return (b.id||'').localeCompare(a.id||''); }); // Stats const done = all.filter(s => s.status === 'completed').length; const prog = all.filter(s => s.status === 'in_progress').length; const pend = all.filter(s => s.status === 'pending').length; const rev = all.filter(s => s.status === 'completed').reduce((a, s) => a + (s.amount||0), 0); document.getElementById('svc-stat-done').textContent = done; document.getElementById('svc-stat-prog').textContent = prog; document.getElementById('svc-stat-pend').textContent = pend; document.getElementById('svc-stat-rev').textContent = '₹' + fmt(rev); const statusBadge = s => { if (s.status === 'completed') return '✓ Completed'; if (s.status === 'in_progress') return '⟳ In Progress'; if (s.status === 'delivered') return '📦 Delivered'; return '⏳ Pending'; }; const catIcon = c => c === 'laptop' ? '💻' : c === 'cctv' ? '📷' : c === 'Desktoop' ? '' : '🔧'; document.getElementById('svc-tbody').innerHTML = list.length ? list.map(s => ` ${s.job_no} ${s.customer_name||'Walk-in'} ${s.customer_phone||'-'} ${catIcon(s.category)} ${s.device||'-'} ${s.complaint||'-'} ${statusBadge(s)} ₹${fmt(s.amount||0)} ${s.date||''} `).join('') : 'No service entries found'; } function openAddService(svcId) { // Load from server asynchronously svcGetAll().then(all => { const existing = svcId ? all.find(s => s.id === svcId) : null; const title = existing ? 'Edit Service Entry' : 'New Service Entry'; const todayDate = new Date().toISOString().split('T')[0]; document.getElementById('mc2').innerHTML = `

🔧 ${title}

`; document.getElementById('mov').classList.add('open'); }); // end svcGetAll().then } async function saveService(svcId) { const name = document.getElementById('sv-name').value.trim(); const complaint = document.getElementById('sv-complaint').value.trim(); if (!name) { toast('Customer name is required', 'e'); return; } if (!complaint) { toast('Complaint description is required', 'e'); return; } const all = await svcGetAll(); let item; if (svcId) { item = all.find(s => s.id === svcId); if (!item) { toast('Service not found', 'e'); return; } } else { item = { id: '', job_no: '', _isNew: true }; } item.customer_name = name; item.customer_phone = document.getElementById('sv-phone').value.trim(); item.category = document.getElementById('sv-cat').value; item.device = document.getElementById('sv-device').value.trim(); item.complaint = complaint; item.remarks = document.getElementById('sv-remarks').value.trim(); item.status = document.getElementById('sv-status').value; item.amount = parseFloat(document.getElementById('sv-amount').value) || 0; item.date = document.getElementById('sv-date').value; item.payment_mode = document.getElementById('sv-pay').value; item.serial = document.getElementById('sv-serial').value.trim(); await svcDbPut(item); // After server save, item.id and job_no are set from server response closeM(); renderServices(); toast('Service entry saved!', 's'); } async function editService(svcId) { openAddService(svcId); } async function deleteService(svcId) { if (!confirm('Delete this service entry?')) return; await svcDbDelete(svcId); renderServices(); toast('Deleted', 's'); } async function svcBillNow(svcId) { const all = await svcGetAll(); const s = all.find(x => x.id === svcId); if (!s) return; // Navigate to billing and pre-fill nav('billing'); await new Promise(r => setTimeout(r, 100)); document.getElementById('bc-name').value = s.customer_name || ''; document.getElementById('bc-phone').value = s.customer_phone || ''; // Add the service as a bill row addRow(); await new Promise(r => setTimeout(r, 80)); const rows = document.getElementById('bill-rows').querySelectorAll('input[type="text"], input:not([type])'); if (rows.length > 0) { // Find the description input in the last row const lastRowInputs = document.getElementById('bill-rows').lastElementChild.querySelectorAll('input'); if (lastRowInputs[0]) { const catLabel = s.category === 'laptop' ? 'Laptop Service' : s.category === 'cctv' ? 'CCTV Service' : s.category === 'roof' ? 'Roof Service' : 'Service'; lastRowInputs[0].value = catLabel + (s.device ? ' - ' + s.device : '') + (s.complaint ? ' (' + s.complaint + ')' : ''); } if (lastRowInputs[1]) lastRowInputs[1].value = '1'; // qty if (lastRowInputs[2]) lastRowInputs[2].value = s.amount || ''; // price } calcT(); toast('Service details copied to bill!', 's'); } // ═══════════════════════════════════════ async function printServiceBill(svcId) { const all = await svcGetAll(); const s = all.find(x => x.id === svcId); if (!s) { toast('Service not found', 'e'); return; } const catLabel = s.category === 'laptop' ? '💻 Laptop Service' : s.category === 'cctv' ? '📷 CCTV Service' : s.category === 'roof' ? '🏠 Roof Service' : '🔧 Service'; const statusLabel = s.status === 'completed' ? 'Completed' : s.status === 'in_progress' ? 'In Progress' : s.status === 'delivered' ? 'Delivered' : 'Pending'; const amtWords = numberToWords(Math.round(s.amount || 0)); const html = `
CHANDRAN COMPUTERS
Computer Sales, Service & CCTV Solutions
Natham, Dindigul - 624401 | Ph: 9786386262
GSTIN: 33DMIPK7820K1ZT
SERVICE BILL
Job No: ${s.job_no}
Date: ${s.date || ''}
Customer Details
${s.customer_name || 'Walk-in'}
${s.customer_phone ? '
📱 ' + s.customer_phone + '
' : ''} ${s.customer_address ? '
' + s.customer_address + '
' : ''}
Device Information
${catLabel}
${s.device ? '
📦 ' + s.device + '
' : ''} ${s.serial ? '
S/N: ' + s.serial + '
' : ''}
Complaint / Issue Reported
${s.complaint || '-'}
Work Done / Remarks
${s.remarks || '-'}
Description of Service
Qty
Amount (₹)
${catLabel.replace(/💻|📷|🔧/g,'').trim()}${s.device ? ' — ' + s.device : ''}${s.complaint ? '
' + s.complaint + '
' : ''}
1
₹${(s.amount||0).toFixed(2)}
Amount in Words
${amtWords} Rupees Only
Service Charge₹${(s.amount||0).toFixed(2)}
TOTAL₹${(s.amount||0).toFixed(2)}
Payment: ${s.payment_mode || 'CASH'}
Service Status
${statusLabel}
Authorised Signature
Chandran Computers
Thank you for choosing Chandran Computers!  |  This is a computer generated service bill.
`; // Open print window const w = window.open('', '_blank', 'width=750,height=900'); w.document.write(`Service Bill - ${s.job_no}
${html} `); w.document.close(); // Auto-save to server -> services_pdf/ try { const fullSvcHtml = 'Service Bill - ' + s.job_no + '' + html + ''; fetch(API+'/api.php/api/save-service-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_no: s.job_no, html: fullSvcHtml }) }).then(r => r.json()).then(d => { if (d.ok) toast('Service bill saved to server! (' + d.file + ')', 's'); }).catch(() => {}); } catch(e) {} } // Helper: number to words (simple Indian format) function numberToWords(n) { if (n === 0) return 'Zero'; const ones = ['','One','Two','Three','Four','Five','Six','Seven','Eight','Nine','Ten','Eleven','Twelve','Thirteen','Fourteen','Fifteen','Sixteen','Seventeen','Eighteen','Nineteen']; const tens = ['','','Twenty','Thirty','Forty','Fifty','Sixty','Seventy','Eighty','Ninety']; function tw(n){ if(n<20)return ones[n]; return tens[Math.floor(n/10)]+(n%10?' '+ones[n%10]:''); } function convert(n){ if(n===0)return ''; if(n<100)return tw(n); if(n<1000)return ones[Math.floor(n/100)]+' Hundred'+(n%100?' '+tw(n%100):''); if(n<100000)return convert(Math.floor(n/1000))+' Thousand'+(n%1000?' '+convert(n%1000):''); if(n<10000000)return convert(Math.floor(n/100000))+' Lakh'+(n%100000?' '+convert(n%100000):''); return convert(Math.floor(n/10000000))+' Crore'+(n%10000000?' '+convert(n%10000000):''); } return convert(n).trim(); } // ═══ INIT ═══ async function init(){ try{await openDB();await seed();const t=tod(),fm=tmon()+'-01';document.getElementById('inv-from').value=fm;document.getElementById('inv-to').value=t;updStat();loadDash();} catch(e){document.body.innerHTML='

Error: '+e.message+'

Please use Chrome or Edge browser. Open the HTML file by double-clicking it.

';} } init();