Antaram commited on
Commit
18b7fd2
·
verified ·
1 Parent(s): ec489c8

Update services/db.ts

Browse files
Files changed (1) hide show
  1. services/db.ts +356 -356
services/db.ts CHANGED
@@ -1,357 +1,357 @@
1
- import {
2
- Party, MirchiType, Lot, Transaction, PartyType,
3
- LotStatus, BillType, ApiResponse, PaymentMode
4
- } from '../types';
5
-
6
- const API_BASE = 'http://localhost:4000/api';
7
-
8
- // Helper function to parse numeric values from API
9
- const parseNumeric = (value: any): number => {
10
- if (typeof value === 'string') {
11
- return parseFloat(value);
12
- }
13
- return value || 0;
14
- };
15
-
16
- // Helper function to parse poti_weights from string to array
17
- const parsePotiWeights = (weights: any): number[] => {
18
- if (typeof weights === 'string') {
19
- try {
20
- return JSON.parse(weights);
21
- } catch {
22
- return [];
23
- }
24
- }
25
- return weights || [];
26
- };
27
-
28
- // Helper function to normalize transaction data from API
29
- const normalizeTransaction = (tx: any): Transaction => {
30
- // Filter out null items that come from PostgreSQL json_agg when there are no items
31
- const rawItems = tx.items || [];
32
- const validItems = Array.isArray(rawItems)
33
- ? rawItems.filter((item: any) => item && item.id !== null)
34
- : [];
35
-
36
- const grossWeightTotal = parseNumeric(tx.gross_weight_total);
37
- const netWeightTotal = parseNumeric(tx.net_weight_total);
38
- const subtotal = parseNumeric(tx.subtotal);
39
- const totalExpenses = parseNumeric(tx.total_expenses);
40
- const totalAmount = parseNumeric(tx.total_amount);
41
- const paidAmount = parseNumeric(tx.paid_amount);
42
- const balanceAmount = parseNumeric(tx.balance_amount);
43
-
44
- // Normalize expenses and reconstruct other_expenses if backend didn't send it
45
- let normalizedExpenses;
46
- if (tx.expenses) {
47
- const hasOtherExpensesField = Object.prototype.hasOwnProperty.call(tx.expenses, 'other_expenses');
48
-
49
- const baseExpenses = {
50
- cess_percent: parseNumeric(tx.expenses.cess_percent),
51
- cess_amount: parseNumeric(tx.expenses.cess_amount),
52
- adat_percent: parseNumeric(tx.expenses.adat_percent),
53
- adat_amount: parseNumeric(tx.expenses.adat_amount),
54
- poti_rate: parseNumeric(tx.expenses.poti_rate),
55
- poti_amount: parseNumeric(tx.expenses.poti_amount),
56
- hamali_per_poti: parseNumeric(tx.expenses.hamali_per_poti),
57
- hamali_amount: parseNumeric(tx.expenses.hamali_amount),
58
- packaging_hamali_per_poti: parseNumeric(tx.expenses.packaging_hamali_per_poti),
59
- packaging_hamali_amount: parseNumeric(tx.expenses.packaging_hamali_amount),
60
- gaadi_bharni: parseNumeric(tx.expenses.gaadi_bharni),
61
- };
62
-
63
- let otherExpensesValue = hasOtherExpensesField
64
- ? parseNumeric(tx.expenses.other_expenses)
65
- : 0;
66
-
67
- // If backend doesn't provide other_expenses but total_expenses is known,
68
- // infer other_expenses as the remaining part after subtracting known components.
69
- if (!hasOtherExpensesField && totalExpenses) {
70
- const knownComponents =
71
- baseExpenses.poti_amount +
72
- baseExpenses.cess_amount +
73
- baseExpenses.adat_amount +
74
- baseExpenses.hamali_amount +
75
- baseExpenses.packaging_hamali_amount +
76
- baseExpenses.gaadi_bharni;
77
-
78
- const diff = totalExpenses - knownComponents;
79
- if (diff > 0) {
80
- otherExpensesValue = diff;
81
- }
82
- }
83
-
84
- normalizedExpenses = {
85
- ...baseExpenses,
86
- other_expenses: otherExpensesValue,
87
- };
88
- } else {
89
- normalizedExpenses = {
90
- cess_percent: 0,
91
- cess_amount: 0,
92
- adat_percent: 0,
93
- adat_amount: 0,
94
- poti_rate: 0,
95
- poti_amount: 0,
96
- hamali_per_poti: 0,
97
- hamali_amount: 0,
98
- packaging_hamali_per_poti: 0,
99
- packaging_hamali_amount: 0,
100
- gaadi_bharni: 0,
101
- other_expenses: 0,
102
- };
103
- }
104
-
105
- return {
106
- ...tx,
107
- gross_weight_total: grossWeightTotal,
108
- net_weight_total: netWeightTotal,
109
- subtotal,
110
- total_expenses: totalExpenses,
111
- total_amount: totalAmount,
112
- paid_amount: paidAmount,
113
- balance_amount: balanceAmount,
114
- items: validItems.map((item: any) => ({
115
- ...item,
116
- poti_weights: parsePotiWeights(item.poti_weights),
117
- gross_weight: parseNumeric(item.gross_weight),
118
- poti_count: parseNumeric(item.poti_count),
119
- total_potya: parseNumeric(item.total_potya),
120
- net_weight: parseNumeric(item.net_weight),
121
- rate_per_kg: parseNumeric(item.rate_per_kg),
122
- item_total: parseNumeric(item.item_total),
123
- })),
124
- expenses: normalizedExpenses,
125
- payments: (tx.payments || []).map((p: any) => ({
126
- ...p,
127
- amount: parseNumeric(p.amount),
128
- })),
129
- };
130
- };
131
-
132
- // Parties API
133
- export const getParties = async (): Promise<Party[]> => {
134
- try {
135
- const res = await fetch(`${API_BASE}/parties`);
136
- if (!res.ok) throw new Error('Failed to load parties');
137
- const data = await res.json();
138
- return data.map((p: any) => ({
139
- ...p,
140
- current_balance: parseNumeric(p.current_balance)
141
- }));
142
- } catch (error) {
143
- console.error('Error fetching parties:', error);
144
- return [];
145
- }
146
- };
147
-
148
- export const saveParty = async (party: Party): Promise<ApiResponse<Party>> => {
149
- try {
150
- const res = await fetch(`${API_BASE}/parties`, {
151
- method: 'POST',
152
- headers: { 'Content-Type': 'application/json' },
153
- body: JSON.stringify(party),
154
- });
155
-
156
- const data = await res.json();
157
- if (!res.ok || !data.success) {
158
- return { success: false, message: data?.message || 'Error saving party' };
159
- }
160
-
161
- return {
162
- success: true,
163
- data: {
164
- ...data.data,
165
- current_balance: parseNumeric(data.data.current_balance)
166
- },
167
- message: data.message || 'Party saved successfully'
168
- };
169
- } catch (e: any) {
170
- return { success: false, message: e.message || 'Database error' };
171
- }
172
- };
173
-
174
- // Mirchi Types API
175
- export const apiGetMirchiTypes = async (): Promise<MirchiType[]> => {
176
- try {
177
- const res = await fetch(`${API_BASE}/mirchi-types`);
178
- if (!res.ok) throw new Error('Failed to load mirchi types');
179
- const data = await res.json();
180
- return data.map((m: any) => ({
181
- ...m,
182
- current_rate: parseNumeric(m.current_rate)
183
- }));
184
- } catch (error) {
185
- console.error('Error fetching mirchi types:', error);
186
- return [];
187
- }
188
- };
189
-
190
- export const apiSaveMirchiType = async (type: MirchiType): Promise<ApiResponse<MirchiType>> => {
191
- try {
192
- const res = await fetch(`${API_BASE}/mirchi-types`, {
193
- method: 'POST',
194
- headers: { 'Content-Type': 'application/json' },
195
- body: JSON.stringify(type),
196
- });
197
- const data = await res.json();
198
- if (!res.ok || !data.success) {
199
- return { success: false, message: data?.message || 'Error saving type' };
200
- }
201
- return {
202
- success: true,
203
- data: {
204
- ...data.data,
205
- current_rate: parseNumeric(data.data.current_rate)
206
- },
207
- message: data.message
208
- };
209
- } catch (e: any) {
210
- return { success: false, message: e.message || 'Error saving type' };
211
- }
212
- };
213
-
214
- // Alias for compatibility
215
- export const getMirchiTypes = apiGetMirchiTypes;
216
- export const saveMirchiType = apiSaveMirchiType;
217
-
218
- // Lots API
219
- export const getActiveLots = async (): Promise<Lot[]> => {
220
- try {
221
- const res = await fetch(`${API_BASE}/lots/active`);
222
- if (!res.ok) throw new Error('Failed to load active lots');
223
- const data = await res.json();
224
- return data.map((l: any) => ({
225
- ...l,
226
- total_quantity: parseNumeric(l.total_quantity),
227
- remaining_quantity: parseNumeric(l.remaining_quantity),
228
- avg_rate: parseNumeric(l.avg_rate)
229
- }));
230
- } catch (error) {
231
- console.error('Error fetching active lots:', error);
232
- return [];
233
- }
234
- };
235
-
236
- // Check if lot number is unique
237
- export const checkLotUnique = async (lotNumber: string): Promise<boolean> => {
238
- try {
239
- const res = await fetch(`${API_BASE}/lots/check-unique`, {
240
- method: 'POST',
241
- headers: { 'Content-Type': 'application/json' },
242
- body: JSON.stringify({ lot_number: lotNumber }),
243
- });
244
- if (!res.ok) throw new Error('Failed to check lot uniqueness');
245
- const data = await res.json();
246
- return !data.exists; // Return true if unique (not exists)
247
- } catch (error) {
248
- console.error('Error checking lot uniqueness:', error);
249
- return false;
250
- }
251
- };
252
-
253
- // Get available lots for a specific mirchi type
254
- export const getAvailableLotsByMirchi = async (mirchiTypeId: string): Promise<Lot[]> => {
255
- try {
256
- const res = await fetch(`${API_BASE}/lots/available/${mirchiTypeId}`);
257
- if (!res.ok) throw new Error('Failed to load available lots');
258
- const data = await res.json();
259
- return data.map((l: any) => ({
260
- ...l,
261
- total_quantity: parseNumeric(l.total_quantity),
262
- remaining_quantity: parseNumeric(l.remaining_quantity),
263
- avg_rate: parseNumeric(l.avg_rate)
264
- }));
265
- } catch (error) {
266
- console.error('Error fetching available lots:', error);
267
- return [];
268
- }
269
- };
270
-
271
- // Transactions API
272
- export const getTransactions = async (): Promise<Transaction[]> => {
273
- try {
274
- const res = await fetch(`${API_BASE}/transactions`);
275
- if (!res.ok) throw new Error('Failed to load transactions');
276
- const data = await res.json();
277
- return data.map(normalizeTransaction);
278
- } catch (error) {
279
- console.error('Error fetching transactions:', error);
280
- return [];
281
- }
282
- };
283
-
284
- export const generateBillNumber = (type: BillType, isReturn: boolean = false): string => {
285
- const prefix = type === BillType.JAWAAK ? (isReturn ? 'JAWAAK-RET' : 'JAWAAK') : (isReturn ? 'AWAAK-RET' : 'AWAAK');
286
- const timestamp = Date.now();
287
- return `${prefix}-${new Date().getFullYear()}-${String(timestamp).slice(-4)}`;
288
- };
289
-
290
- export const saveTransaction = async (transaction: Transaction): Promise<ApiResponse<Transaction>> => {
291
- try {
292
- // Validate Payload
293
- if (!transaction.party_id) return { success: false, message: "Party is required" };
294
- if (!transaction.items || transaction.items.length === 0) return { success: false, message: "At least one item is required" };
295
-
296
- console.log('=== SAVING TRANSACTION ===');
297
- console.log('Transaction ID:', transaction.id);
298
- console.log('Items count:', transaction.items?.length);
299
- console.log('Items data:', JSON.stringify(transaction.items, null, 2));
300
-
301
- const res = await fetch(`${API_BASE}/transactions`, {
302
- method: 'POST',
303
- headers: { 'Content-Type': 'application/json' },
304
- body: JSON.stringify(transaction)
305
- });
306
-
307
- if (!res.ok) {
308
- const error = await res.json();
309
- console.error('Save transaction error:', error);
310
- throw new Error(error.message || 'Failed to save transaction');
311
- }
312
-
313
- const result = await res.json();
314
- console.log('=== SAVE RESPONSE ===');
315
- console.log('Response data:', result.data);
316
- console.log('Response items count:', result.data?.items?.length);
317
- console.log('Response items:', JSON.stringify(result.data?.items, null, 2));
318
-
319
- return {
320
- success: true,
321
- data: normalizeTransaction(result.data),
322
- message: result.message
323
- };
324
- } catch (error: any) {
325
- console.error('Error saving transaction:', error);
326
- return {
327
- success: false,
328
- message: error.message || 'Failed to save transaction'
329
- };
330
- }
331
- };
332
-
333
- export const updateTransactionPayment = async (transactionId: string, amount: number): Promise<ApiResponse<Transaction>> => {
334
- try {
335
- // Validate amount
336
- if (amount <= 0) return { success: false, message: "Amount must be greater than 0" };
337
-
338
- const res = await fetch(`${API_BASE}/transactions/${transactionId}/payment`, {
339
- method: 'PATCH',
340
- headers: { 'Content-Type': 'application/json' },
341
- body: JSON.stringify({ amount }),
342
- });
343
-
344
- const data = await res.json();
345
- if (!res.ok || !data.success) {
346
- return { success: false, message: data?.message || 'Error updating payment' };
347
- }
348
-
349
- return {
350
- success: true,
351
- message: data.message || 'Payment updated successfully'
352
- };
353
- } catch (e: any) {
354
- console.error('Error updating payment:', e);
355
- return { success: false, message: e.message || 'Error updating payment' };
356
- }
357
  };
 
1
+ import {
2
+ Party, MirchiType, Lot, Transaction, PartyType,
3
+ LotStatus, BillType, ApiResponse, PaymentMode
4
+ } from '../types';
5
+
6
+ const API_BASE = 'https://antaram-stagingbackend.hf.space/api';
7
+
8
+ // Helper function to parse numeric values from API
9
+ const parseNumeric = (value: any): number => {
10
+ if (typeof value === 'string') {
11
+ return parseFloat(value);
12
+ }
13
+ return value || 0;
14
+ };
15
+
16
+ // Helper function to parse poti_weights from string to array
17
+ const parsePotiWeights = (weights: any): number[] => {
18
+ if (typeof weights === 'string') {
19
+ try {
20
+ return JSON.parse(weights);
21
+ } catch {
22
+ return [];
23
+ }
24
+ }
25
+ return weights || [];
26
+ };
27
+
28
+ // Helper function to normalize transaction data from API
29
+ const normalizeTransaction = (tx: any): Transaction => {
30
+ // Filter out null items that come from PostgreSQL json_agg when there are no items
31
+ const rawItems = tx.items || [];
32
+ const validItems = Array.isArray(rawItems)
33
+ ? rawItems.filter((item: any) => item && item.id !== null)
34
+ : [];
35
+
36
+ const grossWeightTotal = parseNumeric(tx.gross_weight_total);
37
+ const netWeightTotal = parseNumeric(tx.net_weight_total);
38
+ const subtotal = parseNumeric(tx.subtotal);
39
+ const totalExpenses = parseNumeric(tx.total_expenses);
40
+ const totalAmount = parseNumeric(tx.total_amount);
41
+ const paidAmount = parseNumeric(tx.paid_amount);
42
+ const balanceAmount = parseNumeric(tx.balance_amount);
43
+
44
+ // Normalize expenses and reconstruct other_expenses if backend didn't send it
45
+ let normalizedExpenses;
46
+ if (tx.expenses) {
47
+ const hasOtherExpensesField = Object.prototype.hasOwnProperty.call(tx.expenses, 'other_expenses');
48
+
49
+ const baseExpenses = {
50
+ cess_percent: parseNumeric(tx.expenses.cess_percent),
51
+ cess_amount: parseNumeric(tx.expenses.cess_amount),
52
+ adat_percent: parseNumeric(tx.expenses.adat_percent),
53
+ adat_amount: parseNumeric(tx.expenses.adat_amount),
54
+ poti_rate: parseNumeric(tx.expenses.poti_rate),
55
+ poti_amount: parseNumeric(tx.expenses.poti_amount),
56
+ hamali_per_poti: parseNumeric(tx.expenses.hamali_per_poti),
57
+ hamali_amount: parseNumeric(tx.expenses.hamali_amount),
58
+ packaging_hamali_per_poti: parseNumeric(tx.expenses.packaging_hamali_per_poti),
59
+ packaging_hamali_amount: parseNumeric(tx.expenses.packaging_hamali_amount),
60
+ gaadi_bharni: parseNumeric(tx.expenses.gaadi_bharni),
61
+ };
62
+
63
+ let otherExpensesValue = hasOtherExpensesField
64
+ ? parseNumeric(tx.expenses.other_expenses)
65
+ : 0;
66
+
67
+ // If backend doesn't provide other_expenses but total_expenses is known,
68
+ // infer other_expenses as the remaining part after subtracting known components.
69
+ if (!hasOtherExpensesField && totalExpenses) {
70
+ const knownComponents =
71
+ baseExpenses.poti_amount +
72
+ baseExpenses.cess_amount +
73
+ baseExpenses.adat_amount +
74
+ baseExpenses.hamali_amount +
75
+ baseExpenses.packaging_hamali_amount +
76
+ baseExpenses.gaadi_bharni;
77
+
78
+ const diff = totalExpenses - knownComponents;
79
+ if (diff > 0) {
80
+ otherExpensesValue = diff;
81
+ }
82
+ }
83
+
84
+ normalizedExpenses = {
85
+ ...baseExpenses,
86
+ other_expenses: otherExpensesValue,
87
+ };
88
+ } else {
89
+ normalizedExpenses = {
90
+ cess_percent: 0,
91
+ cess_amount: 0,
92
+ adat_percent: 0,
93
+ adat_amount: 0,
94
+ poti_rate: 0,
95
+ poti_amount: 0,
96
+ hamali_per_poti: 0,
97
+ hamali_amount: 0,
98
+ packaging_hamali_per_poti: 0,
99
+ packaging_hamali_amount: 0,
100
+ gaadi_bharni: 0,
101
+ other_expenses: 0,
102
+ };
103
+ }
104
+
105
+ return {
106
+ ...tx,
107
+ gross_weight_total: grossWeightTotal,
108
+ net_weight_total: netWeightTotal,
109
+ subtotal,
110
+ total_expenses: totalExpenses,
111
+ total_amount: totalAmount,
112
+ paid_amount: paidAmount,
113
+ balance_amount: balanceAmount,
114
+ items: validItems.map((item: any) => ({
115
+ ...item,
116
+ poti_weights: parsePotiWeights(item.poti_weights),
117
+ gross_weight: parseNumeric(item.gross_weight),
118
+ poti_count: parseNumeric(item.poti_count),
119
+ total_potya: parseNumeric(item.total_potya),
120
+ net_weight: parseNumeric(item.net_weight),
121
+ rate_per_kg: parseNumeric(item.rate_per_kg),
122
+ item_total: parseNumeric(item.item_total),
123
+ })),
124
+ expenses: normalizedExpenses,
125
+ payments: (tx.payments || []).map((p: any) => ({
126
+ ...p,
127
+ amount: parseNumeric(p.amount),
128
+ })),
129
+ };
130
+ };
131
+
132
+ // Parties API
133
+ export const getParties = async (): Promise<Party[]> => {
134
+ try {
135
+ const res = await fetch(`${API_BASE}/parties`);
136
+ if (!res.ok) throw new Error('Failed to load parties');
137
+ const data = await res.json();
138
+ return data.map((p: any) => ({
139
+ ...p,
140
+ current_balance: parseNumeric(p.current_balance)
141
+ }));
142
+ } catch (error) {
143
+ console.error('Error fetching parties:', error);
144
+ return [];
145
+ }
146
+ };
147
+
148
+ export const saveParty = async (party: Party): Promise<ApiResponse<Party>> => {
149
+ try {
150
+ const res = await fetch(`${API_BASE}/parties`, {
151
+ method: 'POST',
152
+ headers: { 'Content-Type': 'application/json' },
153
+ body: JSON.stringify(party),
154
+ });
155
+
156
+ const data = await res.json();
157
+ if (!res.ok || !data.success) {
158
+ return { success: false, message: data?.message || 'Error saving party' };
159
+ }
160
+
161
+ return {
162
+ success: true,
163
+ data: {
164
+ ...data.data,
165
+ current_balance: parseNumeric(data.data.current_balance)
166
+ },
167
+ message: data.message || 'Party saved successfully'
168
+ };
169
+ } catch (e: any) {
170
+ return { success: false, message: e.message || 'Database error' };
171
+ }
172
+ };
173
+
174
+ // Mirchi Types API
175
+ export const apiGetMirchiTypes = async (): Promise<MirchiType[]> => {
176
+ try {
177
+ const res = await fetch(`${API_BASE}/mirchi-types`);
178
+ if (!res.ok) throw new Error('Failed to load mirchi types');
179
+ const data = await res.json();
180
+ return data.map((m: any) => ({
181
+ ...m,
182
+ current_rate: parseNumeric(m.current_rate)
183
+ }));
184
+ } catch (error) {
185
+ console.error('Error fetching mirchi types:', error);
186
+ return [];
187
+ }
188
+ };
189
+
190
+ export const apiSaveMirchiType = async (type: MirchiType): Promise<ApiResponse<MirchiType>> => {
191
+ try {
192
+ const res = await fetch(`${API_BASE}/mirchi-types`, {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify(type),
196
+ });
197
+ const data = await res.json();
198
+ if (!res.ok || !data.success) {
199
+ return { success: false, message: data?.message || 'Error saving type' };
200
+ }
201
+ return {
202
+ success: true,
203
+ data: {
204
+ ...data.data,
205
+ current_rate: parseNumeric(data.data.current_rate)
206
+ },
207
+ message: data.message
208
+ };
209
+ } catch (e: any) {
210
+ return { success: false, message: e.message || 'Error saving type' };
211
+ }
212
+ };
213
+
214
+ // Alias for compatibility
215
+ export const getMirchiTypes = apiGetMirchiTypes;
216
+ export const saveMirchiType = apiSaveMirchiType;
217
+
218
+ // Lots API
219
+ export const getActiveLots = async (): Promise<Lot[]> => {
220
+ try {
221
+ const res = await fetch(`${API_BASE}/lots/active`);
222
+ if (!res.ok) throw new Error('Failed to load active lots');
223
+ const data = await res.json();
224
+ return data.map((l: any) => ({
225
+ ...l,
226
+ total_quantity: parseNumeric(l.total_quantity),
227
+ remaining_quantity: parseNumeric(l.remaining_quantity),
228
+ avg_rate: parseNumeric(l.avg_rate)
229
+ }));
230
+ } catch (error) {
231
+ console.error('Error fetching active lots:', error);
232
+ return [];
233
+ }
234
+ };
235
+
236
+ // Check if lot number is unique
237
+ export const checkLotUnique = async (lotNumber: string): Promise<boolean> => {
238
+ try {
239
+ const res = await fetch(`${API_BASE}/lots/check-unique`, {
240
+ method: 'POST',
241
+ headers: { 'Content-Type': 'application/json' },
242
+ body: JSON.stringify({ lot_number: lotNumber }),
243
+ });
244
+ if (!res.ok) throw new Error('Failed to check lot uniqueness');
245
+ const data = await res.json();
246
+ return !data.exists; // Return true if unique (not exists)
247
+ } catch (error) {
248
+ console.error('Error checking lot uniqueness:', error);
249
+ return false;
250
+ }
251
+ };
252
+
253
+ // Get available lots for a specific mirchi type
254
+ export const getAvailableLotsByMirchi = async (mirchiTypeId: string): Promise<Lot[]> => {
255
+ try {
256
+ const res = await fetch(`${API_BASE}/lots/available/${mirchiTypeId}`);
257
+ if (!res.ok) throw new Error('Failed to load available lots');
258
+ const data = await res.json();
259
+ return data.map((l: any) => ({
260
+ ...l,
261
+ total_quantity: parseNumeric(l.total_quantity),
262
+ remaining_quantity: parseNumeric(l.remaining_quantity),
263
+ avg_rate: parseNumeric(l.avg_rate)
264
+ }));
265
+ } catch (error) {
266
+ console.error('Error fetching available lots:', error);
267
+ return [];
268
+ }
269
+ };
270
+
271
+ // Transactions API
272
+ export const getTransactions = async (): Promise<Transaction[]> => {
273
+ try {
274
+ const res = await fetch(`${API_BASE}/transactions`);
275
+ if (!res.ok) throw new Error('Failed to load transactions');
276
+ const data = await res.json();
277
+ return data.map(normalizeTransaction);
278
+ } catch (error) {
279
+ console.error('Error fetching transactions:', error);
280
+ return [];
281
+ }
282
+ };
283
+
284
+ export const generateBillNumber = (type: BillType, isReturn: boolean = false): string => {
285
+ const prefix = type === BillType.JAWAAK ? (isReturn ? 'JAWAAK-RET' : 'JAWAAK') : (isReturn ? 'AWAAK-RET' : 'AWAAK');
286
+ const timestamp = Date.now();
287
+ return `${prefix}-${new Date().getFullYear()}-${String(timestamp).slice(-4)}`;
288
+ };
289
+
290
+ export const saveTransaction = async (transaction: Transaction): Promise<ApiResponse<Transaction>> => {
291
+ try {
292
+ // Validate Payload
293
+ if (!transaction.party_id) return { success: false, message: "Party is required" };
294
+ if (!transaction.items || transaction.items.length === 0) return { success: false, message: "At least one item is required" };
295
+
296
+ console.log('=== SAVING TRANSACTION ===');
297
+ console.log('Transaction ID:', transaction.id);
298
+ console.log('Items count:', transaction.items?.length);
299
+ console.log('Items data:', JSON.stringify(transaction.items, null, 2));
300
+
301
+ const res = await fetch(`${API_BASE}/transactions`, {
302
+ method: 'POST',
303
+ headers: { 'Content-Type': 'application/json' },
304
+ body: JSON.stringify(transaction)
305
+ });
306
+
307
+ if (!res.ok) {
308
+ const error = await res.json();
309
+ console.error('Save transaction error:', error);
310
+ throw new Error(error.message || 'Failed to save transaction');
311
+ }
312
+
313
+ const result = await res.json();
314
+ console.log('=== SAVE RESPONSE ===');
315
+ console.log('Response data:', result.data);
316
+ console.log('Response items count:', result.data?.items?.length);
317
+ console.log('Response items:', JSON.stringify(result.data?.items, null, 2));
318
+
319
+ return {
320
+ success: true,
321
+ data: normalizeTransaction(result.data),
322
+ message: result.message
323
+ };
324
+ } catch (error: any) {
325
+ console.error('Error saving transaction:', error);
326
+ return {
327
+ success: false,
328
+ message: error.message || 'Failed to save transaction'
329
+ };
330
+ }
331
+ };
332
+
333
+ export const updateTransactionPayment = async (transactionId: string, amount: number): Promise<ApiResponse<Transaction>> => {
334
+ try {
335
+ // Validate amount
336
+ if (amount <= 0) return { success: false, message: "Amount must be greater than 0" };
337
+
338
+ const res = await fetch(`${API_BASE}/transactions/${transactionId}/payment`, {
339
+ method: 'PATCH',
340
+ headers: { 'Content-Type': 'application/json' },
341
+ body: JSON.stringify({ amount }),
342
+ });
343
+
344
+ const data = await res.json();
345
+ if (!res.ok || !data.success) {
346
+ return { success: false, message: data?.message || 'Error updating payment' };
347
+ }
348
+
349
+ return {
350
+ success: true,
351
+ message: data.message || 'Payment updated successfully'
352
+ };
353
+ } catch (e: any) {
354
+ console.error('Error updating payment:', e);
355
+ return { success: false, message: e.message || 'Error updating payment' };
356
+ }
357
  };