Repository navigation
Expand file tree
/
Copy pathPAYMENT_VALIDATION.sql
More file actions
397 lines (340 loc) · 12.2 KB
/
Copy pathPAYMENT_VALIDATION.sql
File metadata and controls
397 lines (340 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
-- =====================================================
-- PURCHASE ORDER PAYMENT VALIDATION & TESTING
-- =====================================================
-- =====================================================
-- SECTION 1: CHECK PAYMENT FUNCTIONS EXIST
-- =====================================================
SELECT
routine_name,
'EXISTS ✅' as status
FROM information_schema.routines
WHERE routine_schema = 'public'
AND routine_name IN (
'process_purchase_order_payment',
'get_purchase_order_payment_summary',
'get_purchase_order_payment_history'
)
ORDER BY routine_name;
-- Expected: All 3 functions should exist
-- If missing, they need to be created
-- =====================================================
-- SECTION 2: VALIDATE PAYMENT TABLE STRUCTURE
-- =====================================================
-- Check purchase_order_payments table
SELECT
column_name,
data_type,
is_nullable
FROM information_schema.columns
WHERE table_name = 'purchase_order_payments'
OR table_name = 'purchase_order_payments'
ORDER BY ordinal_position;
-- Check if we're using the correct table name
SELECT
CASE
WHEN EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'purchase_order_payments')
THEN 'purchase_order_payments'
WHEN EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'purchase_order_payments')
THEN 'purchase_order_payments'
ELSE 'NO PAYMENT TABLE FOUND ❌'
END as payment_table_name;
-- =====================================================
-- SECTION 3: PAYMENT DATA INTEGRITY CHECKS
-- =====================================================
-- Check for payments with invalid purchase order references
WITH payment_table AS (
SELECT 'purchase_order_payments' as tbl_name
WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'purchase_order_payments')
UNION ALL
SELECT 'purchase_order_payments'
WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'purchase_order_payments')
LIMIT 1
)
SELECT
COUNT(*) as orphaned_payments,
CASE
WHEN COUNT(*) = 0 THEN 'OK ✅'
ELSE 'Orphaned payments found ⚠️'
END as status
FROM purchase_order_payments pop
LEFT JOIN lats_purchase_orders po ON pop.purchase_order_id = po.id
WHERE po.id IS NULL;
-- Check for payments with invalid account references
SELECT
COUNT(*) as invalid_account_payments,
CASE
WHEN COUNT(*) = 0 THEN 'OK ✅'
ELSE 'Invalid account references ⚠️'
END as status
FROM purchase_order_payments pop
LEFT JOIN finance_accounts fa ON pop.payment_account_id = fa.id
WHERE fa.id IS NULL;
-- =====================================================
-- SECTION 4: PAYMENT SUMMARY BY STATUS
-- =====================================================
SELECT
status,
COUNT(*) as payment_count,
SUM(amount) as total_amount,
COUNT(DISTINCT purchase_order_id) as unique_purchase_orders,
MIN(payment_date) as earliest_payment,
MAX(payment_date) as latest_payment
FROM purchase_order_payments
GROUP BY status
ORDER BY status;
-- =====================================================
-- SECTION 5: PURCHASE ORDER PAYMENT RECONCILIATION
-- =====================================================
-- Compare PO totals with payments received
SELECT
po.id,
po.order_number,
po.status,
po.total_amount as po_total,
COALESCE(SUM(pop.amount), 0) as total_paid,
po.total_amount - COALESCE(SUM(pop.amount), 0) as remaining,
ROUND((COALESCE(SUM(pop.amount), 0) / NULLIF(po.total_amount, 0)) * 100, 2) as payment_percentage,
COUNT(pop.id) as payment_count,
CASE
WHEN COALESCE(SUM(pop.amount), 0) >= po.total_amount THEN 'Fully Paid ✅'
WHEN COALESCE(SUM(pop.amount), 0) > 0 THEN 'Partially Paid ⏳'
ELSE 'Unpaid ⚠️'
END as payment_status
FROM lats_purchase_orders po
LEFT JOIN purchase_order_payments pop ON po.id = pop.purchase_order_id
AND pop.status = 'completed'
WHERE po.status NOT IN ('draft', 'cancelled')
GROUP BY po.id, po.order_number, po.status, po.total_amount
ORDER BY po.created_at DESC
LIMIT 20;
-- =====================================================
-- SECTION 6: OVERPAYMENT DETECTION
-- =====================================================
-- Check for purchase orders where payments exceed total amount
SELECT
po.id,
po.order_number,
po.total_amount,
SUM(pop.amount) as total_paid,
SUM(pop.amount) - po.total_amount as overpayment_amount,
'OVERPAYMENT DETECTED ⚠️' as alert
FROM lats_purchase_orders po
JOIN purchase_order_payments pop ON po.id = pop.purchase_order_id
WHERE pop.status = 'completed'
GROUP BY po.id, po.order_number, po.total_amount
HAVING SUM(pop.amount) > po.total_amount;
-- Expected: Should return 0 rows (no overpayments)
-- =====================================================
-- SECTION 7: FINANCE ACCOUNT BALANCE CHECK
-- =====================================================
-- Verify finance account balances are consistent with payments
SELECT
fa.id,
fa.account_number as account_name,
fa.type as account_type,
fa.currency,
fa.balance as current_balance,
COALESCE(SUM(pop.amount), 0) as total_payments_made,
'Account used for PO payments' as note
FROM finance_accounts fa
LEFT JOIN purchase_order_payments pop ON fa.id = pop.payment_account_id
AND pop.status = 'completed'
GROUP BY fa.id, fa.account_number, fa.type, fa.currency, fa.balance
HAVING COUNT(pop.id) > 0
ORDER BY fa.account_number;
-- =====================================================
-- SECTION 8: CURRENCY CONSISTENCY CHECK
-- =====================================================
-- Check for currency mismatches between PO and payments
SELECT
po.id,
po.order_number,
po.currency as po_currency,
pop.currency as payment_currency,
pop.amount,
pop.notes,
'Currency mismatch - check for conversion' as alert
FROM lats_purchase_orders po
JOIN purchase_order_payments pop ON po.id = pop.purchase_order_id
WHERE po.currency != pop.currency
AND pop.status = 'completed'
ORDER BY pop.created_at DESC;
-- Expected: If there are currency mismatches, notes should explain conversion
-- =====================================================
-- SECTION 9: RECENT PAYMENT ACTIVITY
-- =====================================================
SELECT
pop.id,
pop.payment_date,
po.order_number,
pop.amount,
pop.currency,
pop.payment_method,
pop.status,
fa.account_number as account_name,
pop.reference,
NOW() - pop.payment_date as time_ago
FROM purchase_order_payments pop
JOIN lats_purchase_orders po ON pop.purchase_order_id = po.id
LEFT JOIN finance_accounts fa ON pop.payment_account_id = fa.id
WHERE pop.payment_date > NOW() - INTERVAL '30 days'
ORDER BY pop.payment_date DESC
LIMIT 50;
-- =====================================================
-- SECTION 10: PAYMENT METHOD DISTRIBUTION
-- =====================================================
SELECT
payment_method,
COUNT(*) as transaction_count,
SUM(amount) as total_amount,
AVG(amount) as avg_amount,
MIN(amount) as min_amount,
MAX(amount) as max_amount,
COUNT(DISTINCT purchase_order_id) as unique_pos
FROM purchase_order_payments
WHERE status = 'completed'
GROUP BY payment_method
ORDER BY total_amount DESC;
-- =====================================================
-- SECTION 11: TEST SPECIFIC PURCHASE ORDER
-- =====================================================
-- Replace with your actual purchase order ID
DO $$
DECLARE
test_po_id UUID := '3c6510dc-c025-4a87-9a63-f4083b5b871b';
BEGIN
RAISE NOTICE '=== Testing Purchase Order: % ===', test_po_id;
-- Get PO details
RAISE NOTICE 'Purchase Order Details:';
PERFORM * FROM lats_purchase_orders WHERE id = test_po_id;
-- Get payment summary
RAISE NOTICE 'Payment Summary:';
PERFORM * FROM purchase_order_payments WHERE purchase_order_id = test_po_id;
END $$;
-- Manual query for the test PO
SELECT
'Purchase Order' as type,
po.order_number as reference,
po.total_amount as amount,
po.currency,
po.status
FROM lats_purchase_orders po
WHERE po.id = '3c6510dc-c025-4a87-9a63-f4083b5b871b'
UNION ALL
SELECT
'Payment' as type,
pop.reference as reference,
pop.amount,
pop.currency,
pop.status
FROM purchase_order_payments pop
WHERE pop.purchase_order_id = '3c6510dc-c025-4a87-9a63-f4083b5b871b'
ORDER BY type DESC;
-- =====================================================
-- SECTION 12: CREATE MISSING PAYMENT FUNCTIONS (IF NEEDED)
-- =====================================================
-- If functions don't exist, uncomment and run these:
/*
-- Function to process payment with balance update
CREATE OR REPLACE FUNCTION process_purchase_order_payment(
purchase_order_id_param UUID,
payment_account_id_param UUID,
amount_param DECIMAL,
currency_param VARCHAR(3),
payment_method_param VARCHAR(50),
payment_method_id_param UUID,
user_id_param UUID,
reference_param TEXT DEFAULT NULL,
notes_param TEXT DEFAULT NULL
) RETURNS BOOLEAN AS $$
DECLARE
account_balance DECIMAL;
BEGIN
-- Get account balance
SELECT balance INTO account_balance
FROM finance_accounts
WHERE id = payment_account_id_param;
-- Check sufficient balance
IF account_balance < amount_param THEN
RAISE EXCEPTION 'Insufficient balance';
END IF;
-- Create payment record
INSERT INTO purchase_order_payments (
purchase_order_id,
payment_account_id,
amount,
currency,
payment_method,
payment_method_id,
reference,
notes,
status,
payment_date,
created_by
) VALUES (
purchase_order_id_param,
payment_account_id_param,
amount_param,
currency_param,
payment_method_param,
payment_method_id_param,
reference_param,
notes_param,
'completed',
NOW(),
user_id_param
);
-- Update account balance
UPDATE finance_accounts
SET balance = balance - amount_param,
updated_at = NOW()
WHERE id = payment_account_id_param;
-- Update PO paid amount
UPDATE lats_purchase_orders
SET paid_amount = COALESCE(paid_amount, 0) + amount_param,
updated_at = NOW()
WHERE id = purchase_order_id_param;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to get payment summary
CREATE OR REPLACE FUNCTION get_purchase_order_payment_summary(
purchase_order_id_param UUID
) RETURNS TABLE (
total_paid DECIMAL,
payment_count INTEGER,
last_payment_date TIMESTAMPTZ,
remaining_amount DECIMAL
) AS $$
BEGIN
RETURN QUERY
SELECT
COALESCE(SUM(pop.amount), 0) as total_paid,
COUNT(pop.id)::INTEGER as payment_count,
MAX(pop.payment_date) as last_payment_date,
po.total_amount - COALESCE(SUM(pop.amount), 0) as remaining_amount
FROM lats_purchase_orders po
LEFT JOIN purchase_order_payments pop ON po.id = pop.purchase_order_id
AND pop.status = 'completed'
WHERE po.id = purchase_order_id_param
GROUP BY po.total_amount;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Grant permissions
GRANT EXECUTE ON FUNCTION process_purchase_order_payment TO authenticated;
GRANT EXECUTE ON FUNCTION get_purchase_order_payment_summary TO authenticated;
*/
-- =====================================================
-- SUMMARY REPORT
-- =====================================================
SELECT
(SELECT COUNT(*) FROM purchase_order_payments) as total_payments,
(SELECT COUNT(DISTINCT purchase_order_id) FROM purchase_order_payments) as pos_with_payments,
(SELECT SUM(amount) FROM purchase_order_payments WHERE status = 'completed') as total_amount_paid,
(SELECT COUNT(*) FROM purchase_order_payments WHERE status = 'pending') as pending_payments,
(SELECT COUNT(*) FROM purchase_order_payments WHERE status = 'failed') as failed_payments,
CASE
WHEN EXISTS (SELECT 1 FROM information_schema.routines WHERE routine_name = 'process_purchase_order_payment')
THEN 'Payment system operational ✅'
ELSE 'Payment functions missing ⚠️'
END as system_status;