Antaram commited on
Commit
6a6bf32
·
verified ·
1 Parent(s): 2d5bd5e

Update utils/LedgerPdfGenerator.ts

Browse files
Files changed (1) hide show
  1. utils/LedgerPdfGenerator.ts +239 -239
utils/LedgerPdfGenerator.ts CHANGED
@@ -1,240 +1,240 @@
1
- import jsPDF from 'jspdf';
2
- import autoTable from 'jspdf-autotable';
3
- import { Party, Transaction } from '../types';
4
-
5
- // --- CONFIGURATION ---
6
- const BG_COLOR: [number, number, number] = [240, 234, 214]; // Beige
7
- const BORDER_COLOR: [number, number, number] = [0, 0, 0]; // Black
8
-
9
- // Helper: Currency Format
10
- const FORMAT_CURRENCY = (amount: number) => {
11
- return new Intl.NumberFormat('en-IN', {
12
- minimumFractionDigits: 2,
13
- maximumFractionDigits: 2
14
- }).format(amount);
15
- };
16
-
17
- // Helper: Date Format
18
- const FORMAT_DATE = (dateStr: string) => {
19
- if (!dateStr) return "";
20
- const dateObj = new Date(dateStr);
21
- return `${dateObj.getDate().toString().padStart(2, '0')}/${(dateObj.getMonth() + 1).toString().padStart(2, '0')}/${dateObj.getFullYear().toString().slice(-2)}`;
22
- };
23
-
24
- export const generateLedgerPDF = (party: Party, transactions: Transaction[]) => {
25
- const doc = new jsPDF();
26
-
27
- // --- 1. PAGE CONFIGURATION ---
28
- const PAGE_WIDTH = 210;
29
- const PAGE_HEIGHT = 297;
30
- const MARGIN = 14;
31
- const CONTENT_WIDTH = PAGE_WIDTH - (MARGIN * 2);
32
-
33
- // --- 2. SETUP & BACKGROUND ---
34
- doc.setFillColor(...BG_COLOR);
35
- doc.rect(0, 0, PAGE_WIDTH, PAGE_HEIGHT, 'F');
36
-
37
- // --- 3. DATA PREPARATION ---
38
- const sortedTxs = [...transactions].sort((a, b) =>
39
- new Date(a.bill_date).getTime() - new Date(b.bill_date).getTime()
40
- );
41
-
42
- let runningBalance = 0;
43
-
44
- const tableRows = sortedTxs.flatMap(tx => {
45
- const rows = [];
46
- const dateStr = FORMAT_DATE(tx.bill_date);
47
-
48
- // --- DETERMINE TYPE (AWAAK vs JAWAAK) ---
49
- // AWAAK = Purchase (We Owe - Credit)
50
- // JAWAAK = Sales (They Owe - Debit)
51
- const typeStr = String(tx.bill_type).toUpperCase();
52
- const numStr = tx.bill_number ? tx.bill_number.toUpperCase() : "";
53
-
54
- const isAwaak = typeStr === 'AWAAK' || numStr.includes('AWAAK');
55
-
56
- // --- A. BILL ROW ---
57
- // 1. Particulars Construction
58
- let itemDetails = "";
59
- if (tx.items && tx.items.length > 0) {
60
- tx.items.forEach(item => {
61
- let wStr = "";
62
- if (Array.isArray(item.poti_weights)) wStr = item.poti_weights.join(",");
63
- else if (item.poti_weights) wStr = String(item.poti_weights);
64
-
65
- const prefix = itemDetails ? "\n" : "";
66
- itemDetails += `${prefix}${item.mirchi_name}`;
67
- if (wStr) itemDetails += ` (${wStr})`;
68
- });
69
- }
70
-
71
- let billParticulars = `Bill No: ${tx.bill_number}`;
72
- if (tx.is_return) billParticulars += " (RETURN)";
73
- if (itemDetails) billParticulars += `\n${itemDetails}`;
74
-
75
- let totalPoti = 0;
76
- tx.items?.forEach(i => totalPoti += Number(i.poti_count) || 0);
77
-
78
- // 2. FINANCIAL LOGIC (THE FIX)
79
- let billDebit = 0;
80
- let billCredit = 0;
81
-
82
- if (isAwaak) {
83
- // AWAAK (Purchase):
84
- // - Normal Bill: CREDIT (Liability increases)
85
- // - Return Bill: DEBIT (Liability decreases)
86
- if (tx.is_return) billDebit = tx.total_amount;
87
- else billCredit = tx.total_amount;
88
- } else {
89
- // JAWAAK (Sales):
90
- // - Normal Bill: DEBIT (Asset increases - They owe us)
91
- // - Return Bill: CREDIT (Asset decreases)
92
- if (tx.is_return) billCredit = tx.total_amount;
93
- else billDebit = tx.total_amount;
94
- }
95
-
96
- // Running Balance = Old + Debit - Credit
97
- runningBalance = runningBalance + billDebit - billCredit;
98
-
99
- // Push Bill Row
100
- rows.push({
101
- date: dateStr,
102
- particulars: billParticulars,
103
- poti: totalPoti > 0 ? totalPoti.toString() : "-",
104
- credit: billCredit > 0 ? FORMAT_CURRENCY(billCredit) : "-",
105
- debit: billDebit > 0 ? FORMAT_CURRENCY(billDebit) : "-",
106
- balance: `${FORMAT_CURRENCY(Math.abs(runningBalance))} ${runningBalance >= 0 ? 'Dr' : 'Cr'}`,
107
- isMainRow: true
108
- });
109
-
110
- // --- B. PAYMENT ROWS ---
111
- if (tx.payments && tx.payments.length > 0) {
112
- const validPayments = tx.payments.filter(p => p.mode.toLowerCase() !== 'due');
113
-
114
- validPayments.forEach(p => {
115
- let payDebit = 0;
116
- let payCredit = 0;
117
-
118
- if (isAwaak) {
119
- // AWAAK (Purchase) Payment:
120
- // - We Pay Money: DEBIT (Liability decreases)
121
- // - Refund (Return): CREDIT
122
- if (tx.is_return) payCredit = p.amount;
123
- else payDebit = p.amount;
124
- } else {
125
- // JAWAAK (Sales) Payment:
126
- // - They Pay Money: CREDIT (Asset decreases - Debt paid off)
127
- // - Refund (Return): DEBIT
128
- if (tx.is_return) payDebit = p.amount;
129
- else payCredit = p.amount;
130
- }
131
-
132
- runningBalance = runningBalance + payDebit - payCredit;
133
-
134
- // Description
135
- const modeStr = p.mode.charAt(0).toUpperCase() + p.mode.slice(1);
136
- let payDesc = ` [${modeStr}]`;
137
- if (p.reference) payDesc += ` ${p.reference}`;
138
-
139
- rows.push({
140
- date: dateStr,
141
- particulars: payDesc,
142
- poti: "-",
143
- credit: payCredit > 0 ? FORMAT_CURRENCY(payCredit) : "-",
144
- debit: payDebit > 0 ? FORMAT_CURRENCY(payDebit) : "-",
145
- balance: `${FORMAT_CURRENCY(Math.abs(runningBalance))} ${runningBalance >= 0 ? 'Dr' : 'Cr'}`,
146
- isMainRow: false
147
- });
148
- });
149
- }
150
- return rows;
151
- });
152
-
153
- // --- 4. GENERATE TABLE ---
154
- autoTable(doc, {
155
- startY: 35,
156
- tableWidth: CONTENT_WIDTH,
157
- margin: { left: MARGIN, right: MARGIN },
158
-
159
- head: [[
160
- 'Date',
161
- 'Particulars',
162
- 'Poti',
163
- 'Credit\n(Jama)',
164
- 'Debit\n(Nave)',
165
- 'Balance'
166
- ]],
167
-
168
- body: tableRows.map(r => [r.date, r.particulars, r.poti, r.credit, r.debit, r.balance]),
169
-
170
- theme: 'grid',
171
-
172
- styles: {
173
- fillColor: false,
174
- textColor: 0,
175
- lineColor: 0,
176
- lineWidth: 0.1,
177
- font: 'helvetica',
178
- fontSize: 9,
179
- valign: 'top',
180
- cellPadding: 3,
181
- overflow: 'linebreak'
182
- },
183
-
184
- headStyles: {
185
- fillColor: false,
186
- textColor: 0,
187
- fontStyle: 'bold',
188
- halign: 'center',
189
- valign: 'middle',
190
- lineWidth: 0.1,
191
- },
192
-
193
- columnStyles: {
194
- 0: { cellWidth: 20, halign: 'center' }, // Date
195
- 1: { cellWidth: 72, halign: 'left' }, // Particulars
196
- 2: { cellWidth: 12, halign: 'center' }, // Poti
197
- 3: { cellWidth: 26, halign: 'right' }, // Credit
198
- 4: { cellWidth: 26, halign: 'right' }, // Debit
199
- 5: { cellWidth: 26, halign: 'right', fontStyle: 'bold' } // Balance
200
- },
201
-
202
- didParseCell: (data) => {
203
- const rowIdx = data.row.index;
204
- const rowData = tableRows[rowIdx];
205
- if (data.section === 'body' && data.column.index === 1 && rowData?.isMainRow) {
206
- data.cell.styles.fontStyle = 'bold';
207
- }
208
- },
209
-
210
- didDrawPage: (data) => {
211
- // Border
212
- doc.setDrawColor(...BORDER_COLOR);
213
- doc.setLineWidth(0.4);
214
- doc.rect(MARGIN, MARGIN, CONTENT_WIDTH, PAGE_HEIGHT - (MARGIN * 2));
215
-
216
- // Header
217
- if (data.pageNumber === 1) {
218
- const startX = MARGIN + 4;
219
- const startY = 25;
220
-
221
- doc.setFontSize(14);
222
- doc.setFont("helvetica", "bold");
223
- doc.text("Name of A/c :", startX, startY);
224
-
225
- const labelWidth = doc.getTextWidth("Name of A/c : ");
226
- doc.text(party.name, startX + labelWidth, startY);
227
-
228
- if (party.city || party.phone) {
229
- doc.setFontSize(9);
230
- doc.setFont("helvetica", "normal");
231
- const subText = [party.city, party.phone].filter(Boolean).join(" - ");
232
- doc.text(subText, startX, startY + 6);
233
- }
234
- }
235
- }
236
- });
237
-
238
- const cleanName = party.name.replace(/[^a-zA-Z0-9]/g, '_');
239
- doc.save(`${cleanName}_Ledger.pdf`);
240
  };
 
1
+ import jsPDF from 'jspdf';
2
+ import autoTable from 'jspdf-autotable';
3
+ import { Party, Transaction } from '../types';
4
+
5
+ // --- CONFIGURATION ---
6
+ const BG_COLOR: [number, number, number] = [240, 234, 214]; // Beige
7
+ const BORDER_COLOR: [number, number, number] = [0, 0, 0]; // Black
8
+
9
+ // Helper: Currency Format
10
+ const FORMAT_CURRENCY = (amount: number) => {
11
+ return new Intl.NumberFormat('en-IN', {
12
+ minimumFractionDigits: 2,
13
+ maximumFractionDigits: 2
14
+ }).format(amount);
15
+ };
16
+
17
+ // Helper: Date Format
18
+ const FORMAT_DATE = (dateStr: string) => {
19
+ if (!dateStr) return "";
20
+ const dateObj = new Date(dateStr);
21
+ return `${dateObj.getDate().toString().padStart(2, '0')}/${(dateObj.getMonth() + 1).toString().padStart(2, '0')}/${dateObj.getFullYear().toString().slice(-2)}`;
22
+ };
23
+
24
+ export const generateLedgerPDF = (party: Party, transactions: Transaction[]) => {
25
+ const doc = new jsPDF();
26
+
27
+ // --- 1. PAGE CONFIGURATION ---
28
+ const PAGE_WIDTH = 210;
29
+ const PAGE_HEIGHT = 297;
30
+ const MARGIN = 14;
31
+ const CONTENT_WIDTH = PAGE_WIDTH - (MARGIN * 2);
32
+
33
+ // --- 2. SETUP & BACKGROUND ---
34
+ doc.setFillColor(...BG_COLOR);
35
+ doc.rect(0, 0, PAGE_WIDTH, PAGE_HEIGHT, 'F');
36
+
37
+ // --- 3. DATA PREPARATION ---
38
+ const sortedTxs = [...transactions].sort((a, b) =>
39
+ new Date(a.bill_date).getTime() - new Date(b.bill_date).getTime()
40
+ );
41
+
42
+ let runningBalance = 0;
43
+
44
+ const tableRows = sortedTxs.flatMap(tx => {
45
+ const rows = [];
46
+ const dateStr = FORMAT_DATE(tx.bill_date);
47
+
48
+ // --- DETERMINE TYPE (AWAAK vs JAWAAK) ---
49
+ // AWAAK = Purchase (We Owe - Credit)
50
+ // JAWAAK = Sales (They Owe - Debit)
51
+ const typeStr = String(tx.bill_type).toUpperCase();
52
+ const numStr = tx.bill_number ? tx.bill_number.toUpperCase() : "";
53
+
54
+ const isAwaak = typeStr === 'AWAAK' || numStr.includes('AWAAK');
55
+
56
+ // --- A. BILL ROW ---
57
+ // 1. Particulars Construction
58
+ let itemDetails = "";
59
+ if (tx.items && tx.items.length > 0) {
60
+ tx.items.forEach(item => {
61
+ let wStr = "";
62
+ if (Array.isArray(item.poti_weights)) wStr = item.poti_weights.join(",");
63
+ else if (item.poti_weights) wStr = String(item.poti_weights);
64
+
65
+ const prefix = itemDetails ? "\n" : "";
66
+ itemDetails += `${prefix}${item.mirchi_name}`;
67
+ if (wStr) itemDetails += ` (${wStr})`;
68
+ });
69
+ }
70
+
71
+ let billParticulars = `No: ${tx.bill_number}`;
72
+ if (tx.is_return) billParticulars += " (RETURN)";
73
+ if (itemDetails) billParticulars += `\n${itemDetails}`;
74
+
75
+ let totalPoti = 0;
76
+ tx.items?.forEach(i => totalPoti += Number(i.poti_count) || 0);
77
+
78
+ // 2. FINANCIAL LOGIC (THE FIX)
79
+ let billDebit = 0;
80
+ let billCredit = 0;
81
+
82
+ if (isAwaak) {
83
+ // AWAAK (Purchase):
84
+ // - Normal Bill: CREDIT (Liability increases)
85
+ // - Return Bill: DEBIT (Liability decreases)
86
+ if (tx.is_return) billDebit = tx.total_amount;
87
+ else billCredit = tx.total_amount;
88
+ } else {
89
+ // JAWAAK (Sales):
90
+ // - Normal Bill: DEBIT (Asset increases - They owe us)
91
+ // - Return Bill: CREDIT (Asset decreases)
92
+ if (tx.is_return) billCredit = tx.total_amount;
93
+ else billDebit = tx.total_amount;
94
+ }
95
+
96
+ // Running Balance = Old + Debit - Credit
97
+ runningBalance = runningBalance + billDebit - billCredit;
98
+
99
+ // Push Bill Row
100
+ rows.push({
101
+ date: dateStr,
102
+ particulars: billParticulars,
103
+ poti: totalPoti > 0 ? totalPoti.toString() : "-",
104
+ credit: billCredit > 0 ? FORMAT_CURRENCY(billCredit) : "-",
105
+ debit: billDebit > 0 ? FORMAT_CURRENCY(billDebit) : "-",
106
+ balance: `${FORMAT_CURRENCY(Math.abs(runningBalance))} ${runningBalance >= 0 ? 'Dr' : 'Cr'}`,
107
+ isMainRow: true
108
+ });
109
+
110
+ // --- B. PAYMENT ROWS ---
111
+ if (tx.payments && tx.payments.length > 0) {
112
+ const validPayments = tx.payments.filter(p => p.mode.toLowerCase() !== 'due');
113
+
114
+ validPayments.forEach(p => {
115
+ let payDebit = 0;
116
+ let payCredit = 0;
117
+
118
+ if (isAwaak) {
119
+ // AWAAK (Purchase) Payment:
120
+ // - We Pay Money: DEBIT (Liability decreases)
121
+ // - Refund (Return): CREDIT
122
+ if (tx.is_return) payCredit = p.amount;
123
+ else payDebit = p.amount;
124
+ } else {
125
+ // JAWAAK (Sales) Payment:
126
+ // - They Pay Money: CREDIT (Asset decreases - Debt paid off)
127
+ // - Refund (Return): DEBIT
128
+ if (tx.is_return) payDebit = p.amount;
129
+ else payCredit = p.amount;
130
+ }
131
+
132
+ runningBalance = runningBalance + payDebit - payCredit;
133
+
134
+ // Description
135
+ const modeStr = p.mode.charAt(0).toUpperCase() + p.mode.slice(1);
136
+ let payDesc = ` [${modeStr}]`;
137
+ if (p.reference) payDesc += ` ${p.reference}`;
138
+
139
+ rows.push({
140
+ date: dateStr,
141
+ particulars: payDesc,
142
+ poti: "-",
143
+ credit: payCredit > 0 ? FORMAT_CURRENCY(payCredit) : "-",
144
+ debit: payDebit > 0 ? FORMAT_CURRENCY(payDebit) : "-",
145
+ balance: `${FORMAT_CURRENCY(Math.abs(runningBalance))} ${runningBalance >= 0 ? 'Dr' : 'Cr'}`,
146
+ isMainRow: false
147
+ });
148
+ });
149
+ }
150
+ return rows;
151
+ });
152
+
153
+ // --- 4. GENERATE TABLE ---
154
+ autoTable(doc, {
155
+ startY: 35,
156
+ tableWidth: CONTENT_WIDTH,
157
+ margin: { left: MARGIN, right: MARGIN },
158
+
159
+ head: [[
160
+ 'Date',
161
+ 'Particulars',
162
+ 'Poti',
163
+ 'Credit\n(Jama)',
164
+ 'Debit\n(Nave)',
165
+ 'Balance'
166
+ ]],
167
+
168
+ body: tableRows.map(r => [r.date, r.particulars, r.poti, r.credit, r.debit, r.balance]),
169
+
170
+ theme: 'grid',
171
+
172
+ styles: {
173
+ fillColor: false,
174
+ textColor: 0,
175
+ lineColor: 0,
176
+ lineWidth: 0.1,
177
+ font: 'helvetica',
178
+ fontSize: 9,
179
+ valign: 'top',
180
+ cellPadding: 3,
181
+ overflow: 'linebreak'
182
+ },
183
+
184
+ headStyles: {
185
+ fillColor: false,
186
+ textColor: 0,
187
+ fontStyle: 'bold',
188
+ halign: 'center',
189
+ valign: 'middle',
190
+ lineWidth: 0.1,
191
+ },
192
+
193
+ columnStyles: {
194
+ 0: { cellWidth: 20, halign: 'center' }, // Date
195
+ 1: { cellWidth: 72, halign: 'left' }, // Particulars
196
+ 2: { cellWidth: 12, halign: 'center' }, // Poti
197
+ 3: { cellWidth: 26, halign: 'right' }, // Credit
198
+ 4: { cellWidth: 26, halign: 'right' }, // Debit
199
+ 5: { cellWidth: 26, halign: 'right', fontStyle: 'bold' } // Balance
200
+ },
201
+
202
+ didParseCell: (data) => {
203
+ const rowIdx = data.row.index;
204
+ const rowData = tableRows[rowIdx];
205
+ if (data.section === 'body' && data.column.index === 1 && rowData?.isMainRow) {
206
+ data.cell.styles.fontStyle = 'bold';
207
+ }
208
+ },
209
+
210
+ didDrawPage: (data) => {
211
+ // Border
212
+ doc.setDrawColor(...BORDER_COLOR);
213
+ doc.setLineWidth(0.4);
214
+ doc.rect(MARGIN, MARGIN, CONTENT_WIDTH, PAGE_HEIGHT - (MARGIN * 2));
215
+
216
+ // Header
217
+ if (data.pageNumber === 1) {
218
+ const startX = MARGIN + 4;
219
+ const startY = 25;
220
+
221
+ doc.setFontSize(14);
222
+ doc.setFont("helvetica", "bold");
223
+ doc.text("Name of A/c :", startX, startY);
224
+
225
+ const labelWidth = doc.getTextWidth("Name of A/c : ");
226
+ doc.text(party.name, startX + labelWidth, startY);
227
+
228
+ if (party.city || party.phone) {
229
+ doc.setFontSize(9);
230
+ doc.setFont("helvetica", "normal");
231
+ const subText = [party.city, party.phone].filter(Boolean).join(" - ");
232
+ doc.text(subText, startX, startY + 6);
233
+ }
234
+ }
235
+ }
236
+ });
237
+
238
+ const cleanName = party.name.replace(/[^a-zA-Z0-9]/g, '_');
239
+ doc.save(`${cleanName}_Ledger.pdf`);
240
  };