Spaces:
Running
Running
| import XLSX from 'xlsx-js-style'; | |
| import { Transaction, Party, BillType } from '../types'; | |
| export interface AnalysisExportRow { | |
| lotNumber: string; | |
| mirchiName: string; | |
| purchaseQty: number; | |
| purchaseValue: number; | |
| avgPurchaseRate: number; | |
| soldQty: number; | |
| salesValue: number; | |
| avgSaleRate: number; | |
| realizedProfit: number; | |
| marginPercent: number; | |
| remainingQty: number; | |
| remainingValueAtCost: number; | |
| } | |
| export interface LotOverviewExportRow { | |
| lotNumber: string; | |
| mirchiName: string; | |
| purchaseQty: number; | |
| purchaseValue: number; | |
| avgPurchaseRate: number; | |
| soldQty: number; | |
| salesValue: number; | |
| avgSaleRate: number; | |
| realizedProfit: number; | |
| marginPercent: number; | |
| remainingQty: number; | |
| remainingValueAtCost: number; | |
| } | |
| export interface LotDetailExportRow { | |
| date: string; | |
| billNo: string; | |
| typeLabel: string; | |
| directionLabel: string; | |
| qtyKg: number; | |
| ratePerKg: number; | |
| grossAmount: number; | |
| expensesAllocated: number; | |
| netAmount: number; | |
| profitImpact: number; | |
| } | |
| // ========================================== | |
| // 2. STYLING CONFIGURATION | |
| // ========================================== | |
| // Common Colors | |
| const CLR_BORDER = { rgb: "D1D5DB" }; // Gray-300 | |
| const CLR_TEXT = { rgb: "374151" }; // Gray-700 | |
| const CLR_HEAD_BG = { rgb: "F3F4F6" }; // Gray-100 | |
| const CLR_GREEN = { rgb: "059669" }; | |
| const CLR_RED = { rgb: "DC2626" }; | |
| const CLR_TEAL = { rgb: "0D9488" }; | |
| const CLR_WHITE = { rgb: "FFFFFF" }; | |
| // Border Definitions | |
| const BORDER_ALL = { | |
| top: { style: 'thin', color: CLR_BORDER }, | |
| bottom: { style: 'thin', color: CLR_BORDER }, | |
| left: { style: 'thin', color: CLR_BORDER }, | |
| right: { style: 'thin', color: CLR_BORDER } | |
| }; | |
| const BORDER_TOP_SIDE = { | |
| top: { style: 'thin', color: { rgb: "9CA3AF" } }, // Darker Gray for Card | |
| left: { style: 'thin', color: { rgb: "9CA3AF" } }, | |
| right: { style: 'thin', color: { rgb: "9CA3AF" } } | |
| }; | |
| const BORDER_BOT_SIDE = { | |
| bottom: { style: 'thin', color: { rgb: "9CA3AF" } }, | |
| left: { style: 'thin', color: { rgb: "9CA3AF" } }, | |
| right: { style: 'thin', color: { rgb: "9CA3AF" } } | |
| }; | |
| // Style Objects | |
| const STYLES = { | |
| TITLE: { | |
| font: { bold: true, sz: 14, color: { rgb: "111827" } }, | |
| alignment: { horizontal: 'left', vertical: 'center' } | |
| }, | |
| // Card Styles | |
| CARD_HEADER: { | |
| border: BORDER_TOP_SIDE, | |
| fill: { fgColor: CLR_WHITE }, | |
| font: { sz: 10, color: { rgb: "6B7280" } }, | |
| alignment: { vertical: 'bottom', horizontal: 'left', indent: 1 } | |
| }, | |
| CARD_VALUE: { | |
| border: BORDER_BOT_SIDE, | |
| fill: { fgColor: CLR_WHITE }, | |
| font: { bold: true, sz: 11, color: { rgb: "1F2937" } }, | |
| alignment: { vertical: 'top', horizontal: 'left', indent: 1 } | |
| }, | |
| // Table Styles | |
| TABLE_HEAD: { | |
| fill: { fgColor: CLR_HEAD_BG }, | |
| font: { bold: true, color: CLR_TEXT }, | |
| border: BORDER_ALL, | |
| alignment: { horizontal: 'center', vertical: 'center' } | |
| }, | |
| CELL_TEXT: { | |
| border: BORDER_ALL, | |
| alignment: { vertical: 'center', horizontal: 'left' }, | |
| font: { color: CLR_TEXT } | |
| }, | |
| CELL_NUM: { | |
| border: BORDER_ALL, | |
| alignment: { vertical: 'center', horizontal: 'right' }, | |
| font: { color: { rgb: "111827" } } | |
| }, | |
| CELL_MONO: { | |
| border: BORDER_ALL, | |
| alignment: { vertical: 'center', horizontal: 'left' }, | |
| font: { name: 'Courier New', sz: 10, color: CLR_TEXT } | |
| }, | |
| // Text Colors | |
| TXT_TEAL: { font: { bold: true, sz: 12, color: CLR_TEAL } }, | |
| TXT_GRAY: { font: { color: { rgb: "6B7280" } } }, | |
| TXT_GREEN: { font: { bold: true, color: CLR_GREEN } }, | |
| TXT_RED: { font: { bold: true, color: CLR_RED } } | |
| }; | |
| // Formats | |
| const FMT_CURRENCY = "₹#,##0.00"; // 2 decimal places for clean alignment | |
| const FMT_QTY = "0.00 \"kg\""; | |
| // ========================================== | |
| // 3. HELPER FUNCTIONS | |
| // ========================================== | |
| // Helper to create cell. | |
| // Uses empty string "" if value is null to avoid "null" text in Excel. | |
| const c = (val: any, s: any = {}, fmt?: string) => { | |
| return { | |
| v: val === null || val === undefined ? "" : val, | |
| s: { ...s, numFmt: fmt }, // Inject format into style for xlsx-js-style | |
| z: fmt // Inject format into standard key for compatibility | |
| }; | |
| }; | |
| const formatIndCurrency = (val: number) => { | |
| return val.toLocaleString('en-IN', { | |
| maximumFractionDigits: 0, // Keep summary cards clean (no decimals) | |
| style: 'currency', | |
| currency: 'INR' | |
| }).replace('₹', '₹'); // Ensure symbol consistency | |
| }; | |
| // ========================================== | |
| // 4. EXPORT FUNCTIONS | |
| // ========================================== | |
| export const exportLotDetailAnalysis = ( | |
| lot: LotOverviewExportRow, | |
| rows: LotDetailExportRow[], | |
| ) => { | |
| const data: any[][] = []; | |
| // --- ROW 1: Main Title --- | |
| data.push([ | |
| c(`${lot.lotNumber} - ${lot.mirchiName} Analysis`, STYLES.TITLE), | |
| c(""), c(""), c(""), c(""), c(""), c(""), c("") | |
| ]); | |
| // --- ROW 2: Spacer --- | |
| data.push([c("")]); | |
| // --- ROW 3: Lot Info (Left) & Remaining (Right) --- | |
| // Note: We used c("") for spacers to ensure no "null" text | |
| data.push([ | |
| c(lot.lotNumber, { ...STYLES.TXT_TEAL, alignment: { vertical: 'bottom' } }), | |
| c(""), c(""), c(""), c(""), c(""), | |
| c("Remaining", { ...STYLES.TXT_GRAY, alignment: { horizontal: 'right', vertical: 'bottom' } }), | |
| c(Number(lot.remainingQty.toFixed(2)), { font: { bold: true }, alignment: { horizontal: 'right', vertical: 'bottom' } }, FMT_QTY) | |
| ]); | |
| // --- ROW 4: Mirchi Name --- | |
| data.push([ | |
| c(lot.mirchiName, STYLES.TXT_GRAY), | |
| c(""), c(""), c(""), c(""), c(""), c(""), c("") | |
| ]); | |
| // --- ROW 5: Spacer --- | |
| data.push([c("")]); | |
| // --- ROW 6: CARD HEADERS --- | |
| data.push([ | |
| c("Purchase", STYLES.CARD_HEADER), c(""), c(""), c(""), | |
| c("Sales", STYLES.CARD_HEADER), c(""), c(""), c("") | |
| ]); | |
| // --- ROW 7: CARD VALUES --- | |
| // Format text manually for the card to ensure it looks perfect | |
| const buyTxt = `${lot.purchaseQty.toFixed(2)} kg • ${formatIndCurrency(lot.purchaseValue)}`; | |
| const sellTxt = `${lot.soldQty.toFixed(2)} kg • ${formatIndCurrency(lot.salesValue)}`; | |
| data.push([ | |
| c(buyTxt, STYLES.CARD_VALUE), c(""), c(""), c(""), | |
| c(sellTxt, STYLES.CARD_VALUE), c(""), c(""), c("") | |
| ]); | |
| // --- ROW 8: Spacer --- | |
| data.push([c("")]); | |
| // --- ROW 9: Avg & Profit Summary --- | |
| const avgText = `Avg Buy ₹${lot.avgPurchaseRate.toFixed(0)} /kg • Avg Sell ₹${lot.avgSaleRate.toFixed(0)} /kg`; | |
| // Profit Logic | |
| const pVal = Number(lot.realizedProfit.toFixed(2)); // Round to avoid .774 | |
| const pLabel = pVal > 0 ? "Profit" : pVal < 0 ? "Loss" : "Break-even"; | |
| const pStyle = pVal > 0 ? STYLES.TXT_GREEN : pVal < 0 ? STYLES.TXT_RED : STYLES.TXT_GRAY; | |
| data.push([ | |
| c(avgText, { font: { color: { rgb: "4B5563" }, italic: true }, alignment: { vertical: 'center' } }), | |
| c(""), c(""), c(""), c(""), c(""), | |
| c(pLabel, { ...STYLES.TXT_GRAY, alignment: { horizontal: 'right', vertical: 'center' } }), | |
| c(pVal, { ...pStyle, alignment: { horizontal: 'right', vertical: 'center' } }, FMT_CURRENCY) | |
| ]); | |
| // --- ROW 10: Spacer --- | |
| data.push([c("")]); | |
| // --- ROW 11: TABLE HEADERS --- | |
| data.push([ | |
| c('Date', STYLES.TABLE_HEAD), | |
| c('Bill No', STYLES.TABLE_HEAD), | |
| c('Type', STYLES.TABLE_HEAD), | |
| c('Import / Export', STYLES.TABLE_HEAD), | |
| c('Qty (kg)', STYLES.TABLE_HEAD), | |
| c('Rate', STYLES.TABLE_HEAD), | |
| c('Net Amount', STYLES.TABLE_HEAD), | |
| c('Profit Impact', STYLES.TABLE_HEAD) | |
| ]); | |
| // --- ROW 12+: DATA --- | |
| if (rows.length === 0) { | |
| data.push([c('No transactions found', STYLES.CELL_TEXT)]); | |
| } else { | |
| rows.forEach(row => { | |
| // Round numbers to 2 decimals to prevent floating point garbage | |
| const safeProfit = Number(row.profitImpact.toFixed(2)); | |
| const safeNet = Number(row.netAmount.toFixed(2)); | |
| const safeQty = Number(row.qtyKg.toFixed(2)); | |
| const safeRate = Number(row.ratePerKg.toFixed(2)); | |
| const impactStyle = safeProfit > 0 | |
| ? { ...STYLES.CELL_NUM, font: { color: CLR_GREEN } } | |
| : safeProfit < 0 | |
| ? { ...STYLES.CELL_NUM, font: { color: CLR_RED } } | |
| : STYLES.CELL_NUM; | |
| data.push([ | |
| c(row.date, STYLES.CELL_TEXT), | |
| c(row.billNo, STYLES.CELL_MONO), | |
| c(row.typeLabel, STYLES.CELL_TEXT), | |
| c(row.directionLabel, STYLES.CELL_TEXT), | |
| c(safeQty, STYLES.CELL_NUM, "0.00"), | |
| c(safeRate, STYLES.CELL_NUM, "0.00"), | |
| c(safeNet, STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(safeProfit, impactStyle, FMT_CURRENCY) | |
| ]); | |
| }); | |
| } | |
| // --- GENERATE --- | |
| const ws = XLSX.utils.aoa_to_sheet(data); | |
| // --- MERGES (0-based indices) --- | |
| ws['!merges'] = [ | |
| { s: { r: 0, c: 0 }, e: { r: 0, c: 7 } }, // Title | |
| { s: { r: 2, c: 0 }, e: { r: 2, c: 5 } }, // Lot Number | |
| { s: { r: 3, c: 0 }, e: { r: 3, c: 5 } }, // Mirchi Name | |
| // Purchase Card | |
| { s: { r: 5, c: 0 }, e: { r: 5, c: 3 } }, | |
| { s: { r: 6, c: 0 }, e: { r: 6, c: 3 } }, | |
| // Sales Card | |
| { s: { r: 5, c: 4 }, e: { r: 5, c: 7 } }, | |
| { s: { r: 6, c: 4 }, e: { r: 6, c: 7 } }, | |
| // Avg Line | |
| { s: { r: 8, c: 0 }, e: { r: 8, c: 5 } }, | |
| ]; | |
| // --- COL WIDTHS --- | |
| ws['!cols'] = [ | |
| { wch: 14 }, // Date | |
| { wch: 22 }, // Bill No | |
| { wch: 12 }, // Type | |
| { wch: 14 }, // Imp/Exp | |
| { wch: 12 }, // Qty | |
| { wch: 10 }, // Rate | |
| { wch: 16 }, // Net Amt | |
| { wch: 16 }, // Profit | |
| ]; | |
| const wb = XLSX.utils.book_new(); | |
| const safeName = lot.lotNumber.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 30); | |
| XLSX.utils.book_append_sheet(wb, ws, safeName); | |
| XLSX.writeFile(wb, `Lot_${safeName}_Analysis.xlsx`); | |
| }; | |
| // 5. OTHER EXPORT FUNCTIONS (Parties/Ledger) | |
| // ========================================== | |
| export const exportPartyLedger = (party: Party, transactions: Transaction[], dateRange?: { from: string; to: string }) => { | |
| const parseDate = (value?: string): Date | undefined => { | |
| if (!value) return undefined; | |
| const trimmed = String(value).trim(); | |
| if (!trimmed) return undefined; | |
| const iso = trimmed.match(/^\d{4}-\d{2}-\d{2}(?:[T\s].*)?$/); | |
| if (iso) { | |
| const d = new Date(trimmed); | |
| return isNaN(d.getTime()) ? undefined : d; | |
| } | |
| const slash = trimmed.match(/^\d{1,2}\/\d{1,2}\/(\d{2}|\d{4})$/); | |
| if (slash) { | |
| const [aStr, bStr, yyStr] = trimmed.split('/'); | |
| const a = Number(aStr); | |
| const b = Number(bStr); | |
| const yy = Number(yyStr); | |
| const year = yyStr.length === 2 ? 2000 + yy : yy; | |
| const day = a > 12 ? a : b > 12 ? b : a; | |
| const month = a > 12 ? b : b > 12 ? a : b; | |
| const d = new Date(year, month - 1, day); | |
| return isNaN(d.getTime()) ? undefined : d; | |
| } | |
| const d = new Date(trimmed); | |
| return isNaN(d.getTime()) ? undefined : d; | |
| }; | |
| const extractDateFromText = (text?: string): Date | undefined => { | |
| if (!text) return undefined; | |
| const iso = text.match(/\b\d{4}-\d{2}-\d{2}\b/); | |
| if (iso?.[0]) return parseDate(iso[0]); | |
| const dmy = text.match(/\b\d{1,2}\/\d{1,2}\/\d{4}\b/); | |
| if (dmy?.[0]) return parseDate(dmy[0]); | |
| return undefined; | |
| }; | |
| const isAwaakTx = (tx: Transaction) => { | |
| const typeStr = String(tx.bill_type).toUpperCase(); | |
| const numStr = tx.bill_number ? tx.bill_number.toUpperCase() : ""; | |
| const isJawaakFromNumber = numStr.startsWith('JAWAAK'); | |
| const isAwaakFromNumber = numStr.startsWith('AWAAK'); | |
| return isAwaakFromNumber ? true : isJawaakFromNumber ? false : typeStr === 'AWAAK'; | |
| }; | |
| const inRange = (d: Date) => { | |
| if (!dateRange) return true; | |
| const from = new Date(dateRange.from); | |
| const to = new Date(dateRange.to); | |
| return d >= from && d <= to; | |
| }; | |
| type LedgerEvent = | |
| | { kind: 'bill'; date: Date; tx: Transaction } | |
| | { kind: 'payment'; date: Date; tx: Transaction; payment: any }; | |
| const events: LedgerEvent[] = []; | |
| transactions.forEach(tx => { | |
| const billDate = parseDate(tx.bill_date) || new Date(tx.bill_date); | |
| if (!inRange(billDate)) return; | |
| events.push({ kind: 'bill', date: billDate, tx }); | |
| const payments = tx.payments || []; | |
| const validPayments = payments.filter((p: any) => String(p.mode).toLowerCase() !== 'due'); | |
| validPayments.forEach((p: any) => { | |
| const paymentDate = | |
| parseDate(p.payment_date) || | |
| parseDate(p.created_at) || | |
| parseDate(p.date) || | |
| extractDateFromText(p.reference) || | |
| billDate; | |
| const safePaymentDate = paymentDate < billDate ? billDate : paymentDate; | |
| if (!inRange(safePaymentDate)) return; | |
| events.push({ kind: 'payment', date: safePaymentDate, tx, payment: p }); | |
| }); | |
| }); | |
| events.sort((a, b) => { | |
| const ta = a.date.getTime(); | |
| const tb = b.date.getTime(); | |
| if (ta !== tb) return ta - tb; | |
| if (a.kind !== b.kind) return a.kind === 'bill' ? -1 : 1; | |
| return (a.tx.bill_number || '').localeCompare(b.tx.bill_number || ''); | |
| }); | |
| const data: any[][] = []; | |
| data.push([c(`Ledger: ${party.name}`, STYLES.TITLE)]); | |
| data.push([c("")]); | |
| data.push(['Date', 'Particulars', 'Credit', 'Debit', 'Balance', 'Dr/Cr'].map(h => c(h, STYLES.TABLE_HEAD))); | |
| let running = 0, tCr = 0, tDr = 0; | |
| events.forEach(ev => { | |
| const tx = ev.tx; | |
| const isAwaak = isAwaakTx(tx); | |
| let cr = 0, dr = 0; | |
| if (ev.kind === 'bill') { | |
| if (isAwaak) { | |
| if (tx.is_return) dr = tx.total_amount; | |
| else cr = tx.total_amount; | |
| } else { | |
| if (tx.is_return) cr = tx.total_amount; | |
| else dr = tx.total_amount; | |
| } | |
| const desc = `${tx.bill_number}` + (tx.is_return ? ' (RETURN)' : '') + (tx.items.length ? ` - ${tx.items[0].mirchi_name}` : ""); | |
| tCr += cr; tDr += dr; running += (dr - cr); | |
| data.push([ | |
| c(ev.date.toLocaleDateString('en-IN'), STYLES.CELL_TEXT), | |
| c(desc, STYLES.CELL_TEXT), | |
| c(cr || "", STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(dr || "", STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(Math.abs(running), STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(running >= 0 ? 'Dr' : 'Cr', STYLES.CELL_TEXT) | |
| ]); | |
| return; | |
| } | |
| const p = ev.payment; | |
| const amount = Number(p.amount) || 0; | |
| if (isAwaak) { | |
| if (tx.is_return) cr = amount; | |
| else dr = amount; | |
| } else { | |
| if (tx.is_return) dr = amount; | |
| else cr = amount; | |
| } | |
| const modeStr = String(p.mode || '').charAt(0).toUpperCase() + String(p.mode || '').slice(1); | |
| let desc = ` [${modeStr}]`; | |
| if (p.reference) desc += ` ${p.reference}`; | |
| tCr += cr; tDr += dr; running += (dr - cr); | |
| data.push([ | |
| c(ev.date.toLocaleDateString('en-IN'), STYLES.CELL_TEXT), | |
| c(desc, STYLES.CELL_TEXT), | |
| c(cr || "", STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(dr || "", STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(Math.abs(running), STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(running >= 0 ? 'Dr' : 'Cr', STYLES.CELL_TEXT) | |
| ]); | |
| }); | |
| const totStyle = { ...STYLES.CELL_NUM, font: { bold: true } }; | |
| data.push([ | |
| c('TOTAL', { ...STYLES.CELL_TEXT, font: { bold: true } }), c(""), | |
| c(tCr, totStyle, FMT_CURRENCY), c(tDr, totStyle, FMT_CURRENCY), | |
| c(Math.abs(running), totStyle, FMT_CURRENCY), | |
| c(running > 0 ? 'Dr' : 'Cr', { ...STYLES.CELL_TEXT, font: { bold: true } }) | |
| ]); | |
| const ws = XLSX.utils.aoa_to_sheet(data); | |
| ws['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 5 } }]; | |
| ws['!cols'] = [{ wch: 12 }, { wch: 40 }, { wch: 15 }, { wch: 15 }, { wch: 15 }, { wch: 8 }]; | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, 'Ledger'); | |
| XLSX.writeFile(wb, `${party.name.replace(/\s/g,'_')}_Ledger.xlsx`); | |
| }; | |
| export const exportAllParties = (parties: Party[]) => { | |
| const data: any[][] = []; | |
| data.push(['Party Name', 'Phone', 'City', 'Type', 'Balance', 'Status'].map(h => c(h, STYLES.TABLE_HEAD))); | |
| let tRec = 0, tPay = 0; | |
| parties.forEach(p => { | |
| if(p.current_balance > 0) tRec += p.current_balance; | |
| if(p.current_balance < 0) tPay += Math.abs(p.current_balance); | |
| const color = p.current_balance > 0 ? CLR_GREEN : p.current_balance < 0 ? CLR_RED : undefined; | |
| data.push([ | |
| c(p.name, STYLES.CELL_TEXT), c(p.phone||'-', STYLES.CELL_TEXT), c(p.city||'-', STYLES.CELL_TEXT), | |
| c(p.party_type, STYLES.CELL_TEXT), | |
| c(p.current_balance, { ...STYLES.CELL_NUM, font: { color } }, FMT_CURRENCY), | |
| c(p.current_balance > 0 ? 'Rec' : p.current_balance < 0 ? 'Pay' : '-', STYLES.CELL_TEXT) | |
| ]); | |
| }); | |
| data.push([c("")]); data.push([c('SUMMARY', { font: { bold: true } })]); | |
| data.push([c('Total Receivable'), c(""), c(""), c(""), c(tRec, STYLES.CELL_NUM, FMT_CURRENCY)]); | |
| data.push([c('Total Payable'), c(""), c(""), c(""), c(tPay, STYLES.CELL_NUM, FMT_CURRENCY)]); | |
| const ws = XLSX.utils.aoa_to_sheet(data); | |
| ws['!cols'] = [{ wch: 30 }, { wch: 15 }, { wch: 20 }, { wch: 10 }, { wch: 18 }, { wch: 10 }]; | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, 'Parties'); | |
| XLSX.writeFile(wb, `All_Parties.xlsx`); | |
| }; | |
| export const exportAllTransactions = (transactions: Transaction[]) => { | |
| const data: any[][] = []; | |
| data.push(['Date', 'Bill No', 'Party', 'Type', 'Amount', 'Paid', 'Balance'].map(h => c(h, STYLES.TABLE_HEAD))); | |
| transactions.forEach(tx => { | |
| data.push([ | |
| c(new Date(tx.bill_date).toLocaleDateString('en-IN'), STYLES.CELL_TEXT), | |
| c(tx.bill_number, STYLES.CELL_MONO), c(tx.party_name, STYLES.CELL_TEXT), c(tx.bill_type, STYLES.CELL_TEXT), | |
| c(tx.total_amount, STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(tx.paid_amount, STYLES.CELL_NUM, FMT_CURRENCY), | |
| c(tx.balance_amount, STYLES.CELL_NUM, FMT_CURRENCY) | |
| ]); | |
| }); | |
| const ws = XLSX.utils.aoa_to_sheet(data); | |
| ws['!cols'] = [{ wch: 12 }, { wch: 20 }, { wch: 25 }, { wch: 10 }, { wch: 15 }, { wch: 15 }, { wch: 15 }]; | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, 'Transactions'); | |
| XLSX.writeFile(wb, `All_Transactions.xlsx`); | |
| }; | |
| export const exportAnalysisReport = (rows: AnalysisExportRow[]) => { | |
| if (!rows.length) return; | |
| const data: any[][] = []; | |
| const headers = ['LOT','Mirchi','Buy Qty','Buy Val','Avg Buy','Sell Qty','Sell Val','Avg Sell','Rem Qty','Rem Val','Profit','Margin %']; | |
| data.push(headers.map(h => c(h, STYLES.TABLE_HEAD))); | |
| rows.forEach(r => { | |
| const pStyle = r.realizedProfit > 0 ? { ...STYLES.CELL_NUM, font: { color: CLR_GREEN } } : r.realizedProfit < 0 ? { ...STYLES.CELL_NUM, font: { color: CLR_RED } } : STYLES.CELL_NUM; | |
| data.push([ | |
| c(r.lotNumber, STYLES.CELL_TEXT), c(r.mirchiName, STYLES.CELL_TEXT), | |
| c(r.purchaseQty, STYLES.CELL_NUM,"0.00"), c(r.purchaseValue, STYLES.CELL_NUM,FMT_CURRENCY), c(r.avgPurchaseRate, STYLES.CELL_NUM,FMT_CURRENCY), | |
| c(r.soldQty, STYLES.CELL_NUM,"0.00"), c(r.salesValue, STYLES.CELL_NUM,FMT_CURRENCY), c(r.avgSaleRate, STYLES.CELL_NUM,FMT_CURRENCY), | |
| c(r.remainingQty, STYLES.CELL_NUM,"0.00"), c(r.remainingValueAtCost, STYLES.CELL_NUM,FMT_CURRENCY), | |
| c(r.realizedProfit, pStyle,FMT_CURRENCY), c(r.marginPercent/100, STYLES.CELL_NUM,"0.00%") | |
| ]); | |
| }); | |
| const ws = XLSX.utils.aoa_to_sheet(data); | |
| ws['!cols'] = headers.map(() => ({ wch: 14 })); | |
| const wb = XLSX.utils.book_new(); | |
| XLSX.utils.book_append_sheet(wb, ws, 'Summary'); | |
| XLSX.writeFile(wb, `Analysis_Summary.xlsx`); | |
| }; |