| Type | Name | Status | PK | FK | Columns | Index | Script | Diff Script |
|---|---|---|---|---|---|---|---|---|
| Table | bill_account_approval_levels | Missing in Target |
|
|||||
| Table | bill_workflow | Missing in Target |
|
|||||
| Table | temp_bill_next_status | Missing in Target |
|
|||||
| Table | vendor_advance_refunds | Missing in Target |
|
|||||
| Table | vendor_advance_settlement_ids | Missing in Target |
|
|||||
| Table | vendor_advance_settlements | Missing in Target |
|
|||||
| Table | v_organization_id | Missing in Target |
|
|||||
| Table | user_roles | Missing in Target |
|
|||||
| Table | schema_versions | Missing in Target |
|
|||||
| Table | vendor_note_statuses | Missing in Target |
|
|||||
| Table | vendor_categories | Missing in Target |
|
|||||
| Table | vendor_note_work_flows | Missing in Target |
|
|||||
| Table | bill_header_ids | Missing in Target |
|
|||||
| Table | bill_status_company_configs | Missing in Target |
|
|||||
| Table | vendor_advances | Missing in Target |
|
|||||
| Table | vendor_note_details | Missing in Target |
|
|||||
| Table | bill_payment_details | Missing in Target |
|
|||||
| Table | cities | Missing in Target |
|
|||||
| Table | draft_bill_details | Missing in Target |
|
|||||
| Table | companies | Missing in Target |
|
|||||
| Table | countries | Missing in Target |
|
|||||
| Table | gate_pass_details | Missing in Target |
|
|||||
| Table | organizations | Missing in Target |
|
|||||
| Table | schedule_execution_logs | Missing in Target |
|
|||||
| Table | roles | Missing in Target |
|
|||||
| Table | vendor_contacts | Missing in Target |
|
|||||
| Table | vendor_bank_accounts | Missing in Target |
|
|||||
| Table | banks | Missing in Target |
|
|||||
| Table | bill_details | Missing in Target |
|
|||||
| Table | bill_approval_user_company | Missing in Target |
|
|||||
| Table | bill_approval_logs | Missing in Target |
|
|||||
| Table | bill_approval_user_account | Missing in Target |
|
|||||
| Table | addresses | Missing in Target |
|
|||||
| Table | bill_payment_headers | Missing in Target |
|
|||||
| Table | bill_headers | Missing in Target |
|
|||||
| Table | bill_approval_issue_logs | Missing in Target |
|
|||||
| Table | __EFMigrationsHistory | Missing in Target |
|
|||||
| Table | bank_accounts | Missing in Target |
|
|||||
| Table | bill_payment_header_ids | Missing in Target |
|
|||||
| Table | bill_statuses | Missing in Target |
|
|||||
| Table | draft_bill_headers | Missing in Target |
|
|||||
| Table | gate_pass_statuses | Missing in Target |
|
|||||
| Table | draft_bill_header_ids | Missing in Target |
|
|||||
| Table | contacts | Missing in Target |
|
|||||
| Table | bill_types | Missing in Target |
|
|||||
| Table | payment_orders | Missing in Target |
|
|||||
| Table | warehouses | Missing in Target |
|
|||||
| Table | gate_pass_headers | Missing in Target |
|
|||||
| Table | payment_statuses | Missing in Target |
|
|||||
| Table | vendor_advance_ids | Missing in Target |
|
|||||
| Table | tds_statuses | Missing in Target |
|
|||||
| Table | upis | Missing in Target |
|
|||||
| Table | recurrence_bill_schedule_details | Missing in Target |
|
|||||
| Table | procedure_logs | Missing in Target |
|
|||||
| Table | states | Missing in Target |
|
|||||
| Table | users | Missing in Target |
|
|||||
| Table | vendor_default_accounts | Missing in Target |
|
|||||
| Table | vendor_note_headers | Missing in Target |
|
|||||
| Table | vendor_upis | Missing in Target |
|
|||||
| Table | vendors | Missing in Target |
|
|||||
| Table | recurring_bill_schedules | Missing in Target |
|
|||||
| Function | checkin_service_provider | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.checkin_service_provider(p_apartment_id uuid, p_pin text, p_created_by uuid)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_service_provider_id int;
7
v_log_id int;
8
BEGIN
9
-- Step 1: Validate service provider by apartmentId and pin
10
SELECT id INTO v_service_provider_id
11
FROM public.service_providers
12
WHERE apartment_id = p_apartment_id
13
AND pin = p_pin
14
AND is_deleted = false
15
LIMIT 1;
16
17
IF v_service_provider_id IS NULL THEN
18
RAISE EXCEPTION 'Service provider not found for apartment % with pin %', p_apartment_id, p_pin
19
USING ERRCODE = 'no_data_found';
20
END IF;
21
22
-- Step 2: Insert service provider log (CheckedIn)
23
INSERT INTO public.service_provider_logs (
24
service_provider_id,
25
current_status_id,
26
entry_time,
27
created_on_utc,
28
created_by
29
)
30
VALUES (
31
v_service_provider_id,
32
1, -- CheckedIn
33
now(),
34
now(),
35
p_created_by
36
)
37
RETURNING id INTO v_log_id;
38
39
RETURN v_log_id;
40
END;
41
$function$
|
|||||
| Function | bulk_delete_vendors_with_flag | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.bulk_delete_vendors_with_flag(p_vendor_ids uuid[], p_modified_by uuid, p_deleted_on_utc timestamp without time zone, p_is_all_possible_delete boolean)
2
RETURNS TABLE(id integer, vendor_id uuid, status text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_id UUID;
7
v_blocked_count INT;
8
row_index INT := 1;
9
BEGIN
10
FOREACH v_id IN ARRAY p_vendor_ids LOOP
11
-- 1. If `isAllPossibleDelete` is true, proceed with deletion for eligible vendors and skip blocked vendors
12
IF p_is_all_possible_delete THEN
13
-- 1.1. Skip if vendor already deleted
14
IF NOT EXISTS (
15
SELECT 1 FROM public.vendors v WHERE v.id = v_id AND v.is_deleted = false
16
) THEN
17
id := row_index;
18
bulk_delete_vendors_with_flag.vendor_id := v_id;
19
bulk_delete_vendors_with_flag.status := 'Skipped - Vendor Not Found or Already Deleted';
20
row_index := row_index + 1;
21
RETURN NEXT;
22
CONTINUE;
23
END IF;
24
25
-- 1.2. Check if any financial bills exist
26
SELECT COUNT(*) INTO v_blocked_count
27
FROM (
28
SELECT 1 FROM public.bill_headers bh
29
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id IN (3, 4)
30
UNION ALL
31
SELECT 1 FROM public.draft_bill_headers dbh
32
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id IN (3, 4)
33
) AS financial_bills;
34
35
IF v_blocked_count > 0 THEN
36
-- If blocked, skip deletion and mark the status as blocked
37
id := row_index;
38
bulk_delete_vendors_with_flag.vendor_id := v_id;
39
bulk_delete_vendors_with_flag.status := 'Blocked - Financial Bills Exist (Approved/Partially Paid/Paid)';
40
row_index := row_index + 1;
41
RETURN NEXT;
42
CONTINUE;
43
END IF;
44
45
-- 1.3. Soft delete vendor and associated records
46
UPDATE public.vendors v
47
SET is_deleted = true,
48
modified_by = p_modified_by,
49
deleted_on_utc = p_deleted_on_utc
50
WHERE v.id = v_id;
51
52
-- 1.4. Return success
53
id := row_index;
54
bulk_delete_vendors_with_flag.vendor_id := v_id;
55
bulk_delete_vendors_with_flag.status := 'Deleted';
56
row_index := row_index + 1;
57
RETURN NEXT;
58
59
ELSE
60
-- 2. If `isAllPossibleDelete` is false, just return the status without deletion
61
-- 2.1. Skip if vendor already deleted
62
IF NOT EXISTS (
63
SELECT 1 FROM public.vendors v WHERE v.id = v_id AND v.is_deleted = false
64
) THEN
65
id := row_index;
66
bulk_delete_vendors_with_flag.vendor_id := v_id;
67
bulk_delete_vendors_with_flag.status := 'Skipped - Vendor Not Found or Already Deleted';
68
row_index := row_index + 1;
69
RETURN NEXT;
70
CONTINUE;
71
END IF;
72
73
-- 2.2. Check if any financial bills exist
74
SELECT COUNT(*) INTO v_blocked_count
75
FROM (
76
SELECT 1 FROM public.bill_headers bh
77
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id IN (3, 4)
78
UNION ALL
79
SELECT 1 FROM public.draft_bill_headers dbh
80
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id IN (3, 4)
81
) AS financial_bills;
82
83
IF v_blocked_count > 0 THEN
84
-- If blocked, return blocked status
85
id := row_index;
86
bulk_delete_vendors_with_flag.vendor_id := v_id;
87
bulk_delete_vendors_with_flag.status := 'Blocked - Financial Bills Exist (Approved/Partially Paid/Paid)';
88
row_index := row_index + 1;
89
RETURN NEXT;
90
CONTINUE;
91
END IF;
92
93
-- 2.3. If no issues, return that vendor is eligible for deletion
94
id := row_index;
95
bulk_delete_vendors_with_flag.vendor_id := v_id;
96
bulk_delete_vendors_with_flag.status := 'Eligible for Deletion';
97
row_index := row_index + 1;
98
RETURN NEXT;
99
END IF;
100
END LOOP;
101
END;
102
$function$
|
|||||
| Function | create_vendor_advance_settlements | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_vendor_advance_settlements(p_company_id uuid, p_vendor_id uuid, p_bill_payment_header_id uuid, p_created_by uuid, p_settlements jsonb)
2
RETURNS TABLE(id uuid, advance_settlement_number text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_settlement_number text;
7
v_row jsonb;
8
v_id uuid;
9
BEGIN
10
FOR v_row IN SELECT * FROM jsonb_array_elements(p_settlements)
11
LOOP
12
v_settlement_number := public.get_new_vendor_advance_settlement_number(
13
p_company_id,
14
COALESCE((v_row->>'settled_date')::date, CURRENT_DATE)
15
);
16
v_id := (v_row->>'id')::uuid;
17
18
INSERT INTO vendor_advance_settlements
19
(
20
id,
21
company_id,
22
vendor_id,
23
advance_id,
24
bill_id,
25
apply_amount,
26
applied_in_payment_id,
27
settled_date,
28
note,
29
created_on_utc,
30
created_by,
31
is_deleted,
32
advance_settlement_number
33
)
34
VALUES
35
(
36
v_id,
37
p_company_id,
38
p_vendor_id,
39
(v_row->>'advance_id')::uuid,
40
(v_row->>'bill_id')::uuid,
41
(v_row->>'apply_amount')::numeric,
42
p_bill_payment_header_id,
43
COALESCE((v_row->>'settled_date')::timestamp, now()),
44
NULLIF(v_row->>'note',''),
45
now(),
46
p_created_by,
47
false,
48
v_settlement_number
49
);
50
51
UPDATE vendor_advances va
52
SET utilized_amount = utilized_amount + (v_row->>'apply_amount')::numeric,
53
modified_by = p_created_by,
54
modified_on_utc = now()
55
WHERE va.id = (v_row->>'advance_id')::uuid
56
AND va.company_id = p_company_id
57
AND va.vendor_id = p_vendor_id
58
AND va.is_deleted = false;
59
60
-- Assign to output variables, then RETURN NEXT;
61
id := v_id;
62
advance_settlement_number := v_settlement_number;
63
RETURN NEXT;
64
END LOOP;
65
END;
66
$function$
|
|||||
| Function | create_user | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_user(p_user_id uuid, p_email text, p_phone_number text, p_first_name text, p_last_name text, p_created_by uuid, p_company_id uuid)
2
RETURNS uuid
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_existing_user_id UUID; -- Variable to store the user ID
7
BEGIN
8
-- Step 1: Check if the user already exists based on email
9
SELECT id INTO v_existing_user_id
10
FROM public.users
11
WHERE email = p_email;
12
13
-- If user already exists, return the existing user_id
14
IF v_existing_user_id IS NOT NULL THEN
15
RETURN v_existing_user_id;
16
END IF;
17
18
-- Step 2: Insert into users table
19
INSERT INTO public.users (
20
id,
21
email,
22
phone_number,
23
first_name,
24
last_name,
25
password_hash,
26
created_on_utc,
27
created_by,
28
company_id
29
) VALUES (
30
p_user_id, -- Generate a new UUID for the user ID
31
p_email, -- User email
32
p_phone_number, -- User phone number
33
p_first_name, -- User first name
34
p_last_name, -- Default empty last name
35
'', -- Default empty password hash
36
NOW(), -- Current timestamp
37
p_created_by, -- Created by
38
p_company_id -- Link to the provided company
39
);
40
41
-- Return the new user ID
42
RETURN p_user_id;
43
END;
44
$function$
|
|||||
| Function | get_all_bill_company_approvals | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_bill_company_approvals(p_company_id uuid)
2
RETURNS TABLE(id integer, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
MIN(ba.id) AS id,
9
ba.status_id,
10
ba.user_id,
11
ba.approval_level,
12
bw.approval_level AS required_approval_level
13
FROM bill_approval_user_company ba
14
JOIN bill_workflow bw
15
ON ba.company_id = bw.company_id
16
WHERE ba.company_id = p_company_id
17
--AND ba.status = 2
18
AND ba.is_deleted = FALSE
19
AND bw.is_deleted = FALSE
20
AND bw.is_enabled = TRUE
21
AND bw.status = 2
22
GROUP BY ba.status_id, ba.user_id, ba.approval_level, bw.approval_level
23
ORDER BY ba.status_id, ba.user_id;
24
END;
25
$function$
|
|||||
| Function | get_all_unit_names_by_apartment_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_unit_names_by_apartment_id(p_apartment_id uuid)
2
RETURNS TABLE(id integer, unit_id integer, unit_name text, building_name text, floor_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
CAST(ROW_NUMBER() OVER (ORDER BY u.id) AS INT) AS id,
9
u.id AS unit_id,
10
u."name" AS unit_name,
11
b."name" AS building_name,
12
f."name" AS floor_name
13
FROM units u
14
INNER JOIN buildings b ON u.building_id = b.id
15
INNER JOIN floors f ON u.floor_id = f.id
16
WHERE u.apartment_id = p_apartment_id
17
AND u.is_deleted = false
18
AND b.is_deleted = false
19
AND f.is_deleted = false;
20
END;
21
$function$
|
|||||
| Function | get_all_schedule_bills | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_schedule_bills(p_company_id uuid)
2
RETURNS TABLE(schedule_id uuid, bill_header_id uuid, company_id uuid, frequency_cycle text, schedule_status text, starts_from timestamp without time zone, end_date timestamp without time zone, created_by uuid, bill_number text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
rbs.id AS schedule_id,
9
rbs.bill_header_id,
10
rbs.company_id,
11
rbs.frequency_cycle,
12
rbs.schedule_status,
13
rbs.starts_from, -- TIMESTAMP type
14
rbs.end_date, -- TIMESTAMP type
15
rbs.created_by,
16
bh.bill_number
17
FROM public.recurring_bill_schedules rbs
18
LEFT JOIN public.bill_headers bh ON bh.id = rbs.bill_header_id
19
WHERE rbs.company_id = p_company_id
20
ORDER BY rbs.starts_from DESC;
21
END;
22
$function$
|
|||||
| Function | get_all_bills_from_span | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_bills_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_bill_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(id uuid, bill_voucher text, bill_number text, vendor_name character varying, vendor_id uuid, due_date date, bill_date date, payment_term integer, total_amount numeric, total_paid_amount numeric, currency_id integer, bill_status character varying, bill_status_id integer, payment_status character varying, payment_status_id integer, tds_status character varying, tds_status_id integer, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, created_by_name character varying, modified_by_name character varying, bill_type character varying, bill_type_id integer, tds_amount numeric, has_next_status_approval boolean, next_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
7
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
8
DRAFT_STATUS CONSTANT integer := 1;
9
BEGIN
10
RETURN QUERY
11
WITH bill_data AS (
12
SELECT
13
bh.id,
14
bh.bill_voucher,
15
bh.bill_number,
16
v.name AS vendor_name,
17
bh.vendor_id,
18
bh.due_date,
19
bh.bill_date,
20
bh.payment_term,
21
bh.total_amount,
22
bh.total_paid_amount,
23
bh.currency_id,
24
bs.name AS bill_status,
25
bh.bill_status_id,
26
ps.name AS payment_status, -- ✅ Added
27
bh.payment_status_id, -- ✅ Added
28
ts.name AS tds_status, -- ✅ Added
29
bh.tds_status_id, -- ✅ Added
30
bh.created_by,
31
bh.created_on_utc,
32
bh.modified_by,
33
bh.modified_on_utc,
34
CONCAT(cu.first_name, ' ', cu.last_name)::character varying AS created_by_name,
35
CONCAT(mu.first_name, ' ', mu.last_name)::character varying AS modified_by_name,
36
bt.name AS bill_type,
37
bh.bill_type_id,
38
bh.tds_amount,
39
bh.debit_account_id
40
FROM public.bill_headers bh
41
LEFT JOIN public.vendors v ON v.id = bh.vendor_id
42
LEFT JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
43
LEFT JOIN public.payment_statuses ps ON ps.id = bh.payment_status_id
44
LEFT JOIN public.tds_statuses ts ON ts.id = bh.tds_status_id -- ✅ Added
45
LEFT JOIN public.bill_types bt ON bt.id = bh.bill_type_id
46
LEFT JOIN public.users cu ON cu.id = bh.created_by
47
LEFT JOIN public.users mu ON mu.id = bh.modified_by
48
WHERE bh.company_id = p_company_id
49
AND bh.is_deleted = false
50
AND (p_bill_type_id IS NULL OR p_bill_type_id = 0 OR bh.bill_type_id = p_bill_type_id)
51
AND bh.bill_date BETWEEN v_start_date AND v_end_date
52
53
UNION ALL
54
55
SELECT
56
dbh.id,
57
dbh.bill_voucher,
58
dbh.bill_number,
59
v.name AS vendor_name,
60
dbh.vendor_id,
61
dbh.due_date,
62
dbh.bill_date,
63
dbh.payment_term,
64
dbh.total_amount,
65
0::numeric(18,2) AS total_paid_amount,
66
dbh.currency_id,
67
bs.name AS bill_status,
68
dbh.bill_status_id,
69
ps.name AS payment_status, -- ✅ Added
70
dbh.payment_status_id, -- ✅ Added
71
ts.name AS tds_status, -- ✅ Added
72
dbh.tds_status_id, -- ✅ Added
73
dbh.created_by,
74
dbh.created_on_utc,
75
dbh.modified_by,
76
dbh.modified_on_utc,
77
CONCAT(cu.first_name, ' ', cu.last_name)::character varying AS created_by_name,
78
CONCAT(mu.first_name, ' ', mu.last_name)::character varying AS modified_by_name,
79
bt.name AS bill_type,
80
dbh.bill_type_id,
81
dbh.tds_amount,
82
dbh.debit_account_id
83
FROM public.draft_bill_headers dbh
84
LEFT JOIN public.vendors v ON v.id = dbh.vendor_id
85
LEFT JOIN public.bill_statuses bs ON bs.id = dbh.bill_status_id
86
LEFT JOIN public.payment_statuses ps ON ps.id = dbh.payment_status_id
87
LEFT JOIN public.tds_statuses ts ON ts.id = dbh.tds_status_id -- ✅ Added
88
LEFT JOIN public.bill_types bt ON bt.id = dbh.bill_type_id
89
LEFT JOIN public.users cu ON cu.id = dbh.created_by
90
LEFT JOIN public.users mu ON mu.id = dbh.modified_by
91
WHERE dbh.company_id = p_company_id
92
AND dbh.is_deleted = false
93
AND (p_bill_type_id IS NULL OR p_bill_type_id = 0 OR dbh.bill_type_id = p_bill_type_id)
94
AND dbh.bill_date BETWEEN v_start_date AND v_end_date
95
),
96
enriched AS (
97
SELECT
98
bd.*,
99
COALESCE(
100
(
101
SELECT bw.next_status
102
FROM bill_workflow bw
103
WHERE bw.company_id = p_company_id
104
AND bw.status = bd.bill_status_id
105
AND bw.is_deleted = false
106
AND (bw.is_enabled IS DISTINCT FROM FALSE)
107
ORDER BY bw.approval_level ASC NULLS LAST
108
LIMIT 1
109
),
110
0
111
) AS next_status_id
112
FROM bill_data bd
113
)
114
SELECT
115
e.id,
116
e.bill_voucher,
117
e.bill_number,
118
e.vendor_name,
119
e.vendor_id,
120
e.due_date::date,
121
e.bill_date::date,
122
e.payment_term,
123
e.total_amount,
124
e.total_paid_amount,
125
e.currency_id,
126
e.bill_status,
127
e.bill_status_id,
128
e.payment_status,
129
e.payment_status_id,
130
e.tds_status,
131
e.tds_status_id,
132
e.created_by,
133
e.created_on_utc,
134
e.modified_by,
135
e.modified_on_utc,
136
e.created_by_name,
137
e.modified_by_name,
138
e.bill_type::character varying,
139
e.bill_type_id,
140
e.tds_amount,
141
CASE
142
WHEN e.bill_status_id = DRAFT_STATUS THEN true
143
WHEN e.next_status_id = 0 THEN false
144
WHEN EXISTS (
145
SELECT 1
146
FROM bill_approval_user_account a
147
WHERE a.account_id = e.debit_account_id
148
AND a.status_id = e.bill_status_id
149
AND a.user_id = p_user_id
150
AND a.is_deleted = false
151
) THEN true
152
WHEN EXISTS (
153
SELECT 1
154
FROM bill_approval_user_company c
155
WHERE c.company_id = p_company_id
156
AND c.status_id = e.bill_status_id
157
AND c.user_id = p_user_id
158
AND c.is_deleted = false
159
) THEN true
160
ELSE false
161
END AS has_next_status_approval,
162
e.next_status_id
163
FROM enriched e;
164
END;
165
$function$
|
|||||
| Function | get_all_vendors | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_vendors(p_company_id uuid)
2
RETURNS TABLE(id uuid, name character varying, contact_name text, gst_in text, mobile_number text, email text, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, billing_address jsonb, contact_count integer)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
v.id,
9
v.name,
10
con.first_name || ' ' || con.last_name AS contact_name,
11
v.gstin AS gst_in,
12
con.mobile_number,
13
con.email,
14
v.created_by,
15
u_created.first_name || ' ' || u_created.last_name AS created_name,
16
v.modified_by,
17
u_modified.first_name || ' ' || u_modified.last_name AS modified_name,
18
v.created_on_utc,
19
v.modified_on_utc,
20
CASE
21
WHEN ba.id IS NOT NULL
22
THEN jsonb_build_object(
23
'AddressLine1', ba.address_line1,
24
'AddressLine2', ba.address_line2,
25
'ZipCode', ba.zip_code,
26
'CountryId', ba.country_id,
27
'StateId', ba.state_id,
28
'CityId', ba.city_id
29
)
30
ELSE NULL
31
END AS billing_address,
32
(SELECT COUNT(*)::integer -- Cast to integer
33
FROM public.vendor_contacts vc_count
34
WHERE vc_count.vendor_id = v.id) AS contact_count -- Contact count subquery
35
FROM
36
public.vendors v
37
JOIN
38
public.vendor_contacts vc ON v.id = vc.vendor_id
39
JOIN
40
public.contacts con ON vc.contact_id = con.id
41
LEFT JOIN
42
public.users u_created ON v.created_by = u_created.id
43
LEFT JOIN
44
public.users u_modified ON v.modified_by = u_modified.id
45
LEFT JOIN
46
public.addresses ba ON v.billing_address_id = ba.id
47
WHERE
48
v.company_id = p_company_id
49
AND v.is_deleted = false
50
AND con.is_primary = true;
51
END;
52
$function$
|
|||||
| Function | get_bill_by_id_json | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_by_id_json(p_bill_header_id uuid)
2
RETURNS json
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_bill_status text;
7
v_bill_json json;
8
BEGIN
9
-- Get bill status name
10
SELECT bs.name INTO v_bill_status
11
FROM bill_statuses bs
12
JOIN bill_headers bh ON bh.bill_status_id = bs.id
13
WHERE bh.id = p_bill_header_id;
14
15
-- Construct the JSON output
16
SELECT json_build_object(
17
'id', bh.id,
18
'billNumber', bh.bill_number,
19
'billDate', bh.bill_date,
20
'paymentTerm', bh.payment_term,
21
'billStatus', v_bill_status,
22
'billStatusId', bh.bill_status_id,
23
'vendor', json_build_object(
24
'id', v.id,
25
'name', v.name,
26
'email', c.email,
27
'mobileNumber', c.mobile_number,
28
'phoneNumber', c.phone_number,
29
'gstIn', v.gstin,
30
'shortName', v.short_name,
31
'pan', v.pan,
32
'tan', v.tan
33
),
34
'debitAccountId', bh.debit_account_id,
35
'currencyId', bh.currency_id,
36
'dueDate', bh.due_date,
37
'taxableAmount', bh.taxable_amount,
38
'fees', bh.fees,
39
'cgstAmount', bh.cgst_amount,
40
'sgstAmount', bh.sgst_amount,
41
'igstAmount', bh.igst_amount,
42
'totalAmount', bh.total_amount,
43
'discount', bh.discount,
44
'note', bh.note,
45
'roundOff', bh.round_off,
46
'sourceWarehouse', NULL,
47
'sourceWarehouseId', bh.source_warehouse_id,
48
'destinationWarehouseId', bh.destination_warehouse_id,
49
'poNo', bh.po_no,
50
'poDate', bh.po_date,
51
'lines', (
52
SELECT json_agg(json_build_object(
53
'id', bl.id,
54
'productId', bl.product_id,
55
'price', bl.price,
56
'quantity', bl.quantity,
57
'fees', bl.fees,
58
'discount', bl.discount,
59
'taxableAmount', bl.taxable_amount,
60
'sgstAmount', bl.sgst_amount,
61
'cgstAmount', bl.cgst_amount,
62
'igstAmount', bl.igst_amount,
63
'totalAmount', bl.total_amount,
64
'serialNumber', bl.serial_number
65
))
66
FROM bill_details bl
67
WHERE bl.bill_header_id = p_bill_header_id
68
),
69
'billTypeId', bh.bill_type_id
70
) INTO v_bill_json
71
FROM bill_headers bh
72
LEFT JOIN vendors v ON v.id = bh.vendor_id
73
LEFT JOIN vendor_contacts vc ON vc.vendor_id = v.id
74
LEFT JOIN contacts c ON c.id = vc.contact_id
75
WHERE bh.id = p_bill_header_id;
76
77
-- Return the JSON object
78
RETURN v_bill_json;
79
END;
80
$function$
|
|||||
| Function | get_new_bill_voucher | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_new_bill_voucher(p_company_id uuid, p_date date)
2
RETURNS character varying
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_finance_year integer;
7
new_bill_id integer;
8
p_prefix varchar;
9
bill_voucher varchar;
10
BEGIN
11
-- Determine the start year of the financial year based on p_date
12
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
13
v_finance_year := EXTRACT(YEAR FROM p_date);
14
ELSE
15
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
16
END IF;
17
18
-- Try to update existing row or insert a new one if not found
19
WITH x AS (
20
UPDATE public.bill_header_ids
21
SET last_bill_id = last_bill_id + 1
22
WHERE company_id = p_company_id AND fin_year = v_finance_year
23
RETURNING last_bill_id, bill_prefix
24
),
25
insert_x AS (
26
INSERT INTO public.bill_header_ids (
27
company_id, fin_year, bill_prefix, last_bill_id, bill_length
28
)
29
SELECT p_company_id, v_finance_year, 'BN', 1, 8
30
WHERE NOT EXISTS (SELECT 1 FROM x)
31
RETURNING last_bill_id, bill_prefix
32
)
33
SELECT
34
COALESCE((SELECT last_bill_id FROM x LIMIT 1), (SELECT last_bill_id FROM insert_x LIMIT 1)),
35
COALESCE((SELECT bill_prefix FROM x LIMIT 1), (SELECT bill_prefix FROM insert_x LIMIT 1))
36
INTO new_bill_id, p_prefix;
37
38
-- Construct the bill voucher number
39
bill_voucher := p_prefix || LPAD(new_bill_id::text, 4, '0');
40
41
RETURN bill_voucher;
42
END;
43
$function$
|
|||||
| Function | get_vendor_advances_by_vendor | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_advances_by_vendor(p_company_id uuid, p_vendor_id uuid, only_unutilized_advances boolean DEFAULT false)
2
RETURNS TABLE(id uuid, vendor_id uuid, vendor_name character varying, advance_number text, paid_date timestamp without time zone, amount numeric, utilized_amount numeric, mode_of_payment text, reference text, description text, currency_id integer)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
va.id,
7
va.vendor_id,
8
v.name, -- Add vendor name
9
va.advance_number,
10
va.paid_date,
11
va.amount,
12
va.utilized_amount,
13
va.mode_of_payment,
14
va.reference,
15
va.description,
16
va.currency_id
17
FROM public.vendor_advances va
18
LEFT JOIN public.vendors v ON va.vendor_id = v.id -- Join here to get the name
19
WHERE va.company_id = p_company_id
20
AND va.vendor_id = p_vendor_id
21
AND va.is_deleted = false
22
AND v.is_deleted = false
23
AND (
24
-- If only_unutilized_advances = true → filter unsettled
25
(only_unutilized_advances = true AND va.amount > va.utilized_amount)
26
-- If false or null → return all
27
OR (only_unutilized_advances IS DISTINCT FROM true)
28
);
29
$function$
|
|||||
| Function | get_payment_distributions_for_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_payment_distributions_for_bill(p_bill_header_id uuid)
2
RETURNS TABLE(payment_detail_id uuid, bill_payment_header_id uuid, payment_number text, bill_header_id uuid, paid_amount numeric, tds_amount numeric, payment_is_deleted boolean, payment_created_by uuid, payment_created_by_name text, payment_created_on timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
bpd.id AS payment_detail_id,
9
bpd.bill_payment_header_id,
10
bph.payment_number,
11
bpd.bill_header_id,
12
bpd.paid_amount,
13
bpd.tds_amount,
14
bpd.is_deleted AS payment_is_deleted,
15
bpd.created_by AS payment_created_by,
16
CONCAT(u.first_name, ' ', u.last_name) AS payment_created_by_name,
17
bpd.created_on_utc AS payment_created_on
18
FROM
19
public.bill_payment_details bpd
20
JOIN public.bill_payment_headers bph ON bph.id = bpd.bill_payment_header_id
21
LEFT JOIN public.users u ON u.id = bpd.created_by
22
WHERE
23
bpd.bill_header_id = p_bill_header_id
24
AND bpd.is_deleted = false
25
ORDER BY
26
bpd.created_on_utc ASC;
27
END;
28
$function$
|
|||||
| Function | get_vendor_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_details(p_vendor_id uuid)
2
RETURNS TABLE(id uuid, name character varying, email text, mobile_number text, phone_number text, gstin text, short_name text, pan text, tan text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
v.id,
9
v.name,
10
ct.email,
11
ct.mobile_number,
12
ct.phone_number,
13
v.gstin,
14
v.short_name,
15
v.pan,
16
v.tan
17
FROM vendors v
18
LEFT JOIN vendor_contacts vc ON vc.vendor_id = v.id
19
LEFT JOIN contacts ct ON vc.contact_id = ct.id
20
WHERE v.id = p_vendor_id
21
ORDER BY ct.is_primary DESC
22
LIMIT 1;
23
END;
24
$function$
|
|||||
| Function | grant_full_schema_access | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.grant_full_schema_access(p_schema text, p_user text)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
obj RECORD;
7
BEGIN
8
-- Grant on tables
9
FOR obj IN
10
SELECT table_name
11
FROM information_schema.tables
12
WHERE table_schema = p_schema
13
AND table_type = 'BASE TABLE'
14
LOOP
15
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.%I TO %I;', p_schema, obj.table_name, p_user);
16
RAISE NOTICE 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %.% TO %;', p_schema, obj.table_name, p_user;
17
END LOOP;
18
19
-- Grant on sequences (USAGE + SELECT + UPDATE: full coverage)
20
FOR obj IN
21
SELECT c.relname AS sequence_name
22
FROM pg_class c
23
JOIN pg_namespace n ON n.oid = c.relnamespace
24
WHERE c.relkind = 'S'
25
AND n.nspname = p_schema
26
LOOP
27
EXECUTE format('GRANT USAGE, SELECT, UPDATE ON SEQUENCE %I.%I TO %I;', p_schema, obj.sequence_name, p_user);
28
RAISE NOTICE 'GRANT USAGE, SELECT, UPDATE ON SEQUENCE %.% TO %;', p_schema, obj.sequence_name, p_user;
29
END LOOP;
30
31
-- Grant on all functions (handles all argument types)
32
FOR obj IN
33
SELECT
34
p.proname AS function_name,
35
pg_get_function_identity_arguments(p.oid) AS args
36
FROM
37
pg_proc p
38
JOIN pg_namespace n ON p.pronamespace = n.oid
39
WHERE
40
n.nspname = p_schema
41
AND p.prokind = 'f' -- f = function
42
LOOP
43
EXECUTE format(
44
'GRANT EXECUTE ON FUNCTION %I.%I(%s) TO %I;',
45
p_schema, obj.function_name, obj.args, p_user
46
);
47
RAISE NOTICE 'GRANT EXECUTE ON FUNCTION %.%(%) TO %;', p_schema, obj.function_name, obj.args, p_user;
48
END LOOP;
49
50
-- Grant on all procedures (Postgres 11+)
51
FOR obj IN
52
SELECT
53
p.proname AS procedure_name,
54
pg_get_function_identity_arguments(p.oid) AS args
55
FROM
56
pg_proc p
57
JOIN pg_namespace n ON p.pronamespace = n.oid
58
WHERE
59
n.nspname = p_schema
60
AND p.prokind = 'p' -- p = procedure
61
LOOP
62
EXECUTE format(
63
'GRANT EXECUTE ON PROCEDURE %I.%I(%s) TO %I;',
64
p_schema, obj.procedure_name, obj.args, p_user
65
);
66
RAISE NOTICE 'GRANT EXECUTE ON PROCEDURE %.%(%) TO %;', p_schema, obj.procedure_name, obj.args, p_user;
67
END LOOP;
68
69
END;
70
$function$
|
|||||
| Function | get_vendor_default_accounts | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_default_accounts(p_company_id uuid)
2
RETURNS TABLE(id integer, vendor_id uuid, account_id uuid)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
vda.id,
7
vda.vendor_id,
8
vda.account_id
9
FROM vendor_default_accounts vda
10
WHERE vda.company_id = p_company_id
11
AND vda.is_deleted = false;
12
$function$
|
|||||
| Function | update_bill_next_status_test | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_bill_next_status_test(p_company_id uuid, p_bill_status_id integer, p_bill_ids uuid[], p_modified_by uuid)
2
RETURNS TABLE(bill_id uuid, status text, error text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_draft_bill RECORD;
7
v_next_status integer;
8
v_account_id uuid;
9
v_user_level INTEGER;
10
v_required_levels INTEGER;
11
v_current_level INTEGER;
12
v_is_account_level BOOLEAN := false;
13
14
APPROVED_STATUS CONSTANT INTEGER := 3;
15
SENDING_FOR_APPROVAL_STATUS CONSTANT INTEGER := 2;
16
BEGIN
17
CREATE TEMP TABLE temp_approval_results (
18
bill_id uuid,
19
status text,
20
error text
21
) ON COMMIT DROP;
22
23
FOR v_draft_bill IN
24
SELECT ph.id, ph.bill_status_id, ph.debit_account_id, ph.current_approval_level
25
FROM public.draft_bill_headers ph
26
WHERE id = ANY(p_bill_ids)
27
LOOP
28
BEGIN
29
RAISE NOTICE 'Processing bill ID: %', v_draft_bill.id;
30
v_account_id := v_draft_bill.debit_account_id;
31
v_next_status := p_bill_status_id;
32
v_current_level := COALESCE(v_draft_bill.current_approval_level, 1);
33
34
-- Direct status update for SENDING_FOR_APPROVAL_STATUS
35
IF p_bill_status_id = SENDING_FOR_APPROVAL_STATUS THEN
36
WITH updated AS (
37
UPDATE public.draft_bill_headers
38
SET bill_status_id = p_bill_status_id,
39
modified_by = p_modified_by,
40
modified_on_utc = now(),
41
current_approval_level = 1
42
WHERE id = v_draft_bill.id
43
RETURNING id
44
)
45
INSERT INTO temp_approval_results
46
SELECT
47
u.id,
48
'pending',
49
NULL
50
FROM updated u;
51
52
CONTINUE;
53
END IF;
54
55
-- Check account-level approval
56
SELECT approval_level INTO v_user_level
57
FROM public.bill_approval_user_account
58
WHERE company_id = p_company_id
59
AND account_id = v_account_id
60
AND status_id = v_next_status
61
AND user_id = p_modified_by
62
AND is_deleted = false
63
LIMIT 1;
64
65
IF FOUND THEN
66
v_is_account_level := true;
67
ELSE
68
-- Check company-level approval
69
SELECT approval_level INTO v_user_level
70
FROM public.bill_approval_user_company
71
WHERE company_id = p_company_id
72
AND status_id = v_next_status
73
AND user_id = p_modified_by
74
AND is_deleted = false
75
LIMIT 1;
76
77
IF NOT FOUND THEN
78
RAISE NOTICE 'User % is not authorized for any approval level.', p_modified_by;
79
INSERT INTO temp_approval_results VALUES (
80
v_draft_bill.id, 'rejected', 'User not authorized for approval'
81
);
82
CONTINUE;
83
END IF;
84
END IF;
85
86
-- Get required approval levels
87
SELECT approval_level_required INTO v_required_levels
88
FROM public.bill_account_approval_levels
89
WHERE company_id = p_company_id
90
AND account_id = v_account_id
91
AND status_id = v_next_status
92
LIMIT 1;
93
94
v_required_levels := COALESCE(v_required_levels, 1);
95
96
RAISE NOTICE 'User level: %, Required: %, Current: %', v_user_level, v_required_levels, v_current_level;
97
98
IF v_user_level <> v_current_level THEN
99
INSERT INTO temp_approval_results VALUES (
100
v_draft_bill.id, 'rejected', FORMAT('Approval level mismatch. Expected: %, You have: %', v_current_level, v_user_level)
101
);
102
CONTINUE;
103
END IF;
104
105
-- If not final level, just increment approval level
106
IF v_current_level < v_required_levels THEN
107
RAISE NOTICE 'Incrementing approval level...';
108
UPDATE draft_bill_headers
109
SET current_approval_level = v_current_level + 1,
110
modified_by = p_modified_by,
111
modified_on_utc = now()
112
WHERE id = v_draft_bill.id;
113
114
INSERT INTO temp_approval_results VALUES (
115
v_draft_bill.id, 'pending', FORMAT('Moved to level %', v_current_level + 1)
116
);
117
ELSE
118
-- Final approval and status change
119
RAISE NOTICE 'Final approval and updating status...';
120
UPDATE draft_bill_headers
121
SET bill_status_id = v_next_status,
122
current_approval_level = NULL,
123
modified_by = p_modified_by,
124
modified_on_utc = now()
125
WHERE id = v_draft_bill.id;
126
127
CALL public.log_bill_approval(
128
v_draft_bill.id,
129
v_next_status,
130
p_modified_by
131
);
132
133
IF v_next_status = APPROVED_STATUS THEN
134
CALL public.transfer_draft_to_bill(
135
v_draft_bill.id,
136
p_modified_by
137
);
138
END IF;
139
140
INSERT INTO temp_approval_results VALUES (
141
v_draft_bill.id, 'approved', NULL
142
);
143
END IF;
144
145
EXCEPTION WHEN OTHERS THEN
146
RAISE NOTICE 'Error processing bill ID %: %', v_draft_bill.id, SQLERRM;
147
INSERT INTO temp_approval_results VALUES (
148
v_draft_bill.id, 'error', SQLERRM
149
);
150
END;
151
END LOOP;
152
153
RETURN QUERY SELECT * FROM temp_approval_results;
154
END;
155
$function$
|
|||||
| Function | upsert_vendor_default_account | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_vendor_default_account(p_updates jsonb)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
update_record JSONB;
7
v_vendor_id UUID;
8
v_account_id UUID;
9
v_company_id UUID;
10
v_user_id UUID;
11
BEGIN
12
FOR update_record IN SELECT * FROM jsonb_array_elements(p_updates)
13
LOOP
14
v_vendor_id := update_record->>'VendorId';
15
v_account_id := update_record->>'AccountId';
16
v_company_id := update_record->>'CompanyId';
17
v_user_id := update_record->>'UserId';
18
19
INSERT INTO vendor_default_accounts (
20
vendor_id, account_id, company_id, created_by, created_on_utc, is_deleted
21
)
22
VALUES (
23
v_vendor_id, v_account_id, v_company_id, v_user_id, now(), false
24
)
25
ON CONFLICT (vendor_id, company_id) DO UPDATE
26
SET
27
account_id = EXCLUDED.account_id,
28
modified_by = EXCLUDED.created_by,
29
modified_on_utc = now(),
30
is_deleted = false;
31
END LOOP;
32
END;
33
$function$
|
|||||
| Function | bulk_delete_vendors | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.bulk_delete_vendors(p_vendor_ids uuid[], p_modified_by uuid, p_deleted_on_utc timestamp without time zone)
2
RETURNS TABLE(id integer, vendor_id uuid, status text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_id UUID;
7
v_blocked_count INT;
8
row_index INT := 1;
9
BEGIN
10
FOREACH v_id IN ARRAY p_vendor_ids LOOP
11
-- 1. Skip if vendor already deleted
12
IF NOT EXISTS (
13
SELECT 1 FROM public.vendors v WHERE v.id = v_id AND v.is_deleted = false
14
) THEN
15
id := row_index;
16
bulk_delete_vendors.vendor_id := v_id;
17
bulk_delete_vendors.status := 'Skipped - Vendor Not Found or Already Deleted';
18
row_index := row_index + 1;
19
RETURN NEXT;
20
CONTINUE;
21
END IF;
22
23
-- 2. Check if any financial bills exist
24
SELECT COUNT(*) INTO v_blocked_count
25
FROM (
26
SELECT 1 FROM public.bill_headers bh
27
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id IN (3, 4, 5)
28
UNION ALL
29
SELECT 1 FROM public.draft_bill_headers dbh
30
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id IN (3, 4, 5)
31
) AS financial_bills;
32
33
IF v_blocked_count > 0 THEN
34
id := row_index;
35
bulk_delete_vendors.vendor_id := v_id;
36
bulk_delete_vendors.status := 'Blocked - Financial Bills Exist (Approved/Partially Paid/Paid)';
37
row_index := row_index + 1;
38
RETURN NEXT;
39
CONTINUE;
40
END IF;
41
42
-- 3. Soft delete eligible draft_bill_details
43
UPDATE public.draft_bill_details dbd
44
SET is_deleted = true,
45
deleted_on_utc = p_deleted_on_utc
46
WHERE dbd.bill_header_id IN (
47
SELECT dbh.id FROM public.draft_bill_headers dbh
48
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id NOT IN (3, 4, 5)
49
);
50
51
-- 4. Soft delete eligible draft_bill_headers
52
UPDATE public.draft_bill_headers dbh
53
SET is_deleted = true,
54
modified_by = p_modified_by,
55
deleted_on_utc = p_deleted_on_utc
56
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id NOT IN (3, 4, 5);
57
58
-- 5. Soft delete eligible bill_details
59
UPDATE public.bill_details bd
60
SET is_deleted = true,
61
deleted_on_utc = p_deleted_on_utc
62
WHERE bd.bill_header_id IN (
63
SELECT bh.id FROM public.bill_headers bh
64
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id NOT IN (3, 4, 5)
65
);
66
67
-- 6. Soft delete eligible bill_headers
68
UPDATE public.bill_headers bh
69
SET is_deleted = true,
70
modified_by = p_modified_by,
71
deleted_on_utc = p_deleted_on_utc
72
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id NOT IN (3, 4, 5);
73
74
-- 7. Soft delete vendor
75
UPDATE public.vendors v
76
SET is_deleted = true,
77
modified_by = p_modified_by,
78
deleted_on_utc = p_deleted_on_utc
79
WHERE v.id = v_id;
80
81
-- 8. Return success
82
id := row_index;
83
bulk_delete_vendors.vendor_id := v_id;
84
bulk_delete_vendors.status := 'Deleted';
85
row_index := row_index + 1;
86
RETURN NEXT;
87
END LOOP;
88
END;
89
$function$
|
|||||
| Function | get_all_bill_headers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_bill_headers(p_company_id uuid)
2
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, vendor_id uuid, vendor_name text, due_date timestamp with time zone, bill_date timestamp with time zone, payment_term integer, total_amount numeric, setteled_amount numeric, discount numeric, note text, currency_id integer, bill_status_id integer, bill_status text, bill_type_id integer, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp with time zone, modified_on_utc timestamp with time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
-- Query BillHeaders
8
SELECT
9
bh.id, bh.bill_number, bh.bill_voucher, bh.vendor_id,
10
v.name::text AS vendor_name,
11
bh.due_date::timestamptz, bh.bill_date::timestamptz, bh.payment_term,
12
bh.total_amount, bh.setteled_amount, bh.discount, bh.note,
13
bh.currency_id, bh.bill_status_id, bs.name::text AS bill_status, bh.bill_type_id,
14
bh.created_by,
15
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name,
16
bh.modified_by,
17
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name,
18
bh.created_on_utc::timestamptz,
19
bh.modified_on_utc::timestamptz
20
FROM
21
bill_headers bh
22
JOIN vendors v ON v.id = bh.vendor_id
23
JOIN bill_statuses bs ON bs.id = bh.bill_status_id
24
LEFT JOIN users u_created ON u_created.id = bh.created_by
25
LEFT JOIN users u_modified ON u_modified.id = bh.modified_by
26
WHERE
27
bh.company_id = p_company_id
28
AND bh.is_deleted = false
29
30
UNION ALL
31
32
-- Query DraftBillHeaders
33
SELECT
34
dbh.id, dbh.bill_number, dbh.bill_voucher, dbh.vendor_id,
35
v.name::text AS vendor_name,
36
dbh.due_date::timestamptz, dbh.bill_date::timestamptz, dbh.payment_term,
37
dbh.total_amount, dbh.setteled_amount, dbh.discount, dbh.note,
38
dbh.currency_id, dbh.bill_status_id, bs.name::text AS bill_status, dbh.bill_type_id,
39
dbh.created_by,
40
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name,
41
dbh.modified_by,
42
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name,
43
dbh.created_on_utc::timestamptz,
44
dbh.modified_on_utc::timestamptz
45
FROM
46
draft_bill_headers dbh
47
JOIN vendors v ON v.id = dbh.vendor_id
48
JOIN bill_statuses bs ON bs.id = dbh.bill_status_id
49
LEFT JOIN users u_created ON u_created.id = dbh.created_by
50
LEFT JOIN users u_modified ON u_modified.id = dbh.modified_by
51
WHERE
52
dbh.company_id = p_company_id
53
AND dbh.is_deleted = false;
54
END;
55
$function$
|
|||||
| Function | get_new_payment_number | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_new_payment_number(p_company_id uuid, p_date date)
2
RETURNS character varying
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_finance_year integer;
7
new_payment_id integer;
8
p_prefix varchar;
9
payment_number varchar;
10
BEGIN
11
-- Determine the start year of the financial year based on p_date
12
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
13
v_finance_year := EXTRACT(YEAR FROM p_date);
14
ELSE
15
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
16
END IF;
17
18
-- Attempt to update existing ID row or insert if not found
19
WITH x AS (
20
UPDATE public.bill_payment_header_ids
21
SET last_payment_id = last_payment_id + 1
22
WHERE company_id = p_company_id AND fin_year = v_finance_year
23
RETURNING last_payment_id, payment_prefix
24
),
25
insert_x AS (
26
INSERT INTO public.bill_payment_header_ids (
27
company_id, fin_year, payment_prefix, last_payment_id, payment_length
28
)
29
SELECT p_company_id, v_finance_year, 'BPV', 1, 8
30
WHERE NOT EXISTS (SELECT 1 FROM x)
31
RETURNING last_payment_id, payment_prefix
32
)
33
SELECT
34
COALESCE((SELECT last_payment_id FROM x LIMIT 1), (SELECT last_payment_id FROM insert_x LIMIT 1)),
35
COALESCE((SELECT payment_prefix FROM x LIMIT 1), (SELECT payment_prefix FROM insert_x LIMIT 1))
36
INTO new_payment_id, p_prefix;
37
38
-- Construct the final payment number (e.g., BPV0001)
39
payment_number := p_prefix || LPAD(new_payment_id::text, 4, '0');
40
41
RETURN payment_number;
42
END;
43
$function$
|
|||||
| Function | get_bill_workflow_config | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_workflow_config(p_company_id uuid)
2
RETURNS TABLE(id integer, name text, is_enabled boolean, next_statuses integer[], previous_statuses integer[])
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
s.id,
7
s.name,
8
COALESCE(cfg.is_enabled, false) AS is_enabled,
9
10
-- Forward links (next_statuses)
11
COALESCE(ARRAY(
12
SELECT w.next_status
13
FROM bill_workflow w
14
WHERE w.company_id = p_company_id
15
AND w.status = s.id
16
AND w.is_deleted = false
17
ORDER BY w.next_status
18
), ARRAY[]::INT[]) AS next_statuses,
19
20
-- Backward links (previous_statuses)
21
COALESCE(ARRAY(
22
SELECT DISTINCT w.previous_status
23
FROM bill_workflow w
24
WHERE w.company_id = p_company_id
25
AND w.status = s.id
26
AND w.is_deleted = false
27
AND w.previous_status IS NOT NULL
28
ORDER BY w.previous_status
29
), ARRAY[]::INT[]) AS previous_statuses
30
31
FROM bill_statuses s
32
LEFT JOIN bill_status_company_configs cfg
33
ON cfg.status_id = s.id AND cfg.company_id = p_company_id
34
WHERE s.is_deleted = false;
35
$function$
|
|||||
| Function | get_warehouse_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_warehouse_details(p_warehouse_id uuid)
2
RETURNS TABLE(id uuid, vendor_id uuid, name text, address_id uuid, country_name text, country_id uuid, state_name text, state_id uuid, city_name text, city_id uuid, address_line1 text, address_line2 text, zip_code text, gstin text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
wh.id,
9
wh.vendor_id, -- Assuming vendor_id is relevant for bills
10
wh.name,
11
wh.address_id,
12
(SELECT cou.name FROM countries cou WHERE cou.id = a.country_id) AS country_name,
13
a.country_id,
14
(SELECT st.name FROM states st WHERE st.id = a.state_id) AS state_name,
15
a.state_id,
16
(SELECT ci.name FROM cities ci WHERE ci.id = a.city_id) AS city_name,
17
a.city_id,
18
a.address_line1,
19
a.address_line2,
20
a.zip_code,
21
wh.gstin
22
FROM warehouses wh
23
JOIN addresses a ON wh.address_id = a.id
24
WHERE wh.id = p_warehouse_id;
25
26
IF NOT FOUND THEN
27
RETURN; -- Return an empty result set if no warehouse is found
28
END IF;
29
END;
30
$function$
|
|||||
| Function | get_bill_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_status(p_bill_id uuid)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
bill_status_id INTEGER;
7
BEGIN
8
-- First, try to find the status in the draft_bill_headers table
9
SELECT dbh.bill_status_id
10
INTO bill_status_id
11
FROM public.draft_bill_headers dbh
12
WHERE dbh.id = p_bill_id;
13
14
-- If not found in draft_bill_headers, check bill_headers
15
IF bill_status_id IS NULL THEN
16
SELECT bh.bill_status_id
17
INTO bill_status_id
18
FROM public.bill_headers bh
19
WHERE bh.id = p_bill_id;
20
END IF;
21
22
RETURN bill_status_id;
23
END;
24
$function$
|
|||||
| Function | insert_multiple_bills_by_excel | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.insert_multiple_bills_by_excel(p_bills jsonb)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
bill RECORD;
7
v_created_by UUID;
8
v_status TEXT := 'success';
9
BEGIN
10
-- Validate the input JSONB structure
11
BEGIN
12
PERFORM *
13
FROM jsonb_to_recordset(p_bills) AS (
14
bill_id UUID,
15
company_id UUID,
16
vendor_id UUID,
17
debit_account_id UUID,
18
credit_account_id UUID,
19
discount NUMERIC,
20
bill_number TEXT,
21
currency_id INTEGER,
22
bill_date TIMESTAMP,
23
payment_term INTEGER,
24
bill_status_id INTEGER,
25
due_date TIMESTAMP,
26
total_amount NUMERIC,
27
note TEXT,
28
taxable_amount NUMERIC,
29
fees NUMERIC,
30
sgst_amount NUMERIC,
31
cgst_amount NUMERIC,
32
igst_amount NUMERIC,
33
tds_amount NUMERIC,
34
round_off NUMERIC,
35
round_off_tds NUMERIC,
36
po_no TEXT,
37
po_date TIMESTAMP,
38
source_warehouse_id UUID,
39
destination_warehouse_id UUID,
40
bill_type_id INTEGER,
41
lines JSONB,
42
created_by UUID
43
);
44
EXCEPTION
45
WHEN OTHERS THEN
46
RAISE NOTICE 'Invalid JSON input: %', SQLERRM;
47
RETURN 'error';
48
END;
49
50
-- Get 'System' user as created_by fallback
51
SELECT id INTO v_created_by
52
FROM public.users
53
WHERE first_name = 'System'
54
LIMIT 1;
55
56
-- Loop through each bill
57
FOR bill IN
58
SELECT * FROM jsonb_to_recordset(p_bills) AS (
59
bill_id UUID,
60
company_id UUID,
61
vendor_id UUID,
62
debit_account_id UUID,
63
credit_account_id UUID,
64
discount NUMERIC,
65
bill_number TEXT,
66
currency_id INTEGER,
67
bill_date TIMESTAMP,
68
payment_term INTEGER,
69
bill_status_id INTEGER,
70
due_date TIMESTAMP,
71
total_amount NUMERIC,
72
note TEXT,
73
taxable_amount NUMERIC,
74
fees NUMERIC,
75
sgst_amount NUMERIC,
76
cgst_amount NUMERIC,
77
igst_amount NUMERIC,
78
tds_amount NUMERIC,
79
round_off NUMERIC,
80
round_off_tds NUMERIC,
81
po_no TEXT,
82
po_date TIMESTAMP,
83
source_warehouse_id UUID,
84
destination_warehouse_id UUID,
85
bill_type_id INTEGER,
86
lines JSONB,
87
created_by UUID
88
)
89
LOOP
90
BEGIN
91
-- Duplicate check
92
IF EXISTS (
93
SELECT 1
94
FROM public.bill_headers
95
WHERE bill_number = bill.bill_number
96
AND company_id = bill.company_id
97
) THEN
98
RAISE NOTICE 'Duplicate: %, %', bill.bill_number, bill.company_id;
99
v_status := 'duplicate';
100
CONTINUE;
101
END IF;
102
103
-- Call the save routine
104
CALL public.save_bill_and_details(
105
bill.bill_id,
106
bill.company_id,
107
bill.vendor_id,
108
bill.discount,
109
bill.bill_date,
110
bill.payment_term,
111
bill.bill_status_id,
112
bill.due_date,
113
bill.total_amount,
114
bill.taxable_amount,
115
bill.fees,
116
bill.sgst_amount,
117
bill.cgst_amount,
118
bill.igst_amount,
119
bill.note,
120
bill.round_off,
121
bill.round_off_tds,
122
bill.debit_account_id,
123
bill.credit_account_id,
124
v_created_by,
125
bill.currency_id,
126
bill.po_date,
127
bill.po_no,
128
bill.destination_warehouse_id,
129
bill.source_warehouse_id,
130
bill.bill_type_id,
131
bill.bill_number,
132
bill.tds_amount,
133
bill.lines
134
);
135
136
EXCEPTION
137
WHEN OTHERS THEN
138
RAISE NOTICE 'Error bill %: %', bill.bill_id, SQLERRM;
139
v_status := 'error';
140
CONTINUE;
141
END;
142
END LOOP;
143
144
RETURN v_status;
145
END;
146
$function$
|
|||||
| Function | pg_get_tabledef | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.pg_get_tabledef(p_table_name text)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
col RECORD;
7
col_defs TEXT := '';
8
pk_cols TEXT := '';
9
result TEXT;
10
BEGIN
11
FOR col IN
12
SELECT
13
column_name,
14
data_type,
15
character_maximum_length,
16
numeric_precision,
17
numeric_scale,
18
is_nullable,
19
column_default
20
FROM information_schema.columns
21
WHERE table_schema = 'public' AND table_name = p_table_name
22
ORDER BY ordinal_position
23
LOOP
24
col_defs := col_defs ||
25
format('"%s" %s%s%s%s, ',
26
col.column_name,
27
CASE
28
WHEN col.data_type = 'character varying' THEN format('varchar(%s)', col.character_maximum_length)
29
WHEN col.data_type = 'numeric' THEN format('numeric(%s,%s)', col.numeric_precision, col.numeric_scale)
30
ELSE col.data_type
31
END,
32
CASE WHEN col.column_default IS NOT NULL THEN ' DEFAULT ' || col.column_default ELSE '' END,
33
CASE WHEN col.is_nullable = 'NO' THEN ' NOT NULL' ELSE '' END,
34
''
35
);
36
END LOOP;
37
38
-- Get primary key columns
39
SELECT string_agg(format('"%s"', kcu.column_name), ', ')
40
INTO pk_cols
41
FROM information_schema.table_constraints tc
42
JOIN information_schema.key_column_usage kcu
43
ON tc.constraint_name = kcu.constraint_name
44
WHERE tc.table_schema = 'public'
45
AND tc.table_name = p_table_name
46
AND tc.constraint_type = 'PRIMARY KEY';
47
48
IF pk_cols IS NOT NULL THEN
49
col_defs := col_defs || format('PRIMARY KEY (%s), ', pk_cols);
50
END IF;
51
52
col_defs := left(col_defs, length(col_defs) - 2);
53
result := format('CREATE TABLE "%s" (%s);', p_table_name, col_defs);
54
RETURN result;
55
END;
56
$function$
|
|||||
| Function | bulk_delete_bills_with_flag | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.bulk_delete_bills_with_flag(p_bill_ids uuid[], p_modified_by uuid, p_deleted_on_utc timestamp without time zone, p_is_all_possible_delete boolean)
2
RETURNS TABLE(id integer, bill_id uuid, status text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_id UUID;
7
v_status_id INT;
8
row_index INT := 1;
9
BEGIN
10
FOREACH v_id IN ARRAY p_bill_ids LOOP
11
-- 1. Check if bill exists and is not deleted
12
SELECT bh.bill_status_id INTO v_status_id
13
FROM public.bill_headers bh
14
WHERE bh.id = v_id AND bh.is_deleted = false;
15
16
-- If not found, skip this bill
17
IF NOT FOUND THEN
18
id := row_index;
19
bulk_delete_bills_with_flag.bill_id := v_id;
20
bulk_delete_bills_with_flag.status := 'Skipped - Bill Not Found or Already Deleted';
21
row_index := row_index + 1;
22
RETURN NEXT;
23
CONTINUE;
24
END IF;
25
26
-- 2. If `isAllPossibleDelete` is true, delete if allowed
27
IF p_is_all_possible_delete THEN
28
-- Check if the bill status is "Blocked"
29
IF v_status_id IN (3, 4) THEN
30
id := row_index;
31
bulk_delete_bills_with_flag.bill_id := v_id;
32
bulk_delete_bills_with_flag.status := 'Blocked - Bill Status Prevents Deletion (Approved/Partially Paid)';
33
row_index := row_index + 1;
34
RETURN NEXT;
35
CONTINUE;
36
END IF;
37
38
-- 2.1 Soft delete from bill_headers
39
UPDATE public.bill_headers bh
40
SET is_deleted = true,
41
modified_by = p_modified_by,
42
deleted_on_utc = p_deleted_on_utc
43
WHERE bh.id = v_id;
44
45
-- 2.2 Soft delete from bill_details
46
UPDATE public.bill_details bd
47
SET is_deleted = true,
48
deleted_on_utc = p_deleted_on_utc
49
WHERE bd.bill_header_id = v_id;
50
51
-- 2.3 Soft delete from draft_bill_headers
52
UPDATE public.draft_bill_headers dbh
53
SET is_deleted = true,
54
modified_by = p_modified_by,
55
deleted_on_utc = p_deleted_on_utc
56
WHERE dbh.id = v_id;
57
58
-- 2.4 Soft delete from draft_bill_details
59
UPDATE public.draft_bill_details dbd
60
SET is_deleted = true,
61
deleted_on_utc = p_deleted_on_utc
62
WHERE dbd.bill_header_id = v_id;
63
64
-- 2.5 Return success
65
id := row_index;
66
bulk_delete_bills_with_flag.bill_id := v_id;
67
bulk_delete_bills_with_flag.status := 'Deleted';
68
row_index := row_index + 1;
69
RETURN NEXT;
70
71
ELSE
72
-- 3. If `isAllPossibleDelete` is false, only check eligibility
73
IF v_status_id IN (3, 4) THEN
74
id := row_index;
75
bulk_delete_bills_with_flag.bill_id := v_id;
76
bulk_delete_bills_with_flag.status := 'Blocked - Bill Status Prevents Deletion (Approved/Partially Paid)';
77
row_index := row_index + 1;
78
RETURN NEXT;
79
CONTINUE;
80
END IF;
81
82
-- 3.1 Eligible
83
id := row_index;
84
bulk_delete_bills_with_flag.bill_id := v_id;
85
bulk_delete_bills_with_flag.status := 'Eligible for Deletion';
86
row_index := row_index + 1;
87
RETURN NEXT;
88
END IF;
89
END LOOP;
90
END;
91
$function$
|
|||||
| Function | get_bill_by_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_by_id(p_bill_header_id uuid)
2
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, debit_account_id uuid, credit_account_id uuid, bill_date timestamp with time zone, payment_term integer, vendor_id uuid, vendor_name text, vendor_gstin text, vendor_short_name text, vendor_pan text, vendor_tan text, vendor_email text, vendor_phone_number text, vendor_mobile_number text, vendor_address_line1 text, vendor_address_line2 text, vendor_city_name text, vendor_state_name text, vendor_country_name text, vendor_zip_code text, due_date timestamp with time zone, total_amount numeric, taxable_amount numeric, fees numeric, cgst_amount numeric, sgst_amount numeric, igst_amount numeric, currency_id integer, bill_status_id integer, bill_status text, discount numeric, note text, round_off numeric, round_off_tds numeric, po_no text, po_date timestamp without time zone, destination_warehouse_id uuid, source_warehouse_id uuid, source_vendor_id uuid, source_warehouse_name text, source_address_id uuid, source_country_name text, source_country_id uuid, source_state_name text, source_state_id uuid, source_city_name text, source_city_id uuid, source_address_line1 text, source_address_line2 text, source_zip_code text, source_gstin text, bill_lines jsonb, bill_type_id integer, current_approval_level integer, tds_amount numeric, created_on timestamp with time zone, modified_on timestamp with time zone, created_by uuid, modified_by uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
v_bill_status_id INTEGER;
8
BEGIN
9
v_bill_status_id := public.get_bill_status(p_bill_header_id);
10
11
IF v_bill_status_id >= 3 THEN
12
RETURN QUERY
13
SELECT
14
bh.id,
15
bh.bill_number::text,
16
bh.bill_voucher::text,
17
bh.debit_account_id,
18
bh.credit_account_id,
19
bh.bill_date::TIMESTAMP WITH TIME ZONE,
20
bh.payment_term,
21
bh.vendor_id,
22
v.name::text AS vendor_name,
23
v.gstin::text AS vendor_gstin,
24
v.short_name::text AS vendor_short_name,
25
v.pan::text AS vendor_pan,
26
v.tan::text AS vendor_tan,
27
co.email::text AS vendor_email,
28
co.phone_number::text AS vendor_phone_number,
29
co.mobile_number::text AS vendor_mobile_number,
30
addr.address_line1::text AS vendor_address_line1,
31
addr.address_line2::text AS vendor_address_line2,
32
cit.name::text AS vendor_city_name,
33
st.name::text AS vendor_state_name,
34
ctry.name::text AS vendor_country_name,
35
addr.zip_code::text AS vendor_zip_code,
36
bh.due_date::TIMESTAMP WITH TIME ZONE,
37
bh.total_amount,
38
bh.taxable_amount,
39
bh.fees,
40
bh.cgst_amount,
41
bh.sgst_amount,
42
bh.igst_amount,
43
bh.currency_id,
44
bh.bill_status_id,
45
blst.name::text,
46
bh.discount,
47
bh.note::text,
48
bh.round_off,
49
bh.round_off_tds,
50
bh.po_no::text,
51
bh.po_date::TIMESTAMP WITHOUT TIME ZONE,
52
bh.destination_warehouse_id,
53
wh.id AS source_warehouse_id,
54
wh.vendor_id AS source_vendor_id,
55
wh.name::text AS source_warehouse_name,
56
wh.address_id AS source_address_id,
57
wh.country_name::text AS source_country_name,
58
wh.country_id AS source_country_id,
59
wh.state_name::text AS source_state_name,
60
wh.state_id AS source_state_id,
61
wh.city_name::text AS source_city_name,
62
wh.city_id AS source_city_id,
63
wh.address_line1::text AS source_address_line1,
64
wh.address_line2::text AS source_address_line2,
65
wh.zip_code::text AS source_zip_code,
66
wh.gstin::text AS source_gstin,
67
jsonb_agg(jsonb_build_object(
68
'bill_line_id', bl.id,
69
'product_id', bl.product_id,
70
'price', bl.price,
71
'quantity', bl.quantity,
72
'fees', bl.fees,
73
'discount', bl.discount,
74
'taxable_amount', bl.taxable_amount,
75
'sgst_amount', bl.sgst_amount,
76
'cgst_amount', bl.cgst_amount,
77
'igst_amount', bl.igst_amount,
78
'total_amount', bl.total_amount,
79
'serial_number', bl.serial_number
80
) ORDER BY bl.serial_number) AS bill_lines,
81
bh.bill_type_id,
82
bh.current_approval_level,
83
bh.tds_amount,
84
bh.created_on_utc::TIMESTAMP WITH TIME ZONE,
85
bh.modified_on_utc::TIMESTAMP WITH TIME ZONE,
86
bh.created_by,
87
bh.modified_by
88
FROM bill_headers bh
89
LEFT JOIN public.vendors v ON bh.vendor_id = v.id
90
LEFT JOIN public.vendor_contacts vc ON v.id = vc.vendor_id AND vc.is_deleted = false
91
LEFT JOIN public.contacts co ON vc.contact_id = co.id AND co.is_deleted = false AND co.is_primary = true
92
LEFT JOIN public.addresses addr ON v.billing_address_id = addr.id
93
LEFT JOIN public.cities cit ON addr.city_id = cit.id
94
LEFT JOIN public.states st ON addr.state_id = st.id
95
LEFT JOIN public.countries ctry ON addr.country_id = ctry.id
96
LEFT JOIN public.get_warehouse_details(bh.source_warehouse_id) wh ON TRUE
97
LEFT JOIN public.get_bill_lines(bh.id, bh.bill_status_id) bl ON TRUE
98
JOIN public.bill_statuses blst ON bh.bill_status_id = blst.id
99
WHERE bh.id = p_bill_header_id
100
GROUP BY
101
bh.id,
102
bh.bill_number,
103
bh.bill_voucher,
104
bh.bill_date,
105
bh.payment_term,
106
bh.vendor_id,
107
v.name,
108
v.gstin,
109
v.short_name,
110
v.pan,
111
v.tan,
112
co.email,
113
co.phone_number,
114
co.mobile_number,
115
addr.address_line1,
116
addr.address_line2,
117
cit.name,
118
st.name,
119
ctry.name,
120
addr.zip_code,
121
bh.due_date,
122
bh.total_amount,
123
bh.taxable_amount,
124
bh.fees,
125
bh.cgst_amount,
126
bh.sgst_amount,
127
bh.igst_amount,
128
bh.currency_id,
129
bh.bill_status_id,
130
blst.name,
131
bh.discount,
132
bh.note,
133
bh.round_off,
134
bh.round_off_tds,
135
bh.po_no,
136
bh.po_date,
137
bh.destination_warehouse_id,
138
bh.source_warehouse_id,
139
wh.id,
140
wh.vendor_id,
141
wh.name,
142
wh.address_id,
143
wh.country_name,
144
wh.country_id,
145
wh.state_name,
146
wh.state_id,
147
wh.city_name,
148
wh.city_id,
149
wh.address_line1,
150
wh.address_line2,
151
wh.zip_code,
152
wh.gstin,
153
bh.bill_type_id,
154
bh.current_approval_level,
155
bh.tds_amount,
156
bh.created_on_utc,
157
bh.modified_on_utc,
158
bh.created_by,
159
bh.modified_by;
160
161
ELSE
162
-- You can add the similar query for draft bill_headers here
163
RETURN QUERY
164
SELECT
165
dbh.id,
166
dbh.bill_number::text,
167
dbh.bill_voucher::text,
168
dbh.debit_account_id,
169
dbh.credit_account_id,
170
dbh.bill_date::TIMESTAMP WITH TIME ZONE,
171
dbh.payment_term,
172
dbh.vendor_id,
173
v.name::text AS vendor_name,
174
v.gstin::text AS vendor_gstin,
175
v.short_name::text AS vendor_short_name,
176
v.pan::text AS vendor_pan,
177
v.tan::text AS vendor_tan,
178
co.email::text AS vendor_email,
179
co.phone_number::text AS vendor_phone_number,
180
co.mobile_number::text AS vendor_mobile_number,
181
addr.address_line1::text,
182
addr.address_line2::text,
183
cit.name::text AS vendor_city_name,
184
st.name::text AS vendor_state_name,
185
ctry.name::text AS vendor_country_name,
186
addr.zip_code::text,
187
dbh.due_date::TIMESTAMP WITH TIME ZONE,
188
dbh.total_amount,
189
dbh.taxable_amount,
190
dbh.fees,
191
dbh.cgst_amount,
192
dbh.sgst_amount,
193
dbh.igst_amount,
194
dbh.currency_id,
195
dbh.bill_status_id,
196
dblst.name::text,
197
dbh.discount,
198
dbh.note::text,
199
dbh.round_off,
200
dbh.round_off_tds,
201
dbh.po_no::text,
202
dbh.po_date::TIMESTAMP WITHOUT TIME ZONE,
203
dbh.destination_warehouse_id,
204
wh.id AS source_warehouse_id,
205
wh.vendor_id AS source_vendor_id,
206
wh.name::text AS source_warehouse_name,
207
wh.address_id AS source_address_id,
208
wh.country_name::text AS source_country_name,
209
wh.country_id AS source_country_id,
210
wh.state_name::text AS source_state_name,
211
wh.state_id AS source_state_id,
212
wh.city_name::text AS source_city_name,
213
wh.city_id AS source_city_id,
214
wh.address_line1::text AS source_address_line1,
215
wh.address_line2::text AS source_address_line2,
216
wh.zip_code::text AS source_zip_code,
217
wh.gstin::text AS source_gstin,
218
jsonb_agg(jsonb_build_object(
219
'bill_line_id', dbl.id,
220
'product_id', dbl.product_id,
221
'price', dbl.price,
222
'quantity', dbl.quantity,
223
'fees', dbl.fees,
224
'discount', dbl.discount,
225
'taxable_amount', dbl.taxable_amount,
226
'sgst_amount', dbl.sgst_amount,
227
'cgst_amount', dbl.cgst_amount,
228
'igst_amount', dbl.igst_amount,
229
'total_amount', dbl.total_amount,
230
'serial_number', dbl.serial_number
231
) ORDER BY dbl.serial_number) AS bill_lines,
232
dbh.bill_type_id,
233
dbh.current_approval_level,
234
dbh.tds_amount,
235
dbh.created_on_utc::TIMESTAMP WITH TIME ZONE,
236
dbh.modified_on_utc::TIMESTAMP WITH TIME ZONE,
237
dbh.created_by,
238
dbh.modified_by
239
FROM draft_bill_headers dbh
240
LEFT JOIN public.vendors v ON dbh.vendor_id = v.id
241
LEFT JOIN public.vendor_contacts vc ON v.id = vc.vendor_id AND vc.is_deleted = false
242
LEFT JOIN public.contacts co ON vc.contact_id = co.id AND co.is_deleted = false AND co.is_primary = true
243
LEFT JOIN public.addresses addr ON v.billing_address_id = addr.id
244
LEFT JOIN public.cities cit ON addr.city_id = cit.id
245
LEFT JOIN public.states st ON addr.state_id = st.id
246
LEFT JOIN public.countries ctry ON addr.country_id = ctry.id
247
LEFT JOIN public.get_warehouse_details(dbh.source_warehouse_id) wh ON TRUE
248
LEFT JOIN public.get_bill_lines(dbh.id, dbh.bill_status_id) dbl ON TRUE
249
JOIN public.bill_statuses dblst ON dbh.bill_status_id = dblst.id
250
WHERE dbh.id = p_bill_header_id
251
GROUP BY
252
dbh.id,
253
dbh.bill_number,
254
dbh.bill_voucher,
255
dbh.bill_date,
256
dbh.payment_term,
257
dbh.vendor_id,
258
v.name,
259
v.gstin,
260
v.short_name,
261
v.pan,
262
v.tan,
263
co.email,
264
co.phone_number,
265
co.mobile_number,
266
addr.address_line1,
267
addr.address_line2,
268
cit.name,
269
st.name,
270
ctry.name,
271
addr.zip_code,
272
dbh.due_date,
273
dbh.total_amount,
274
dbh.taxable_amount,
275
dbh.fees,
276
dbh.cgst_amount,
277
dbh.sgst_amount,
278
dbh.igst_amount,
279
dbh.currency_id,
280
dbh.bill_status_id,
281
dblst.name,
282
dbh.discount,
283
dbh.note,
284
dbh.round_off,
285
dbh.round_off_tds,
286
dbh.destination_warehouse_id,
287
dbh.po_no,
288
dbh.po_date,
289
dbh.source_warehouse_id,
290
wh.id,
291
wh.vendor_id,
292
wh.name,
293
wh.address_id,
294
wh.country_name,
295
wh.country_id,
296
wh.state_name,
297
wh.state_id,
298
wh.city_name,
299
wh.city_id,
300
wh.address_line1,
301
wh.address_line2,
302
wh.zip_code,
303
wh.gstin,
304
dbh.bill_type_id,
305
dbh.current_approval_level,
306
dbh.tds_amount,
307
dbh.created_on_utc,
308
dbh.modified_on_utc,
309
dbh.created_by,
310
dbh.modified_by;
311
END IF;
312
END;
313
$function$
|
|||||
| Function | update_bill_next_status_main | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_bill_next_status_main(p_company_id uuid, p_bill_ids uuid[], p_modified_by uuid)
2
RETURNS TABLE(bill_id uuid, status_name text, error_msg text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_error_message text;
7
v_bill_ids_draft uuid[];
8
v_bill_ids_pending uuid[];
9
v_bill_ids_other uuid[];
10
v_next_temp_id int;
11
BEGIN
12
RAISE NOTICE 'START update_bill_next_status_main: company_id=%, modified_by=%', p_company_id, p_modified_by;
13
RAISE NOTICE 'Incoming Bill IDs: %', p_bill_ids;
14
15
-- Clean up temp_bill_next_status for these bill IDs to avoid duplicate entries
16
DELETE FROM temp_bill_next_status tbnv
17
WHERE tbnv.bill_id = ANY(p_bill_ids);
18
19
-- Classify bills by current status
20
SELECT array_agg(id) INTO v_bill_ids_draft
21
FROM public.draft_bill_headers dbh
22
WHERE dbh.id = ANY(p_bill_ids) AND dbh.bill_status_id = 1;
23
24
SELECT array_agg(id) INTO v_bill_ids_pending
25
FROM public.draft_bill_headers dbh
26
WHERE dbh.id = ANY(p_bill_ids) AND dbh.bill_status_id = 2;
27
28
SELECT array_agg(id) INTO v_bill_ids_other
29
FROM public.bill_headers bh
30
WHERE bh.id = ANY(p_bill_ids) AND bh.bill_status_id >= 3;
31
32
RAISE NOTICE 'Draft bills: %', COALESCE(v_bill_ids_draft, '{}');
33
RAISE NOTICE 'Pending Approval bills: %', COALESCE(v_bill_ids_pending, '{}');
34
RAISE NOTICE 'Other (Approved or higher) bills: %', COALESCE(v_bill_ids_other, '{}');
35
36
BEGIN
37
-- Directly update draft bills to Pending Approval
38
IF array_length(v_bill_ids_draft, 1) > 0 THEN
39
RAISE NOTICE 'Directly updating Draft bills to Pending Approval';
40
41
UPDATE public.draft_bill_headers
42
SET bill_status_id = 2,
43
modified_by = p_modified_by,
44
modified_on_utc = now()
45
WHERE id = ANY(v_bill_ids_draft);
46
47
-- Insert approval logs
48
INSERT INTO public.bill_approval_logs(
49
bill_id, status_id, approved_by, approved_on, "comment",
50
created_on_utc, created_by, approval_level
51
)
52
SELECT
53
dbh.id, 2, p_modified_by, now(),
54
'Direct update from Draft to Pending Approval',
55
now(), p_modified_by, 0
56
FROM public.draft_bill_headers dbh
57
WHERE dbh.id = ANY(v_bill_ids_draft);
58
59
-- Generate next id for temp_bill_next_status
60
SELECT COALESCE(MAX(id), 0) INTO v_next_temp_id FROM temp_bill_next_status;
61
62
-- Insert into temp_bill_next_status
63
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
64
SELECT
65
row_number() OVER () + v_next_temp_id AS id,
66
dbh.id,
67
'Pending Approval',
68
NULL
69
FROM public.draft_bill_headers dbh
70
WHERE dbh.id = ANY(v_bill_ids_draft);
71
72
END IF;
73
74
-- Call update_draft_bill_next_status if Pending Approval bills found and status_id = 2
75
IF array_length(v_bill_ids_pending, 1) > 0 THEN
76
RAISE NOTICE 'Calling update_draft_bill_next_status for Pending Approval bills';
77
CALL public.update_draft_bill_next_status(p_company_id, v_bill_ids_pending, p_modified_by);
78
END IF;
79
80
-- Call update_bill_next_status_for_bill_header for Approved or higher bills
81
IF array_length(v_bill_ids_other, 1) > 0 THEN
82
RAISE NOTICE 'Calling update_bill_next_status_for_bill_header for Approved or higher bills';
83
CALL public.update_bill_next_status_for_bill_header(p_company_id, v_bill_ids_other, p_modified_by);
84
END IF;
85
86
EXCEPTION WHEN OTHERS THEN
87
v_error_message := SQLERRM;
88
RAISE NOTICE 'Exception in child procedures: %', v_error_message;
89
END;
90
91
-- Return rows from temp_bill_next_status
92
RAISE NOTICE 'Preparing to return rows from temp_bill_next_status';
93
RETURN QUERY
94
SELECT tbn.bill_id, tbn.status AS status_name, tbn.error AS error_msg
95
FROM temp_bill_next_status tbn
96
WHERE tbn.bill_id = ANY(p_bill_ids);
97
98
RAISE NOTICE 'END update_bill_next_status_main';
99
END;
100
$function$
|
|||||
| Function | list_scheduled_bill_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.list_scheduled_bill_ids(p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
2
RETURNS TABLE(bill_header_id uuid, draft_bill_header_id uuid, company_id uuid, organization_id uuid)
3
LANGUAGE sql
4
AS $function$
5
WITH base AS (
6
SELECT
7
s.id AS schedule_id,
8
s.bill_header_id AS bill_header_id,
9
s.draft_bill_header_id AS draft_bill_header_id,
10
s.company_id AS company_id,
11
c.organization_id AS organization_id,
12
d.frequency_cycle,
13
d.day_of_week,
14
d.day_of_month,
15
d.month_of_year,
16
s.starts_from,
17
s.end_date
18
FROM public.recurring_bill_schedules s
19
JOIN public.recurrence_bill_schedule_details d
20
ON d.schedule_id = s.id
21
AND d.is_deleted = false
22
JOIN public.companies c
23
ON c.id = s.company_id
24
WHERE s.is_deleted = false
25
AND s.schedule_status = 'active'
26
AND s.starts_from <= (p_schedule_date::date)
27
AND (s.end_date IS NULL OR s.end_date >= (p_schedule_date::date))
28
),
29
aligned AS (
30
SELECT
31
b.schedule_id,
32
b.bill_header_id,
33
b.draft_bill_header_id,
34
b.company_id,
35
b.organization_id
36
FROM base b
37
WHERE CASE lower(b.frequency_cycle)
38
WHEN 'daily' THEN TRUE
39
WHEN 'weekly' THEN b.day_of_week IS NOT NULL
40
AND b.day_of_week = EXTRACT(ISODOW FROM p_schedule_date)::int
41
WHEN 'monthly' THEN b.day_of_month IS NOT NULL
42
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
43
WHEN 'yearly' THEN b.month_of_year IS NOT NULL AND b.day_of_month IS NOT NULL
44
AND b.month_of_year = EXTRACT(MONTH FROM p_schedule_date)::int
45
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
46
ELSE FALSE
47
END
48
)
49
SELECT DISTINCT -- 👈 this was missing in your version
50
a.bill_header_id,
51
a.draft_bill_header_id,
52
a.company_id,
53
a.organization_id
54
FROM aligned a
55
WHERE NOT EXISTS (
56
SELECT 1
57
FROM public.schedule_execution_logs l
58
WHERE l.schedule_id = a.schedule_id
59
AND l.execution_date = (p_schedule_date::date)
60
AND l.is_deleted = false
61
);
62
$function$
|
|||||
| Function | create_vendor_advance_settlements_123 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_vendor_advance_settlements_123(p_company_id uuid, p_vendor_id uuid, p_bill_payment_header_id uuid, p_created_by uuid, p_settlements jsonb)
2
RETURNS TABLE(id uuid, advance_settlement_number text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_settlement_number text;
7
v_row jsonb;
8
v_id uuid;
9
BEGIN
10
FOR v_row IN SELECT * FROM jsonb_array_elements(p_settlements)
11
LOOP
12
v_settlement_number := public.get_new_vendor_advance_settlement_number(
13
p_company_id,
14
COALESCE((v_row->>'settled_date')::date, CURRENT_DATE)
15
);
16
v_id := (v_row->>'id')::uuid;
17
18
INSERT INTO vendor_advance_settlements
19
(
20
id,
21
company_id,
22
vendor_id,
23
advance_id,
24
bill_id,
25
apply_amount,
26
applied_in_payment_id,
27
settled_date,
28
note,
29
created_on_utc,
30
created_by,
31
is_deleted,
32
advance_settlement_number
33
)
34
VALUES
35
(
36
v_id,
37
p_company_id,
38
p_vendor_id,
39
(v_row->>'advance_id')::uuid,
40
(v_row->>'bill_id')::uuid,
41
(v_row->>'apply_amount')::numeric,
42
p_bill_payment_header_id,
43
COALESCE((v_row->>'settled_date')::timestamp, now()),
44
NULLIF(v_row->>'note',''),
45
now(),
46
p_created_by,
47
false,
48
v_settlement_number
49
);
50
51
UPDATE vendor_advances va
52
SET utilized_amount = utilized_amount + (v_row->>'apply_amount')::numeric,
53
modified_by = p_created_by,
54
modified_on_utc = now()
55
WHERE va.id = (v_row->>'advance_id')::uuid
56
AND va.company_id = p_company_id
57
AND va.vendor_id = p_vendor_id
58
AND va.is_deleted = false;
59
60
-- Assign to output variables, then RETURN NEXT;
61
id := v_id;
62
advance_settlement_number := v_settlement_number;
63
RETURN NEXT;
64
END LOOP;
65
END;
66
$function$
|
|||||
| Function | get_vendor_by_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vendor_by_id(p_vendor_id uuid)
2
RETURNS TABLE(id uuid, name text, gstin text, short_name text, pan text, tan text, proprietor_name text, outstanding_limit numeric, is_non_work boolean, interest_percentage numeric, billing_address_id uuid, billing_address jsonb, shipping_address_id uuid, shipping_address jsonb, bank_accounts jsonb, contacts jsonb, contact jsonb)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
v.id,
9
v.name::text,
10
v.gstin::text,
11
v.short_name::text,
12
v.pan::text,
13
v.tan::text,
14
v.proprietor_name::text,
15
v.outstanding_limit,
16
v.is_non_work,
17
v.interest_percentage,
18
v.billing_address_id,
19
20
-- Billing Address as JSONB
21
CASE
22
WHEN ba.id IS NOT NULL THEN jsonb_build_object(
23
'AddressLine1', ba.address_line1,
24
'AddressLine2', ba.address_line2,
25
'ZipCode', ba.zip_code,
26
'CountryId', ba.country_id,
27
'StateId', ba.state_id,
28
'StateName', s.name,
29
'CityId', ba.city_id,
30
'CityName', ct.name
31
)
32
ELSE NULL
33
END AS billing_address,
34
35
v.shipping_address_id,
36
37
-- Shipping Address as JSONB
38
CASE
39
WHEN sa.id IS NOT NULL THEN jsonb_build_object(
40
'AddressLine1', sa.address_line1,
41
'AddressLine2', sa.address_line2,
42
'ZipCode', sa.zip_code,
43
'CountryId', sa.country_id,
44
'StateId', sa.state_id,
45
'StateName', s2.name,
46
'CityId', sa.city_id,
47
'CityName', ct2.name
48
)
49
ELSE NULL
50
END AS shipping_address,
51
52
-- Bank Accounts as JSONB Array
53
COALESCE(
54
(
55
SELECT jsonb_agg(
56
jsonb_build_object(
57
'Id', ba.id,
58
'BankId', ba.bank_id,
59
'BranchName', ba.branch_name,
60
'BankName', b.name,
61
'AccountNumber', ba.account_number,
62
'IFSC', ba.ifsc
63
)
64
)
65
FROM vendor_bank_accounts vba
66
JOIN bank_accounts ba ON vba.bank_account_id = ba.id
67
JOIN banks b ON ba.bank_id = b.id
68
WHERE vba.vendor_id = p_vendor_id AND ba.is_deleted = false
69
),
70
'[]'::jsonb
71
) AS bank_accounts,
72
73
-- Vendor Contacts as JSONB Array
74
COALESCE(
75
(
76
SELECT jsonb_agg(
77
jsonb_build_object(
78
'Id', con.id,
79
'Salutation', con.salutation,
80
'FirstName', con.first_name,
81
'LastName', con.last_name,
82
'Email', con.email,
83
'PhoneNumber', con.phone_number,
84
'MobileNumber', con.mobile_number,
85
'IsPrimary', con.is_primary
86
)
87
)
88
FROM vendor_contacts vc
89
JOIN contacts con ON vc.contact_id = con.id
90
WHERE vc.vendor_id = p_vendor_id AND con.is_deleted = false
91
),
92
'[]'::jsonb
93
) AS contacts,
94
95
-- Primary Contact as JSONB
96
COALESCE(
97
(
98
SELECT jsonb_build_object(
99
'Id', con.id,
100
'Salutation', con.salutation,
101
'FirstName', con.first_name,
102
'LastName', con.last_name,
103
'Email', con.email,
104
'PhoneNumber', con.phone_number,
105
'MobileNumber', con.mobile_number
106
)
107
FROM vendor_contacts vc
108
JOIN contacts con ON vc.contact_id = con.id
109
WHERE vc.vendor_id = p_vendor_id AND con.is_primary = TRUE AND con.is_deleted = false
110
),
111
NULL
112
) AS contact
113
FROM vendors v
114
LEFT JOIN addresses ba ON v.billing_address_id = ba.id
115
LEFT JOIN addresses sa ON v.shipping_address_id = sa.id
116
LEFT JOIN states s ON ba.state_id = s.id
117
LEFT JOIN cities ct ON ba.city_id = ct.id
118
LEFT JOIN states s2 ON sa.state_id = s2.id
119
LEFT JOIN cities ct2 ON sa.city_id = ct2.id
120
WHERE v.id = p_vendor_id AND v.is_deleted = false;
121
END;
122
$function$
|
|||||
| Function | get_new_draft_bill_voucher | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_new_draft_bill_voucher(p_company_id uuid, p_date date)
2
RETURNS character varying
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_finance_year integer;
7
new_bill_id integer;
8
p_prefix varchar;
9
bill_voucher varchar;
10
BEGIN
11
-- Determine the start year of the financial year based on p_date
12
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
13
v_finance_year := EXTRACT(YEAR FROM p_date);
14
ELSE
15
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
16
END IF;
17
18
-- Attempt to update existing ID row or insert if not found
19
WITH x AS (
20
UPDATE public.draft_bill_header_ids
21
SET last_bill_id = last_bill_id + 1
22
WHERE company_id = p_company_id AND fin_year = v_finance_year
23
RETURNING last_bill_id, bill_prefix
24
),
25
insert_x AS (
26
INSERT INTO public.draft_bill_header_ids (
27
company_id, fin_year, bill_prefix, last_bill_id, bill_length
28
)
29
SELECT p_company_id, v_finance_year, 'DBV', 1, 8
30
WHERE NOT EXISTS (SELECT 1 FROM x)
31
RETURNING last_bill_id, bill_prefix
32
)
33
SELECT
34
COALESCE((SELECT last_bill_id FROM x LIMIT 1), (SELECT last_bill_id FROM insert_x LIMIT 1)),
35
COALESCE((SELECT bill_prefix FROM x LIMIT 1), (SELECT bill_prefix FROM insert_x LIMIT 1))
36
INTO new_bill_id, p_prefix;
37
38
-- Construct the final draft bill voucher number (e.g., DBV0001)
39
bill_voucher := p_prefix || LPAD(new_bill_id::text, 4, '0');
40
41
RETURN bill_voucher;
42
END;
43
$function$
|
|||||
| Function | get_bill_payments_by_company | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_payments_by_company(p_company_id uuid)
2
RETURNS TABLE(id uuid, vendor_name text, payment_number text, vendor_id uuid, paid_amount numeric, grand_total_amount numeric, advance_amount numeric, credit_account_id uuid, description text, mode_of_payment text, paid_date timestamp without time zone, reference text, created_by uuid, modified_by uuid, created_by_name text, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
bph.id,
9
v.name :: text AS vendor_name,
10
bph.payment_number,
11
bph.vendor_id,
12
bph.paid_amount,
13
bph.grand_total_amount,
14
bph.advance_amount,
15
bph.credit_account_id,
16
bph.description,
17
bph.mode_of_payment,
18
bph.paid_date,
19
bph.reference,
20
bph.created_by,
21
bph.modified_by,
22
COALESCE(u1.first_name || ' ' || u1.last_name, '') AS created_by_name,
23
COALESCE(u2.first_name || ' ' || u2.last_name, '') AS modified_by_name,
24
bph.created_on_utc,
25
bph.modified_on_utc
26
FROM bill_payment_headers bph
27
LEFT JOIN vendors v ON v.id = bph.vendor_id
28
LEFT JOIN users u1 ON u1.id = bph.created_by
29
LEFT JOIN users u2 ON u2.id = bph.modified_by
30
WHERE bph.company_id = p_company_id
31
AND (bph.is_deleted IS FALSE OR bph.is_deleted IS NULL);
32
END;
33
$function$
|
|||||
| Function | get_all_bill_account_approvals | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_bill_account_approvals(p_company_id uuid)
2
RETURNS TABLE(id integer, account_id uuid, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
baua.id,
9
baua.account_id,
10
baua.status_id,
11
baua.user_id,
12
baua.approval_level,
13
baal.approval_level_required AS required_approval_levels
14
FROM bill_approval_user_account baua
15
JOIN public.bill_account_approval_levels baal
16
ON baua.company_id = baal.company_id
17
AND baal.account_id = baua.account_id
18
WHERE baua.company_id = p_company_id
19
--AND baal.account_id = ba.account_id
20
AND baua.is_deleted = false
21
AND baal.is_deleted = false
22
AND baal.status_id in(2)
23
ORDER BY baua.account_id, baua.status_id;
24
END;
25
$function$
|
|||||
| Function | upsert_bill_status_company_config | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_bill_status_company_config(p_company_id uuid, p_status_id integer, p_is_enabled boolean, p_user_id uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
-- Try update first
7
UPDATE bill_status_company_configs
8
SET is_enabled = p_is_enabled,
9
modified_by = p_user_id,
10
modified_on_utc = NOW()
11
WHERE company_id = p_company_id AND status_id = p_status_id AND is_deleted = FALSE;
12
13
IF NOT FOUND THEN
14
-- Insert if update affected no rows
15
INSERT INTO bill_status_company_configs (
16
company_id,
17
status_id,
18
is_enabled,
19
created_by,
20
created_on_utc,
21
is_deleted
22
) VALUES (
23
p_company_id,
24
p_status_id,
25
p_is_enabled,
26
p_user_id,
27
NOW(),
28
FALSE
29
);
30
END IF;
31
END;
32
$function$
|
|||||
| Function | get_all_vendors | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_vendors(p_company_id uuid, p_fin_year_id integer, p_is_get_all boolean)
2
RETURNS TABLE(id uuid, name character varying, contact_name text, gst_in text, mobile_number text, email text, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, billing_address jsonb, contact_count integer, total_billed_amount numeric, total_vendor_paid_amount numeric, total_tds_paid_amount numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_organization_id uuid;
7
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
8
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
9
BEGIN
10
SELECT com.organization_id INTO v_organization_id
11
FROM public.companies com
12
WHERE com.id = p_company_id;
13
14
RETURN QUERY
15
SELECT
16
v.id,
17
v.name,
18
con.first_name || ' ' || con.last_name AS contact_name,
19
v.gstin AS gst_in,
20
con.mobile_number,
21
con.email,
22
v.created_by,
23
u_created.first_name || ' ' || u_created.last_name AS created_by_name,
24
v.modified_by,
25
u_modified.first_name || ' ' || u_modified.last_name AS modified_by_name,
26
v.created_on_utc,
27
v.modified_on_utc,
28
CASE
29
WHEN ba.id IS NOT NULL THEN jsonb_build_object(
30
'AddressLine1', ba.address_line1,
31
'AddressLine2', ba.address_line2,
32
'ZipCode', ba.zip_code,
33
'CountryId', ba.country_id,
34
'StateId', ba.state_id,
35
'CityId', ba.city_id
36
)
37
ELSE NULL
38
END AS billing_address,
39
(SELECT COUNT(*)::integer
40
FROM public.vendor_contacts vc_count
41
WHERE vc_count.vendor_id = v.id) AS contact_count,
42
COALESCE(bill_sums.total_billed_amount, 0) AS total_billed_amount,
43
COALESCE(bill_sums.total_vendor_paid_amount, 0) AS total_vendor_paid_amount,
44
COALESCE(bill_sums.total_tds_paid_amount, 0) AS total_tds_paid_amount
45
FROM public.vendors v
46
JOIN public.vendor_contacts vc ON v.id = vc.vendor_id
47
JOIN public.contacts con ON vc.contact_id = con.id
48
LEFT JOIN public.users u_created ON v.created_by = u_created.id
49
LEFT JOIN public.users u_modified ON v.modified_by = u_modified.id
50
LEFT JOIN public.addresses ba ON v.billing_address_id = ba.id
51
LEFT JOIN (
52
SELECT
53
b.vendor_id,
54
SUM(b.total_amount) AS total_billed_amount,
55
SUM(b.vendor_paid_amount) AS total_vendor_paid_amount,
56
SUM(b.tds_paid_amount) AS total_tds_paid_amount
57
FROM public.bill_headers b
58
WHERE b.company_id = p_company_id
59
AND b.is_deleted = false
60
AND b.bill_date >= v_start_date
61
AND b.bill_date <= v_end_date
62
GROUP BY b.vendor_id
63
) bill_sums ON bill_sums.vendor_id = v.id
64
WHERE v.company_id IN (
65
SELECT c.id FROM public.companies c
66
WHERE (p_is_get_all AND c.organization_id = v_organization_id)
67
OR (NOT p_is_get_all AND c.id = p_company_id)
68
)
69
AND v.is_deleted = false
70
AND con.is_primary = true;
71
END;
72
$function$
|
|||||
| Function | get_bill_analytics | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_analytics(billing_company_id uuid, p_finance_year_id integer, p_bill_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(status text, count integer, total_amount numeric, month_start_date date)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_financial_year_start DATE;
7
v_financial_year_end DATE;
8
v_date_start DATE;
9
v_date_end DATE;
10
BEGIN
11
-- Set default financial year range
12
v_financial_year_start := TO_DATE(p_finance_year_id || '-04-01', 'YYYY-MM-DD');
13
v_financial_year_end := TO_DATE((p_finance_year_id + 1) || '-03-31', 'YYYY-MM-DD');
14
15
-- Use date range override if provided, otherwise use financial year range
16
v_date_start := COALESCE(p_start_date::DATE, v_financial_year_start);
17
v_date_end := COALESCE(p_end_date::DATE, v_financial_year_end);
18
19
RETURN QUERY
20
21
-- PAID
22
SELECT 'Paid' AS status,
23
COUNT(*)::INTEGER AS count,
24
COALESCE(SUM(b.vendor_paid_amount), 0) AS total_amount,
25
DATE_TRUNC('month', b.bill_date)::DATE AS month_start_date
26
FROM bill_headers b
27
WHERE b.company_id = billing_company_id
28
AND b.bill_status_id IN (4, 5)
29
AND b.is_deleted = false
30
AND b.bill_date BETWEEN v_date_start AND v_date_end
31
AND (p_bill_type_id IS NULL OR b.bill_type_id = p_bill_type_id)
32
GROUP BY month_start_date
33
34
UNION ALL
35
36
-- PENDING
37
SELECT 'Pending' AS status,
38
COUNT(*)::INTEGER AS count,
39
COALESCE(SUM(b.total_amount - b.vendor_paid_amount), 0) AS total_amount,
40
DATE_TRUNC('month', b.bill_date)::DATE AS month_start_date
41
FROM bill_headers b
42
WHERE b.company_id = billing_company_id
43
AND b.bill_status_id IN (3, 4)
44
AND b.is_deleted = false
45
AND b.bill_date BETWEEN v_date_start AND v_date_end
46
AND (p_bill_type_id IS NULL OR b.bill_type_id = p_bill_type_id)
47
GROUP BY month_start_date
48
49
UNION ALL
50
51
-- OVERDUE
52
SELECT 'Overdue' AS status,
53
COUNT(*)::INTEGER AS count,
54
COALESCE(SUM(b.total_amount - b.vendor_paid_amount), 0) AS total_amount,
55
DATE_TRUNC('month', b.bill_date)::DATE AS month_start_date
56
FROM bill_headers b
57
WHERE b.company_id = billing_company_id
58
AND b.bill_status_id IN (3, 4)
59
AND b.is_deleted = false
60
AND b.due_date < CURRENT_DATE
61
AND b.bill_date BETWEEN v_date_start AND v_date_end
62
AND (p_bill_type_id IS NULL OR b.bill_type_id = p_bill_type_id)
63
GROUP BY month_start_date;
64
END;
65
$function$
|
|||||
| Function | get_bill_approval_log | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_approval_log(p_bill_id uuid)
2
RETURNS TABLE(status integer, status_name text, next_status integer, next_status_name text, previous_status integer, previous_status_name text, is_initbal boolean, approver_user_id uuid, approver_user_name text, approved_by uuid, approved_by_name text, approved_on timestamp without time zone, bill_id uuid, sales_account uuid, created_by_name text, approval_level integer)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
IF EXISTS (SELECT 1 FROM public.bill_headers WHERE id = p_bill_id) THEN
7
RETURN QUERY
8
SELECT
9
bw.status,
10
cs.name::TEXT AS status_name,
11
bw.next_status,
12
ns.name::TEXT AS next_status_name,
13
bw.previous_status,
14
ps.name::TEXT AS previous_status_name,
15
bw.is_initial,
16
COALESCE(baua.user_id, bauc.user_id) AS approver_user_id,
17
CONCAT(COALESCE(uaa.first_name, uca.first_name), ' ', COALESCE(uaa.last_name, uca.last_name))::TEXT AS approver_user_name,
18
bal.approved_by,
19
CONCAT(approved_by_user.first_name, ' ', approved_by_user.last_name)::TEXT AS approved_by_name,
20
bal.approved_on,
21
bh.id AS bill_id,
22
bh.credit_account_id AS sales_account,
23
CONCAT(created_by_user.first_name, ' ', created_by_user.last_name)::TEXT AS created_by_name,
24
bw.approval_level
25
FROM bill_workflow bw
26
JOIN bill_headers bh ON bh.id = p_bill_id
27
LEFT JOIN bill_statuses cs ON cs.id = bw.status
28
LEFT JOIN bill_statuses ns ON ns.id = bw.next_status
29
LEFT JOIN bill_statuses ps ON ps.id = bw.previous_status
30
LEFT JOIN bill_approval_user_company bauc
31
ON bauc.company_id = bh.company_id
32
AND bauc.status_id = bw.status
33
AND bauc.approval_level = bw.approval_level
34
LEFT JOIN bill_approval_user_account baua
35
ON baua.company_id = bh.company_id
36
AND baua.account_id = bh.credit_account_id
37
AND baua.status_id = bw.status
38
AND baua.approval_level = bw.approval_level
39
LEFT JOIN users uca ON uca.id = bauc.user_id
40
LEFT JOIN users uaa ON uaa.id = baua.user_id
41
LEFT JOIN users created_by_user ON created_by_user.id = bh.created_by
42
LEFT JOIN LATERAL (
43
SELECT * FROM bill_approval_logs log
44
WHERE log.bill_id = bh.id
45
AND log.status_id = bw.status
46
AND log.approval_level = bw.approval_level
47
ORDER BY log.approved_on DESC
48
LIMIT 1
49
) bal ON true
50
LEFT JOIN users approved_by_user ON approved_by_user.id = bal.approved_by
51
WHERE bw.company_id = bh.company_id
52
AND bw.is_deleted = false
53
AND bw.is_enabled = true
54
ORDER BY bw.approval_level;
55
56
ELSE
57
RETURN QUERY
58
SELECT
59
bw.status,
60
cs.name::TEXT AS status_name,
61
bw.next_status,
62
ns.name::TEXT AS next_status_name,
63
bw.previous_status,
64
ps.name::TEXT AS previous_status_name,
65
bw.is_initial,
66
COALESCE(baua.user_id, bauc.user_id) AS approver_user_id,
67
CONCAT(COALESCE(uaa.first_name, uca.first_name), ' ', COALESCE(uaa.last_name, uca.last_name))::TEXT AS approver_user_name,
68
NULL::UUID AS approved_by,
69
NULL::TEXT AS approved_by_name,
70
NULL::TIMESTAMP AS approved_on,
71
dbh.id AS bill_id,
72
dbh.credit_account_id AS sales_account,
73
CONCAT(created_by_user.first_name, ' ', created_by_user.last_name)::TEXT AS created_by_name,
74
bw.approval_level
75
FROM bill_workflow bw
76
JOIN draft_bill_headers dbh ON dbh.id = p_bill_id
77
LEFT JOIN bill_statuses cs ON cs.id = bw.status
78
LEFT JOIN bill_statuses ns ON ns.id = bw.next_status
79
LEFT JOIN bill_statuses ps ON ps.id = bw.previous_status
80
LEFT JOIN bill_approval_user_company bauc
81
ON bauc.company_id = dbh.company_id
82
AND bauc.status_id = bw.status
83
AND bauc.approval_level = bw.approval_level
84
LEFT JOIN bill_approval_user_account baua
85
ON baua.company_id = dbh.company_id
86
AND baua.account_id = dbh.credit_account_id
87
AND baua.status_id = bw.status
88
AND baua.approval_level = bw.approval_level
89
LEFT JOIN users uca ON uca.id = bauc.user_id
90
LEFT JOIN users uaa ON uaa.id = baua.user_id
91
LEFT JOIN users created_by_user ON created_by_user.id = dbh.created_by
92
WHERE bw.company_id = dbh.company_id
93
AND bw.is_deleted = false
94
AND bw.is_enabled = true
95
ORDER BY bw.approval_level;
96
END IF;
97
END;
98
$function$
|
|||||
| Function | get_new_vendor_advance_number | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_new_vendor_advance_number(p_company_id uuid, p_date date)
2
RETURNS character varying
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_finance_year integer;
7
new_advance_id integer;
8
p_prefix varchar;
9
advance_number varchar;
10
BEGIN
11
-- Determine the start year of the financial year based on p_date
12
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
13
v_finance_year := EXTRACT(YEAR FROM p_date);
14
ELSE
15
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
16
END IF;
17
18
-- Attempt to update existing row or insert if not found
19
WITH x AS (
20
UPDATE public.vendor_advance_ids
21
SET last_advance_id = last_advance_id + 1
22
WHERE company_id = p_company_id AND fin_year = v_finance_year
23
RETURNING last_advance_id, advance_prefix
24
),
25
insert_x AS (
26
INSERT INTO public.vendor_advance_ids (
27
company_id, fin_year, advance_prefix, last_advance_id, advance_length
28
)
29
SELECT p_company_id, v_finance_year, 'VADV', 1, 8
30
WHERE NOT EXISTS (SELECT 1 FROM x)
31
RETURNING last_advance_id, advance_prefix
32
)
33
SELECT
34
COALESCE((SELECT last_advance_id FROM x LIMIT 1), (SELECT last_advance_id FROM insert_x LIMIT 1)),
35
COALESCE((SELECT advance_prefix FROM x LIMIT 1), (SELECT advance_prefix FROM insert_x LIMIT 1))
36
INTO new_advance_id, p_prefix;
37
38
-- Construct the final advance number (e.g., VADV0001)
39
advance_number := p_prefix || LPAD(new_advance_id::text, 4, '0');
40
41
RETURN advance_number;
42
END;
43
$function$
|
|||||
| Function | upsert_bill_workflow_and_config | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_bill_workflow_and_config(p_company_id uuid, p_config jsonb, p_user_id uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
item JSONB;
7
v_status INT;
8
BEGIN
9
FOR item IN SELECT * FROM jsonb_array_elements(p_config)
10
LOOP
11
-- Convert status to INT (used in both tables)
12
v_status := (item->>'Status')::INT;
13
14
-- First: Upsert into bill_workflow
15
IF EXISTS (
16
SELECT 1 FROM bill_workflow
17
WHERE company_id = p_company_id
18
AND status = (item->>'Status')::INT
19
AND next_status = (item->>'NextStatus')::INT
20
AND previous_status = (item->>'PreviousStatus')::INT
21
AND is_deleted = false
22
) THEN
23
UPDATE bill_workflow
24
SET
25
is_initial = (item->>'is_initial')::BOOL,
26
modified_on_utc = now(),
27
modified_by = p_user_id
28
WHERE company_id = p_company_id
29
AND status = (item->>'Status')::INT
30
AND next_status = (item->>'NextStatus')::INT
31
AND previous_status = (item->>'PreviousStatus')::INT
32
AND is_deleted = false;
33
ELSE
34
INSERT INTO public.bill_workflow
35
(company_id, status, next_status, previous_status, is_initial, created_on_utc, modified_on_utc,
36
deleted_on_utc, is_deleted, created_by, modified_by)
37
VALUES (
38
p_company_id,
39
(item->>'Status')::INT,
40
(item->>'NextStatus')::INT,
41
(item->>'PreviousStatus')::INT,
42
(item->>'IsInitial')::BOOL,
43
now(),
44
null,
45
null,
46
false,
47
p_user_id,
48
null
49
);
50
END IF;
51
52
-- Second: Upsert into bill_status_company_configs
53
INSERT INTO public.bill_status_company_configs
54
(company_id, status_id, is_enabled, created_on_utc, modified_on_utc, created_by, modified_by, is_deleted)
55
VALUES (
56
p_company_id,
57
v_status,
58
(item->>'IsEnabled')::BOOL,
59
now(),
60
null,
61
p_user_id,
62
null,
63
false
64
)
65
ON CONFLICT (company_id, status_id)
66
DO UPDATE SET
67
is_enabled = EXCLUDED.is_enabled,
68
modified_on_utc = now(),
69
modified_by = p_user_id;
70
END LOOP;
71
END;
72
$function$
|
|||||
| Function | check_bill_approval_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.check_bill_approval_permissions(p_company_id uuid, p_status_id integer, p_user_id uuid, p_account_id uuid, p_approval_level integer)
2
RETURNS TABLE(company_id uuid, status_id integer, user_id uuid, approval_level integer, account_id uuid, account_status_id integer, permission_check text)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_has_permission BOOLEAN;
7
BEGIN
8
-- First check for account-level permissions
9
RETURN QUERY
10
SELECT
11
baua.company_id,
12
baua.status_id,
13
baua.user_id,
14
baua.approval_level,
15
baua.account_id,
16
baua.status_id AS account_status_id,
17
'Account-level permission granted' AS permission_check
18
FROM public.bill_approval_user_account baua
19
WHERE baua.company_id = p_company_id
20
AND baua.status_id = p_status_id
21
AND baua.user_id = p_user_id
22
AND baua.account_id = p_account_id
23
AND baua.approval_level = p_approval_level
24
AND baua.is_deleted = false;
25
26
-- If no rows found at account-level, check company-level
27
IF NOT FOUND THEN
28
RETURN QUERY
29
SELECT
30
bauc.company_id,
31
bauc.status_id,
32
bauc.user_id,
33
bauc.approval_level,
34
NULL::uuid AS account_id,
35
bauc.status_id AS account_status_id,
36
'Company-level permission granted' AS permission_check
37
FROM public.bill_approval_user_company bauc
38
WHERE bauc.company_id = p_company_id
39
AND bauc.status_id = p_status_id
40
AND bauc.user_id = p_user_id
41
AND bauc.approval_level = p_approval_level
42
AND bauc.is_deleted = false;
43
END IF;
44
END;
45
$function$
|
|||||
| Function | get_all_bill_headers_by_vendor_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_bill_headers_by_vendor_id(p_vendor_id uuid, p_financial_year integer)
2
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, debit_account_id uuid, bill_date timestamp without time zone, vendor_id uuid, vendor_name text, due_date timestamp without time zone, payment_term integer, note text, currency_id integer, discount numeric, bill_status_id integer, bill_status text, total_amount numeric, tds_amount numeric, vendor_paid_amount numeric, tds_paid_amount numeric, is_vendor_paid boolean, is_tds_paid boolean, is_closed boolean, total_paid_amount numeric, vendor_net_amount numeric, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, bill_type_id integer, current_approval_level integer)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
v_is_tds_vendor boolean;
8
start_date date := make_date(p_financial_year, 4, 1);
9
end_date date := make_date(p_financial_year + 1, 3, 31);
10
v_company_id uuid;
11
v_org_id uuid;
12
BEGIN
13
-- Get is_tds_vendor flag and company_id for the vendor
14
SELECT v.is_tds_vendor,
15
v.company_id
16
INTO v_is_tds_vendor, v_company_id
17
FROM public.vendors v
18
WHERE v.id = p_vendor_id;
19
20
-- Get organization_id of the company related to the vendor
21
SELECT c.organization_id
22
INTO v_org_id
23
FROM public.companies c
24
WHERE c.id = v_company_id;
25
26
IF NOT v_is_tds_vendor THEN
27
RETURN QUERY
28
SELECT
29
bh.id AS id,
30
bh.bill_number AS bill_number,
31
bh.bill_voucher AS bill_voucher,
32
bh.debit_account_id AS debit_account_id,
33
bh.bill_date AS bill_date,
34
v.id AS vendor_id,
35
v.name::text AS vendor_name,
36
bh.due_date AS due_date,
37
bh.payment_term AS payment_term,
38
bh.note AS note,
39
bh.currency_id AS currency_id,
40
bh.discount AS discount,
41
bs.id AS bill_status_id,
42
bs.name::text AS bill_status,
43
bh.total_amount AS total_amount,
44
COALESCE(bh.tds_amount, 0) AS tds_amount,
45
COALESCE(bh.vendor_paid_amount, 0) AS vendor_paid_amount,
46
COALESCE(bh.tds_paid_amount, 0) AS tds_paid_amount,
47
COALESCE(bh.is_vendor_paid, false) AS is_vendor_paid,
48
COALESCE(bh.is_tds_paid, false) AS is_tds_paid,
49
COALESCE(bh.is_closed, false) AS is_closed,
50
COALESCE(bh.total_paid_amount, 0) AS total_paid_amount,
51
COALESCE(bh.vendor_net_amount, 0) AS vendor_net_amount,
52
bh.created_by AS created_by,
53
bh.created_on_utc AS created_on_utc,
54
bh.modified_by AS modified_by,
55
bh.modified_on_utc AS modified_on_utc,
56
bh.bill_type_id AS bill_type_id,
57
bh.current_approval_level AS current_approval_level
58
FROM public.bill_headers bh
59
INNER JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
60
INNER JOIN public.vendors v ON v.id = bh.vendor_id
61
WHERE bh.vendor_id = p_vendor_id
62
AND bh.bill_date BETWEEN start_date AND end_date
63
AND bh.bill_status_id IN (3, 4)
64
AND bh.is_deleted = false;
65
--AND bh.is_vendor_paid = false;
66
67
ELSE
68
RETURN QUERY
69
SELECT
70
bh.id AS id,
71
bh.bill_number AS bill_number,
72
bh.bill_voucher AS bill_voucher,
73
bh.debit_account_id AS debit_account_id,
74
bh.bill_date AS bill_date,
75
v.id AS vendor_id,
76
v.name::text AS vendor_name,
77
bh.due_date AS due_date,
78
bh.payment_term AS payment_term,
79
bh.note AS note,
80
bh.currency_id AS currency_id,
81
bh.discount AS discount,
82
bs.id AS bill_status_id,
83
bs.name::text AS bill_status,
84
bh.total_amount AS total_amount,
85
COALESCE(bh.tds_amount, 0) AS tds_amount,
86
COALESCE(bh.vendor_paid_amount, 0) AS vendor_paid_amount,
87
COALESCE(bh.tds_paid_amount, 0) AS tds_paid_amount,
88
COALESCE(bh.is_vendor_paid, false) AS is_vendor_paid,
89
COALESCE(bh.is_tds_paid, false) AS is_tds_paid,
90
COALESCE(bh.is_closed, false) AS is_closed,
91
COALESCE(bh.total_paid_amount, 0) AS total_paid_amount,
92
COALESCE(bh.vendor_net_amount, 0) AS vendor_net_amount,
93
bh.created_by AS created_by,
94
bh.created_on_utc AS created_on_utc,
95
bh.modified_by AS modified_by,
96
bh.modified_on_utc AS modified_on_utc,
97
bh.bill_type_id AS bill_type_id,
98
bh.current_approval_level AS current_approval_level
99
FROM public.bill_headers bh
100
INNER JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
101
INNER JOIN public.vendors v ON v.id = bh.vendor_id
102
WHERE bh.bill_date BETWEEN start_date AND end_date
103
AND bh.bill_status_id IN (3, 4, 5)
104
AND bh.is_tds_paid = false
105
AND COALESCE(bh.tds_amount, 0) > 0
106
AND bh.is_deleted = false
107
AND bh.company_id IN (
108
SELECT c.id
109
FROM public.companies c
110
WHERE c.organization_id = v_org_id
111
);
112
END IF;
113
END;
114
$function$
|
|||||
| Function | get_all_pending_tds_bill_headers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_pending_tds_bill_headers(p_financial_year integer)
2
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, debit_account_id uuid, bill_date timestamp without time zone, vendor_id uuid, vendor_name text, due_date timestamp without time zone, payment_term integer, note text, currency_id integer, discount numeric, bill_status_id integer, bill_status text, total_amount numeric, tds_amount numeric, setteled_amount numeric, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, bill_type_id integer, current_approval_level integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
start_date date := make_date(p_financial_year, 4, 1); -- 1 April of given year
7
end_date date := make_date(p_financial_year + 1, 3, 31); -- 31 March of next year
8
BEGIN
9
RETURN QUERY
10
SELECT
11
bh.id,
12
bh.bill_number,
13
bh.bill_voucher,
14
bh.debit_account_id,
15
bh.bill_date,
16
v.id,
17
v.name::text,
18
bh.due_date,
19
bh.payment_term,
20
bh.note,
21
bh.currency_id,
22
bh.discount,
23
bs.id,
24
bs.name::text,
25
bh.total_amount,
26
COALESCE(bh.tds_amount, 0),
27
-- CASE
28
-- WHEN COALESCE(bh.tds_amount, 0) = 0 THEN 0
29
-- ELSE ROUND((bh.setteled_amount / bh.total_amount) * bh.tds_amount)
30
-- END,
31
bh.setteled_amount,
32
bh.created_by,
33
bh.created_on_utc,
34
bh.modified_by,
35
bh.modified_on_utc,
36
bh.bill_type_id,
37
bh.current_approval_level
38
FROM public.bill_headers bh
39
JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
40
JOIN public.vendors v ON v.id = bh.vendor_id
41
WHERE
42
bh.bill_date >= start_date
43
AND bh.bill_date <= end_date
44
AND COALESCE(bh.tds_amount, 0) > 0
45
AND COALESCE(bh.is_tds_paid, false) = false
46
AND bh.setteled_amount > 0
47
--AND (COALESCE(bh.tds_amount, 0) - COALESCE(bh.paid_tds_amount, 0)) > 0
48
AND bh.is_deleted = false;
49
END;
50
$function$
|
|||||
| Function | get_all_tds_paid_bills | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_tds_paid_bills(p_company_id uuid, p_financial_year integer, p_start_date date DEFAULT NULL::date, p_end_date date DEFAULT NULL::date)
2
RETURNS TABLE(id uuid, bill_header_id uuid, bill_number text, vendor_id uuid, vendor_name text, tds_amount numeric, is_tds_paid boolean, total_paid_amount numeric, created_on_utc timestamp without time zone, created_by uuid, modified_on_utc timestamp without time zone, modified_by uuid, company_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
6
BEGIN
7
RETURN QUERY
8
SELECT
9
bh.id AS id,
10
bh.id AS bill_header_id,
11
bh.bill_number,
12
bh.vendor_id,
13
v.name::text AS vendor_name, -- <-- Join & select vendor name
14
bh.tds_amount,
15
bh.is_tds_paid,
16
bh.setteled_amount AS total_paid_amount,
17
bh.created_on_utc,
18
bh.created_by,
19
bh.modified_on_utc,
20
bh.modified_by,
21
bh.company_id
22
FROM bill_headers bh
23
INNER JOIN vendors v ON v.id = bh.vendor_id -- <-- Join
24
WHERE
25
bh.company_id = p_company_id
26
AND EXTRACT(YEAR FROM bh.bill_date) = p_financial_year
27
AND bh.is_tds_paid = true
28
AND bh.is_deleted = false
29
AND bh.tds_amount > 0
30
AND (p_start_date IS NULL OR bh.bill_date >= p_start_date)
31
AND (p_end_date IS NULL OR bh.bill_date <= p_end_date);
32
END;
33
$function$
|
|||||
| Function | get_all_vendor_advances | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_vendor_advances(p_company_id uuid, p_finance_year_id integer, p_only_unutilized boolean DEFAULT false)
2
RETURNS TABLE(id uuid, vendor_id uuid, vendor_name character varying, advance_number text, paid_date timestamp without time zone, amount numeric, utilized_amount numeric, mode_of_payment text, reference text, description text, currency_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_financial_year_start date;
7
v_financial_year_end date;
8
BEGIN
9
-- Start of financial year: April 1 of the given year
10
v_financial_year_start := TO_DATE(p_finance_year_id || '-04-01', 'YYYY-MM-DD');
11
12
-- End of financial year: March 31 of the next year
13
v_financial_year_end := TO_DATE((p_finance_year_id + 1) || '-03-31', 'YYYY-MM-DD');
14
15
RETURN QUERY
16
SELECT
17
va.id,
18
va.vendor_id,
19
v.name, -- Add vendor name
20
va.advance_number,
21
va.paid_date,
22
va.amount,
23
va.utilized_amount,
24
va.mode_of_payment,
25
va.reference,
26
va.description,
27
va.currency_id
28
FROM public.vendor_advances va
29
LEFT JOIN public.vendors v ON va.vendor_id = v.id -- Join here to get the name
30
WHERE va.company_id = p_company_id
31
AND va.is_deleted = false
32
AND v.is_deleted = false
33
AND va.paid_date >= v_financial_year_start
34
AND va.paid_date <= v_financial_year_end
35
AND (
36
(p_only_unutilized = true AND va.amount > va.utilized_amount)
37
OR (p_only_unutilized IS DISTINCT FROM true)
38
);
39
END;
40
$function$
|
|||||
| Function | get_bill_lines | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_lines(p_bill_header_id uuid, p_bill_status_id integer)
2
RETURNS TABLE(id uuid, product_id uuid, price numeric, quantity numeric, fees numeric, discount numeric, taxable_amount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, total_amount numeric, serial_number integer)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
IF p_bill_status_id >= 3 THEN
7
-- Query Approved Bill Details
8
RETURN QUERY
9
SELECT
10
bd.id,
11
bd.product_id,
12
bd.price,
13
bd.quantity,
14
bd.fees,
15
bd.discount,
16
bd.taxable_amount,
17
bd.sgst_amount,
18
bd.cgst_amount,
19
bd.igst_amount,
20
bd.total_amount,
21
bd.serial_number
22
FROM bill_details bd
23
WHERE bd.bill_header_id = p_bill_header_id AND bd.is_deleted = FALSE
24
ORDER BY bd.serial_number;
25
ELSE
26
-- Query Draft Bill Details
27
RETURN QUERY
28
SELECT
29
dbd.id,
30
dbd.product_id,
31
dbd.price,
32
dbd.quantity,
33
dbd.fees,
34
dbd.discount,
35
dbd.taxable_amount,
36
dbd.sgst_amount,
37
dbd.cgst_amount,
38
dbd.igst_amount,
39
dbd.total_amount,
40
dbd.serial_number
41
FROM draft_bill_details dbd
42
WHERE dbd.bill_header_id = p_bill_header_id AND dbd.is_deleted = FALSE
43
ORDER BY dbd.serial_number;
44
END IF;
45
END;
46
$function$
|
|||||
| Function | get_bill_payments_by_company_with_tds_check | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_bill_payments_by_company_with_tds_check(p_company_id uuid, p_is_tds_paid_bills boolean)
2
RETURNS TABLE(id uuid, vendor_name text, payment_number text, vendor_id uuid, paid_amount numeric, grand_total_amount numeric, advance_amount numeric, credit_account_id uuid, description text, mode_of_payment text, paid_date timestamp without time zone, reference text, created_by uuid, modified_by uuid, created_by_name text, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
6
BEGIN
7
--select is_tds_vendor into v_is_tds_vendor from vendors where id = p_vendor_id;
8
RETURN QUERY
9
SELECT
10
bph.id,
11
v.name :: text AS vendor_name,
12
bph.payment_number,
13
bph.vendor_id,
14
bph.paid_amount,
15
bph.grand_total_amount,
16
bph.advance_amount,
17
bph.credit_account_id,
18
bph.description,
19
bph.mode_of_payment,
20
bph.paid_date,
21
bph.reference,
22
bph.created_by,
23
bph.modified_by,
24
COALESCE(u1.first_name || ' ' || u1.last_name, '') AS created_by_name,
25
COALESCE(u2.first_name || ' ' || u2.last_name, '') AS modified_by_name,
26
bph.created_on_utc,
27
bph.modified_on_utc
28
FROM bill_payment_headers bph
29
LEFT JOIN vendors v ON v.id = bph.vendor_id
30
LEFT JOIN users u1 ON u1.id = bph.created_by
31
LEFT JOIN users u2 ON u2.id = bph.modified_by
32
WHERE bph.company_id = p_company_id
33
AND (bph.is_deleted IS FALSE OR bph.is_deleted IS NULL)
34
AND(
35
(p_is_tds_paid_bills = TRUE AND v.is_tds_vendor = TRUE)
36
or
37
(p_is_tds_paid_bills = FALSE)
38
);
39
40
END;
41
$function$
|
|||||
| Function | get_new_vendor_advance_settlement_number | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_new_vendor_advance_settlement_number(p_company_id uuid, p_date date)
2
RETURNS character varying
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_finance_year int;
7
new_id int;
8
v_prefix text := 'ADV'; -- 👈 default prefix
9
v_length int := 4; -- 👈 default length
10
number varchar;
11
BEGIN
12
-- derive financial year
13
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
14
v_finance_year := EXTRACT(YEAR FROM p_date);
15
ELSE
16
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
17
END IF;
18
19
-- upsert into vendor_advance_settlement_ids
20
WITH upd AS (
21
UPDATE public.vendor_advance_settlement_ids
22
SET last_settlement_id = last_settlement_id + 1
23
WHERE company_id = p_company_id AND fin_year = v_finance_year
24
RETURNING last_settlement_id, settlement_prefix, settlement_length
25
), ins AS (
26
INSERT INTO public.vendor_advance_settlement_ids(
27
company_id, fin_year, settlement_prefix, settlement_length, last_settlement_id
28
)
29
SELECT p_company_id, v_finance_year, v_prefix, v_length, 1
30
WHERE NOT EXISTS (SELECT 1 FROM upd)
31
RETURNING last_settlement_id, settlement_prefix, settlement_length
32
)
33
SELECT COALESCE(u.last_settlement_id, i.last_settlement_id),
34
COALESCE(u.settlement_prefix, i.settlement_prefix),
35
COALESCE(u.settlement_length, i.settlement_length)
36
INTO new_id, v_prefix, v_length
37
FROM upd u
38
FULL JOIN ins i ON true;
39
40
-- build formatted settlement number
41
number := v_prefix || LPAD(new_id::text, v_length, '0');
42
RETURN number;
43
END;
44
$function$
|
|||||
| Function | get_tds_bills_from_span | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_tds_bills_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_bill_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS TABLE(id uuid, bill_voucher text, bill_number text, vendor_name character varying, vendor_id uuid, due_date date, bill_date date, payment_term integer, total_amount numeric, total_paid_amount numeric, currency_id integer, bill_status character varying, bill_status_id integer, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, created_by_name character varying, modified_by_name character varying, bill_type character varying, bill_type_id integer, tds_amount numeric, has_next_status_approval boolean, next_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
7
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
8
DRAFT_STATUS CONSTANT integer := 1;
9
BEGIN
10
RETURN QUERY
11
SELECT
12
bh.id,
13
bh.bill_voucher,
14
bh.bill_number,
15
v.name AS vendor_name,
16
bh.vendor_id,
17
bh.due_date::date,
18
bh.bill_date::date,
19
bh.payment_term,
20
bh.total_amount,
21
bh.total_paid_amount,
22
bh.currency_id,
23
bs.name::varchar AS bill_status,
24
bh.bill_status_id,
25
bh.created_by,
26
bh.created_on_utc,
27
bh.modified_by,
28
bh.modified_on_utc,
29
CONCAT(cu.first_name, ' ', cu.last_name)::varchar AS created_by_name,
30
CONCAT(mu.first_name, ' ', mu.last_name)::varchar AS modified_by_name,
31
bt.name::varchar AS bill_type,
32
bh.bill_type_id,
33
bh.tds_amount,
34
CASE
35
WHEN bh.bill_status_id = DRAFT_STATUS THEN true
36
WHEN EXISTS (
37
SELECT 1
38
FROM bill_approval_user_account a
39
WHERE a.account_id = bh.debit_account_id
40
AND a.status_id = bh.bill_status_id
41
AND a.user_id = p_user_id
42
AND a.is_deleted = false
43
) THEN true
44
WHEN EXISTS (
45
SELECT 1
46
FROM bill_approval_user_company c
47
WHERE c.company_id = p_company_id
48
AND c.status_id = bh.bill_status_id
49
AND c.user_id = p_user_id
50
AND c.is_deleted = false
51
) THEN true
52
ELSE false
53
END AS has_next_status_approval,
54
COALESCE(
55
(SELECT bw.next_status
56
FROM bill_workflow bw
57
WHERE bw.company_id = p_company_id
58
AND bw.status = bh.bill_status_id
59
AND bw.is_deleted = false
60
AND (bw.is_enabled IS DISTINCT FROM FALSE)
61
ORDER BY bw.approval_level ASC NULLS LAST
62
LIMIT 1),
63
0
64
) AS next_status_id
65
FROM public.bill_headers bh
66
LEFT JOIN public.vendors v ON v.id = bh.vendor_id
67
LEFT JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
68
LEFT JOIN public.bill_types bt ON bt.id = bh.bill_type_id
69
LEFT JOIN users cu ON cu.id = bh.created_by
70
LEFT JOIN users mu ON mu.id = bh.modified_by
71
WHERE bh.company_id = p_company_id
72
AND bh.is_deleted = false
73
AND bh.tds_amount IS NOT NULL
74
AND bh.tds_amount > 0
75
AND (p_bill_type_id IS NULL OR p_bill_type_id = 0 OR bh.bill_type_id = p_bill_type_id)
76
AND bh.bill_date BETWEEN v_start_date AND v_end_date;
77
END;
78
$function$
|
|||||
| Procedure | create_bill_payment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_bill_payment(IN p_bill_payment_header_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_paid_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_paid_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_bill_payment_details jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
5
DECLARE
6
v_bill_status_id INT;
7
v_bill_header_id UUID;
8
v_payment_amount NUMERIC;
9
v_tds_amount NUMERIC;
10
v_serial_number INT;
11
v_row JSONB;
12
v_payment_number character varying;
13
v_is_tds_vendor bool;
14
v_is_tds_paid bool;
15
v_bill_total_amount NUMERIC;
16
v_bill_tds_amount NUMERIC;
17
v_vendor_paid_amount NUMERIC;
18
v_tds_paid_amount NUMERIC;
19
v_tds_status_id INT;
20
v_payment_status_id INT;
21
v_vendor_net_amount NUMERIC;
22
BEGIN
23
-- Generate new payment number
24
v_payment_number := get_new_payment_number(p_company_id, p_paid_date);
25
26
-- Check if vendor is TDS vendor
27
SELECT is_tds_vendor
28
INTO v_is_tds_vendor
29
FROM vendors
30
WHERE id = p_vendor_id;
31
32
-- Insert payment header
33
INSERT INTO public.bill_payment_headers (
34
id,
35
company_id,
36
vendor_id,
37
paid_date,
38
mode_of_payment,
39
credit_account_id,
40
debit_account_id,
41
reference,
42
paid_amount,
43
description,
44
tds_amount,
45
advance_amount,
46
grand_total_amount,
47
payment_number,
48
created_by,
49
created_on_utc,
50
is_deleted
51
)
52
VALUES (
53
p_bill_payment_header_id,
54
p_company_id,
55
p_vendor_id,
56
p_paid_date,
57
p_mode_of_payment,
58
p_credit_account_id,
59
p_debit_account_id,
60
p_reference,
61
p_paid_amount,
62
p_description,
63
p_tds_amount,
64
p_advance_amount,
65
p_grand_total_amount,
66
v_payment_number,
67
p_created_by,
68
now(),
69
false
70
);
71
72
-- Loop through each bill payment detail record
73
FOR v_row IN SELECT * FROM jsonb_array_elements(p_bill_payment_details)
74
LOOP
75
v_bill_header_id := (v_row->>'header_id')::UUID;
76
v_payment_amount := COALESCE((v_row->>'payment_amount')::NUMERIC, 0);
77
v_tds_amount := COALESCE((v_row->>'tds_amount')::NUMERIC, 0);
78
v_serial_number := (v_row->>'serial_number')::INT;
79
v_is_tds_paid := COALESCE((v_row->>'is_tds_paid')::BOOLEAN, false);
80
81
-- Fetch current amounts from bill header
82
SELECT vendor_paid_amount, total_amount, tds_amount, tds_paid_amount, vendor_net_amount
83
INTO v_vendor_paid_amount, v_bill_total_amount, v_bill_tds_amount, v_tds_paid_amount, v_vendor_net_amount
84
FROM public.bill_headers
85
WHERE id = v_bill_header_id;
86
87
-- Insert payment details record
88
INSERT INTO public.bill_payment_details (
89
id,
90
bill_payment_header_id,
91
bill_header_id,
92
paid_amount,
93
tds_amount,
94
serial_number,
95
created_by,
96
created_on_utc
97
)
98
VALUES (
99
(v_row->>'id')::uuid,
100
p_bill_payment_header_id,
101
v_bill_header_id,
102
v_payment_amount,
103
v_tds_amount,
104
v_serial_number,
105
p_created_by,
106
now()
107
);
108
109
-- Update paid amounts based on payment type
110
IF v_is_tds_vendor OR v_is_tds_paid THEN
111
v_tds_paid_amount := COALESCE(v_tds_paid_amount, 0) + v_payment_amount;
112
ELSE
113
v_vendor_paid_amount := COALESCE(v_vendor_paid_amount, 0) + v_payment_amount;
114
END IF;
115
116
-- Status calculations using updated paid amounts (after applying current payment)
117
118
-- Bill payment status
119
IF (v_vendor_paid_amount >= (v_bill_total_amount - v_bill_tds_amount))
120
AND (v_tds_paid_amount >= v_bill_tds_amount) THEN
121
v_bill_status_id := 5; -- PAID
122
ELSE
123
v_bill_status_id := 4; -- PARTIALLY_PAID
124
END IF;
125
126
-- TDS status for TDS vendors
127
IF v_is_tds_vendor THEN
128
IF v_tds_paid_amount = 0 AND v_bill_tds_amount > 0 THEN
129
v_tds_status_id := 2; -- Unpaid
130
ELSIF v_tds_paid_amount > 0 AND v_tds_paid_amount = v_bill_tds_amount THEN
131
v_tds_status_id := 3; -- Deducted
132
ELSE
133
v_tds_status_id := 1; -- N/A fallback
134
END IF;
135
ELSE
136
v_tds_status_id := 1; -- N/A for non-TDS vendors
137
END IF;
138
139
-- Payment status assignment (now covers both TDS and non-TDS vendors)
140
IF NOT v_is_tds_vendor THEN
141
IF v_vendor_net_amount > 0 AND v_vendor_paid_amount = 0 THEN
142
v_payment_status_id := 1; -- Unpaid
143
ELSIF v_vendor_net_amount > 0 AND v_vendor_paid_amount > 0 AND v_vendor_paid_amount < v_vendor_net_amount THEN
144
v_payment_status_id := 2; -- PartiallyPaid
145
ELSIF v_vendor_net_amount = v_vendor_paid_amount THEN
146
v_payment_status_id := 3; -- FullyPaid
147
ELSE
148
v_payment_status_id := 1; -- Default Unpaid fallback
149
END IF;
150
ELSE
151
-- TDS vendors
152
IF v_vendor_net_amount = v_vendor_paid_amount THEN
153
v_payment_status_id := 3; -- FullyPaid
154
ELSIF v_vendor_paid_amount > 0 THEN
155
v_payment_status_id := 2; -- PartiallyPaid
156
ELSE
157
v_payment_status_id := 1; -- Unpaid
158
END IF;
159
END IF;
160
161
-- Final safeguard to avoid nulls
162
v_payment_status_id := COALESCE(v_payment_status_id, 1);
163
164
-- Update bill header with new payment and status details
165
UPDATE public.bill_headers
166
SET vendor_paid_amount = v_vendor_paid_amount,
167
tds_paid_amount = v_tds_paid_amount,
168
bill_status_id = v_bill_status_id,
169
tds_status_id = CASE
170
WHEN v_is_tds_vendor THEN v_tds_status_id
171
ELSE 1 -- N/A for non-TDS vendors
172
END,
173
payment_status_id = CASE
174
WHEN v_is_tds_vendor THEN payment_status_id -- leave unchanged for TDS vendors
175
ELSE v_payment_status_id -- update for non-TDS vendors
176
END
177
WHERE id = v_bill_header_id;
178
179
END LOOP;
180
181
RAISE NOTICE 'Bill payment processed successfully';
182
END;
183
$procedure$
|
|||||
| Procedure | create_draft_bill_detail | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_draft_bill_detail(IN p_bill_header_id uuid, IN p_price numeric, IN p_quantity numeric, IN p_discount numeric, IN p_fees numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_sgst_amount numeric, IN p_taxable_amount numeric, IN p_total_amount numeric, IN p_serial_number integer, IN p_product_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
p_id uuid;
6
BEGIN
7
-- Generate a new UUID for the detail entry
8
p_id := gen_random_uuid();
9
10
-- Insert into draft_bill_details table
11
INSERT INTO public.draft_bill_details(
12
id,
13
bill_header_id,
14
price,
15
quantity,
16
cgst_amount,
17
discount,
18
fees,
19
igst_amount,
20
sgst_amount,
21
taxable_amount,
22
total_amount,
23
serial_number,
24
product_id,
25
is_deleted
26
)
27
VALUES (
28
p_id,
29
p_bill_header_id,
30
p_price,
31
p_quantity,
32
p_cgst_amount,
33
p_discount,
34
p_fees,
35
p_igst_amount,
36
p_sgst_amount,
37
p_taxable_amount,
38
p_total_amount,
39
p_serial_number,
40
p_product_id,
41
false
42
);
43
44
-- Log the success
45
RAISE NOTICE 'Draft bill detail inserted with ID: %', p_id;
46
47
END;
48
$procedure$
|
|||||
| Procedure | create_multiple_bill_payments | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_multiple_bill_payments(IN p_bill_payments jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
bill_payment RECORD;
6
payment_statuses TEXT[] := ARRAY[]::TEXT[];
7
BEGIN
8
FOR bill_payment IN
9
SELECT * FROM jsonb_to_recordset(p_bill_payments) AS (
10
bill_payment_header_id uuid,
11
company_id uuid,
12
vendor_id uuid,
13
paid_date date,
14
paid_amount numeric,
15
grand_total_amount numeric,
16
tds_amount numeric,
17
advance_amount numeric,
18
description text,
19
debit_account_id uuid,
20
credit_account_id uuid,
21
mode_of_payment text,
22
reference text,
23
created_by uuid,
24
detail_lines jsonb
25
)
26
LOOP
27
BEGIN
28
IF NOT EXISTS (
29
SELECT 1
30
FROM public.bill_payment_headers
31
WHERE reference = bill_payment.reference
32
AND company_id = bill_payment.company_id
33
) THEN
34
CALL public.create_bill_payment(
35
bill_payment.bill_payment_header_id,
36
bill_payment.company_id,
37
bill_payment.vendor_id,
38
bill_payment.paid_date,
39
bill_payment.mode_of_payment,
40
bill_payment.credit_account_id,
41
bill_payment.debit_account_id,
42
bill_payment.reference,
43
bill_payment.paid_amount,
44
bill_payment.grand_total_amount,
45
bill_payment.advance_amount,
46
bill_payment.description,
47
bill_payment.tds_amount,
48
bill_payment.created_by,
49
bill_payment.detail_lines
50
);
51
payment_statuses := payment_statuses || format('%s:success', bill_payment.bill_payment_header_id);
52
ELSE
53
RAISE NOTICE 'Duplicate bill payment found for reference: %, company_id: %. Skipping.', bill_payment.reference, bill_payment.company_id;
54
payment_statuses := payment_statuses || format('%s:duplicate', bill_payment.bill_payment_header_id);
55
CONTINUE;
56
END IF;
57
EXCEPTION
58
WHEN OTHERS THEN
59
RAISE NOTICE 'An error occurred while processing bill payment ID: %, Error: %', bill_payment.bill_payment_header_id, SQLERRM;
60
payment_statuses := payment_statuses || format('%s:failed', bill_payment.bill_payment_header_id);
61
END;
62
END LOOP;
63
64
RAISE NOTICE 'BILL_PAYMENT_STATUS:%', to_json(payment_statuses);
65
RAISE NOTICE 'All bill payments processed.';
66
END;
67
$procedure$
|
|||||
| Procedure | create_vendor_advance | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_vendor_advance(IN p_vendor_advance_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_paid_date date, IN p_mode_of_payment text, IN p_through_account_id uuid, IN p_reference text, IN p_amount numeric, IN p_description text, IN p_currency_id integer, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_advance_number varchar;
6
BEGIN
7
-- Generate a new vendor advance number for this company & date
8
v_advance_number := public.get_new_vendor_advance_number(p_company_id, p_paid_date);
9
10
-- Insert into vendor_advances
11
INSERT INTO vendor_advances
12
(
13
id,
14
company_id,
15
vendor_id,
16
advance_number,
17
paid_date,
18
amount,
19
mode_of_payment,
20
through_account_id,
21
reference,
22
description,
23
currency_id,
24
utilized_amount,
25
created_on_utc,
26
created_by
27
)
28
VALUES
29
(
30
p_vendor_advance_id,
31
p_company_id,
32
p_vendor_id,
33
v_advance_number,
34
p_paid_date,
35
p_amount,
36
p_mode_of_payment,
37
p_through_account_id,
38
NULLIF(p_reference, ''),
39
NULLIF(p_description, ''),
40
p_currency_id,
41
0.0, -- initialize utilized_amount
42
now(),
43
p_created_by
44
);
45
END;
46
$procedure$
|
|||||
| Procedure | delete_bill_data_by_company_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.delete_bill_data_by_company_id(IN p_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Deleting from bill_payment_details
6
DELETE FROM public.bill_payment_details
7
USING public.bill_payment_headers
8
WHERE bill_payment_details.bill_payment_header_id = bill_payment_headers.id
9
AND bill_payment_headers.company_id = p_company_id;
10
11
-- Deleting from bill_payment_headers
12
DELETE FROM public.bill_payment_headers
13
WHERE company_id = p_company_id;
14
15
-- Deleting from bill_details
16
DELETE FROM public.bill_details
17
USING public.bill_headers
18
WHERE bill_details.bill_header_id = bill_headers.id
19
AND bill_headers.company_id = p_company_id;
20
21
-- Deleting from bill_headers
22
DELETE FROM public.bill_headers
23
WHERE company_id = p_company_id;
24
25
-- Deleting from draft_bill_details
26
DELETE FROM public.draft_bill_details
27
USING public.draft_bill_headers
28
WHERE draft_bill_details.bill_header_id = draft_bill_headers.id
29
AND draft_bill_headers.company_id = p_company_id;
30
31
-- Deleting from draft_bill_headers
32
DELETE FROM public.draft_bill_headers
33
WHERE company_id = p_company_id;
34
35
END;
36
$procedure$
|
|||||
| Procedure | delete_vendor_notes_by_company | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.delete_vendor_notes_by_company(IN p_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Delete from vendor_note_details first to maintain referential integrity
6
DELETE FROM public.vendor_note_details
7
WHERE vendor_note_header_id IN (
8
SELECT id FROM public.vendor_note_headers WHERE company_id = p_company_id
9
);
10
11
-- Delete from vendor_note_headers
12
DELETE FROM public.vendor_note_headers
13
WHERE company_id = p_company_id;
14
15
RAISE NOTICE 'Data deleted successfully for company_id: %', p_company_id;
16
END;
17
$procedure$
|
|||||
| Procedure | initialize_bill_header_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_bill_header_ids(IN p_default_company_id uuid, IN p_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_header_exists BOOLEAN;
6
BEGIN
7
-- Check if bill header IDs already exist for the new company
8
SELECT EXISTS (
9
SELECT 1
10
FROM bill_header_ids
11
WHERE company_id = p_company_id
12
) INTO v_header_exists;
13
14
IF NOT v_header_exists THEN
15
16
-- Insert new records for the new company
17
INSERT INTO bill_header_ids (
18
id,
19
company_id,
20
fin_year,
21
bill_prefix,
22
bill_length,
23
last_bill_id
24
)
25
SELECT
26
nextval('bill_header_ids_id_seq'), -- Generate new sequential ID
27
p_company_id, -- Assign the new company ID
28
fin_year,
29
bill_prefix,
30
bill_length,
31
0
32
FROM bill_header_ids
33
WHERE company_id = p_default_company_id;
34
35
RAISE NOTICE 'Bill header IDs initialized successfully for new company ID: %', p_company_id;
36
ELSE
37
RAISE NOTICE 'Bill header IDs already exist for company ID: %. Skipping initialization.', p_company_id;
38
END IF;
39
END
40
$procedure$
|
|||||
| Procedure | initialize_bill_payment_header_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_bill_payment_header_ids(IN p_default_company_id uuid, IN p_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_header_exists BOOLEAN;
6
BEGIN
7
-- Check if bill payment header IDs already exist for the new company
8
SELECT EXISTS (
9
SELECT 1
10
FROM bill_payment_header_ids
11
WHERE company_id = p_company_id
12
) INTO v_header_exists;
13
14
IF NOT v_header_exists THEN
15
PERFORM setval(
16
'bill_payment_header_ids_id_seq', -- Sequence name
17
(SELECT COALESCE(MAX(id), 0) FROM bill_payment_header_ids), -- Current max ID in the table
18
true -- Ensure the sequence is ready to return the next value
19
);
20
21
-- Insert new records for the new company
22
INSERT INTO bill_payment_header_ids (
23
id,
24
company_id,
25
fin_year,
26
payment_prefix,
27
payment_length,
28
last_payment_id
29
)
30
SELECT
31
nextval('bill_payment_header_ids_id_seq'), -- Generate new sequential ID
32
p_company_id, -- Assign the new company ID
33
fin_year,
34
payment_prefix,
35
payment_length,
36
0
37
FROM bill_payment_header_ids
38
WHERE company_id = p_default_company_id;
39
40
RAISE NOTICE 'Bill payment header IDs initialized successfully for new company ID: %', p_company_id;
41
ELSE
42
RAISE NOTICE 'Bill payment header IDs already exist for company ID: %. Skipping initialization.', p_company_id;
43
END IF;
44
END
45
$procedure$
|
|||||
| Procedure | initialize_bill_workflow | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_bill_workflow(IN p_default_company_id uuid, IN p_company_id uuid, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_workflow_exists BOOLEAN;
6
BEGIN
7
-- Check if bill workflow records already exist for the new company
8
SELECT EXISTS (
9
SELECT 1
10
FROM public.bill_workflow
11
WHERE company_id = p_company_id
12
) INTO v_workflow_exists;
13
14
IF NOT v_workflow_exists THEN
15
-- Insert new records for the new company
16
INSERT INTO public.bill_workflow (
17
id,
18
company_id,
19
status,
20
next_status,
21
previous_status,
22
is_initial,
23
created_on_utc,
24
created_by,
25
approval_level
26
)
27
SELECT
28
nextval('bill_workflow_id_seq'), -- Generate new sequential ID
29
p_company_id, -- Assign the new company ID
30
status,
31
next_status,
32
previous_status,
33
is_initial,
34
NOW(),
35
p_created_by, -- Use the provided created_by UUID
36
approval_level
37
FROM public.bill_workflow -- The approval_level from the default company
38
WHERE company_id = p_default_company_id;
39
40
-- Optional: Log the operation
41
RAISE NOTICE 'Bill workflow records have been copied from company % to company % by user %.', p_default_company_id, p_company_id, p_created_by;
42
ELSE
43
RAISE NOTICE 'Bill workflow records already exist for company %. Skipping initialization.', p_company_id;
44
END IF;
45
46
END;
47
$procedure$
|
|||||
| Procedure | initialize_draft_bill_header_ids | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_draft_bill_header_ids(IN p_default_company_id uuid, IN p_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_header_exists BOOLEAN;
6
BEGIN
7
-- Check if draft bill header IDs already exist for the new company
8
SELECT EXISTS (
9
SELECT 1
10
FROM draft_bill_header_ids
11
WHERE company_id = p_company_id
12
) INTO v_header_exists;
13
14
IF NOT v_header_exists THEN
15
-- Insert new records for the new company
16
INSERT INTO draft_bill_header_ids (
17
id,
18
company_id,
19
fin_year,
20
bill_prefix,
21
bill_length,
22
last_bill_id
23
)
24
SELECT
25
nextval('draft_bill_header_ids_id_seq'),
26
p_company_id, -- Assign the new company ID
27
fin_year,
28
bill_prefix,
29
bill_length,
30
last_bill_id
31
FROM draft_bill_header_ids
32
WHERE company_id = p_default_company_id;
33
34
RAISE NOTICE 'Draft bill header IDs initialized successfully for new company ID: %', p_company_id;
35
ELSE
36
RAISE NOTICE 'Draft bill header IDs already exist for company ID: %. Skipping initialization.', p_company_id;
37
END IF;
38
END
39
$procedure$
|
|||||
| Procedure | initialize_organization | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_organization(IN p_id uuid, IN p_name text, IN p_company_ids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number text, IN p_email text, IN p_created_by uuid, IN p_default_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
5
DECLARE
6
v_company_id uuid; -- Variable for iterating through company IDs
7
v_company_name text; -- Variable for iterating through company names
8
v_company_ids uuid[]; -- Array to hold parsed company IDs
9
v_company_names text[]; -- Array to hold parsed company names
10
i integer; -- Iterator for looping
11
v_organization_exists boolean;
12
v_user_exists boolean;
13
14
BEGIN
15
-- Check if organization already exists by ID
16
SELECT EXISTS (
17
SELECT 1 FROM public.organizations WHERE id = p_id OR (id = p_id AND name = p_name)
18
) INTO v_organization_exists;
19
20
IF v_organization_exists THEN
21
RAISE NOTICE 'Organization with ID % already exists. Skipping initialization.', p_id;
22
ELSE
23
-- Insert into organizations table
24
INSERT INTO public.organizations (
25
id,
26
name,
27
created_on_utc,
28
created_by
29
) VALUES (
30
p_id,
31
p_name,
32
NOW(),
33
p_created_by
34
);
35
36
RAISE NOTICE 'Initialized organization: % with ID: %', p_name, p_id;
37
END IF;
38
39
-- Parse company IDs and names
40
v_company_ids := string_to_array(p_company_ids, ',');
41
v_company_names := string_to_array(p_company_names, ',');
42
43
-- Loop through each company and initialize
44
FOR i IN 1..array_length(v_company_ids, 1) LOOP
45
v_company_id := v_company_ids[i];
46
v_company_name := v_company_names[i];
47
48
-- Call initialize_company for each company
49
CALL public.initialize_company(
50
v_company_id, -- Company ID passed as parameter
51
p_id, -- Organization ID
52
v_company_name, -- Organization Name
53
true, -- Is Apartment
54
p_created_by, -- Created by user
55
p_default_company_id -- Old Company ID passed as parameter
56
);
57
58
END LOOP;
59
60
v_company_id := v_company_ids[1];
61
62
-- Check if user already exists by ID or Email before creating
63
SELECT EXISTS (
64
SELECT 1 FROM public.users WHERE id = p_user_id OR email = p_email
65
) INTO v_user_exists;
66
67
IF NOT v_user_exists THEN
68
-- Call create_user function to set up the user if they don't exist
69
PERFORM public.create_user(
70
p_user_id,
71
p_email,
72
p_phone_number,
73
p_user_first_name,
74
p_user_last_name,
75
p_created_by,
76
v_company_id
77
);
78
RAISE NOTICE 'Initialized user with ID: % and email: % in organization: %', p_user_id, p_email, p_name;
79
ELSE
80
UPDATE public.users
81
SET company_id = v_company_id
82
WHERE id = p_user_id OR email = p_email;
83
RAISE NOTICE 'User with ID % or email % already exists. Skipping user creation.', p_user_id, p_email;
84
END IF;
85
86
END;
87
$procedure$
|
|||||
| Procedure | update_bill_previous_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_bill_previous_status(IN p_company_id uuid, IN p_bill_status_id integer, IN p_bill_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill RECORD;
6
v_minimum_status INTEGER := 0; -- Minimum allowed status
7
BEGIN
8
-- Process draft bills for previous status update
9
FOR v_bill IN
10
SELECT id, bill_status_id
11
FROM public.draft_bill_headers
12
WHERE id = ANY(p_bill_ids)
13
LOOP
14
-- Ensure the status doesn't go below the minimum allowed status
15
IF v_bill.bill_status_id > v_minimum_status THEN
16
-- Update the draft bill to the previous status
17
UPDATE public.draft_bill_headers
18
SET bill_status_id = p_bill_status_id,
19
modified_by = p_modified_by,
20
modified_on_utc = now()
21
WHERE id = v_bill.id;
22
23
RAISE NOTICE 'Draft bill status updated to previous status for Bill ID: %', v_bill.id;
24
ELSE
25
RAISE NOTICE 'Cannot decrement status below the minimum allowed status for Draft Bill ID: %', v_bill.id;
26
END IF;
27
END LOOP;
28
29
-- Process approved bills for previous status update
30
FOR v_bill IN
31
SELECT id, bill_status_id
32
FROM public.bill_headers
33
WHERE id = ANY(p_bill_ids)
34
LOOP
35
-- Ensure the status doesn't go below the minimum allowed status
36
IF v_bill.bill_status_id > v_minimum_status THEN
37
-- Update the bill to the previous status
38
UPDATE public.bill_headers
39
SET bill_status_id = p_bill_status_id,
40
modified_by = p_modified_by,
41
modified_on_utc = now()
42
WHERE id = v_bill.id;
43
44
RAISE NOTICE 'Bill status updated to previous status for Bill ID: %', v_bill.id;
45
ELSE
46
RAISE NOTICE 'Cannot decrement status below the minimum allowed status for Bill ID: %', v_bill.id;
47
END IF;
48
END LOOP;
49
50
END
51
$procedure$
|
|||||
| Procedure | save_company_prefix_for_bill_and_payment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.save_company_prefix_for_bill_and_payment(IN p_company_id uuid, IN p_fin_year integer, IN p_bill_prefix text, IN p_bill_length integer, IN p_draft_bill_prefix text, IN p_draft_bill_length integer, IN p_payment_prefix text, IN p_payment_length integer)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Handle bill_header_ids
6
IF EXISTS (SELECT 1 FROM public.bill_header_ids WHERE company_id = p_company_id AND fin_year = p_fin_year) THEN
7
UPDATE public.bill_header_ids
8
SET
9
bill_prefix = COALESCE(p_bill_prefix, bill_prefix),
10
bill_length = COALESCE(p_bill_length, bill_length)
11
WHERE company_id = p_company_id AND fin_year = p_fin_year;
12
ELSE
13
INSERT INTO public.bill_header_ids (id, company_id, fin_year, bill_prefix, bill_length, last_bill_id)
14
VALUES ((SELECT COALESCE(MAX(id), 0) + 1 FROM bill_header_ids), p_company_id, p_fin_year, p_bill_prefix, p_bill_length, 0);
15
END IF;
16
17
-- Handle draft_bill_header_ids
18
IF EXISTS (SELECT 1 FROM public.draft_bill_header_ids WHERE company_id = p_company_id AND fin_year = p_fin_year) THEN
19
UPDATE public.draft_bill_header_ids
20
SET
21
bill_prefix = COALESCE(p_draft_bill_prefix, bill_prefix),
22
bill_length = COALESCE(p_draft_bill_length, bill_length)
23
WHERE company_id = p_company_id AND fin_year = p_fin_year;
24
ELSE
25
INSERT INTO public.draft_bill_header_ids (id, company_id, fin_year, bill_prefix, bill_length, last_bill_id)
26
VALUES ((SELECT COALESCE(MAX(id), 0) + 1 FROM draft_bill_header_ids), p_company_id, p_fin_year, p_draft_bill_prefix, p_draft_bill_length, 0);
27
END IF;
28
29
-- Handle bill_payment_header_ids
30
IF EXISTS (SELECT 1 FROM public.bill_payment_header_ids WHERE company_id = p_company_id AND fin_year = p_fin_year) THEN
31
UPDATE public.bill_payment_header_ids
32
SET
33
payment_prefix = COALESCE(p_payment_prefix, payment_prefix),
34
payment_length = COALESCE(p_payment_length, payment_length)
35
WHERE company_id = p_company_id AND fin_year = p_fin_year;
36
ELSE
37
INSERT INTO public.bill_payment_header_ids (id, company_id, fin_year, payment_prefix, payment_length, last_payment_id)
38
VALUES ((SELECT COALESCE(MAX(id), 0) + 1 FROM bill_payment_header_ids), p_company_id, p_fin_year, p_payment_prefix, p_payment_length, 0);
39
END IF;
40
41
END;
42
$procedure$
|
|||||
| Procedure | update_account_level_approval_configuration | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_account_level_approval_configuration(IN p_company_id uuid, IN p_account_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
5
DECLARE
6
approval_item JSONB;
7
existing_id INT;
8
v_status_id INT;
9
BEGIN
10
-- 1️⃣ Soft delete old records not present in incoming approvals:
11
UPDATE bill_approval_user_account
12
SET is_deleted = TRUE,
13
deleted_on_utc = NOW(),
14
modified_on_utc = NOW(),
15
modified_by = p_modified_by
16
WHERE company_id = p_company_id
17
AND account_id = p_account_id
18
AND NOT EXISTS (
19
SELECT 1
20
FROM jsonb_array_elements(p_approvals) AS elem
21
WHERE (elem->>'userId')::UUID = bill_approval_user_account.user_id
22
AND (elem->>'approvalLevel')::INT = bill_approval_user_account.approval_level
23
AND (elem->>'statusId')::INT = bill_approval_user_account.status_id
24
);
25
26
-- 2️⃣ Upsert incoming approvals:
27
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
28
LOOP
29
SELECT id INTO existing_id
30
FROM bill_approval_user_account
31
WHERE company_id = p_company_id
32
AND account_id = p_account_id
33
AND user_id = (approval_item->>'userId')::UUID
34
AND approval_level = (approval_item->>'approvalLevel')::INT
35
AND status_id = (approval_item->>'statusId')::INT
36
AND is_deleted = FALSE
37
LIMIT 1;
38
39
IF existing_id IS NOT NULL THEN
40
-- -- UPDATE bill_approval_user_account
41
-- SET status_id = (approval_item->>'statusId')::INT,
42
-- modified_on_utc = NOW(),
43
-- modified_by = p_modified_by
44
-- WHERE id = existing_id;
45
RAISE NOTICE 'existing IDs: %', existing_id;
46
ELSE
47
INSERT INTO bill_approval_user_account (
48
company_id,
49
account_id,
50
status_id,
51
user_id,
52
approval_level,
53
is_deleted,
54
created_on_utc,
55
created_by
56
)
57
VALUES (
58
p_company_id,
59
p_account_id,
60
(approval_item->>'statusId')::INT,
61
(approval_item->>'userId')::UUID,
62
(approval_item->>'approvalLevel')::INT,
63
FALSE,
64
NOW(),
65
p_modified_by
66
);
67
END IF;
68
END LOOP;
69
70
-- 3️⃣ Upsert bill_account_approval_levels:
71
IF EXISTS (
72
SELECT 1
73
FROM bill_account_approval_levels
74
WHERE company_id = p_company_id
75
AND account_id = p_account_id
76
AND status_id = 2 -- temparary updating only for status 2
77
) THEN
78
UPDATE bill_account_approval_levels
79
SET approval_level_required = p_required_approval_levels,
80
status_id = 2,
81
modified_on_utc = NOW(),
82
modified_by = p_modified_by
83
WHERE company_id = p_company_id
84
AND account_id = p_account_id
85
AND status_id = 2; -- temparary checking for only status 2
86
ELSE
87
INSERT INTO bill_account_approval_levels (
88
company_id,
89
account_id,
90
status_id,
91
approval_level_required,
92
created_on_utc,
93
created_by
94
)
95
VALUES (
96
p_company_id,
97
p_account_id,
98
2,
99
p_required_approval_levels,
100
NOW(),
101
p_modified_by
102
);
103
END IF;
104
105
END;
106
$procedure$
|
|||||
| Procedure | transfer_draft_to_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.transfer_draft_to_bill(IN p_draft_bill_id uuid, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
draft_bill RECORD;
6
draft_bill_details RECORD;
7
8
BEGIN
9
-- Fetch the draft bill header
10
SELECT * INTO draft_bill
11
FROM public.draft_bill_headers
12
WHERE id = p_draft_bill_id;
13
14
IF draft_bill IS NULL THEN
15
RAISE EXCEPTION 'Draft bill not found: %', p_draft_bill_id;
16
END IF;
17
18
-- Call to create bill header using the procedure
19
CALL public.create_bill_header(
20
draft_bill.id,
21
draft_bill.company_id,
22
draft_bill.vendor_id,
23
draft_bill.credit_account_id,
24
draft_bill.debit_account_id,
25
draft_bill.bill_date::date,
26
draft_bill.due_date::date,
27
draft_bill.payment_term,
28
draft_bill.taxable_amount,
29
draft_bill.cgst_amount,
30
draft_bill.sgst_amount,
31
draft_bill.igst_amount,
32
draft_bill.tds_amount,
33
draft_bill.total_amount,
34
draft_bill.discount,
35
draft_bill.fees,
36
draft_bill.round_off,
37
draft_bill.round_off_tds,
38
draft_bill.currency_id,
39
draft_bill.note,
40
draft_bill.bill_status_id,
41
draft_bill.created_by,
42
draft_bill.source_warehouse_id,
43
draft_bill.destination_warehouse_id,
44
draft_bill.bill_type_id,
45
draft_bill.bill_number,
46
draft_bill.po_no,
47
draft_bill.po_date::date
48
);
49
50
-- Raise a notice to indicate success
51
RAISE NOTICE 'Bill header inserted with ID: %', draft_bill.id;
52
53
-- Fetch and insert bill details
54
FOR draft_bill_details IN
55
SELECT * FROM public.draft_bill_details
56
WHERE bill_header_id = p_draft_bill_id
57
LOOP
58
INSERT INTO public.bill_details(
59
id, bill_header_id, product_id, price, quantity, fees, discount,
60
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount, serial_number)
61
VALUES(
62
draft_bill_details.id, draft_bill_details.bill_header_id,
63
draft_bill_details.product_id, draft_bill_details.price,
64
draft_bill_details.quantity, draft_bill_details.fees,
65
draft_bill_details.discount, draft_bill_details.taxable_amount,
66
draft_bill_details.sgst_amount, draft_bill_details.cgst_amount,
67
draft_bill_details.igst_amount, draft_bill_details.total_amount,
68
draft_bill_details.serial_number
69
);
70
71
UPDATE public.draft_bill_details
72
SET
73
is_deleted = TRUE,
74
deleted_on_utc = now()
75
WHERE bill_header_id = p_draft_bill_id;
76
77
END LOOP;
78
79
-- Mark the draft bill as deleted
80
UPDATE public.draft_bill_headers
81
SET
82
is_deleted = TRUE,
83
deleted_on_utc = now()
84
WHERE id = p_draft_bill_id;
85
86
87
END;
88
$procedure$
|
|||||
| Procedure | update_bill_next_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_bill_next_status(IN p_company_id uuid, IN p_bill_status_id integer, IN p_bill_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_draft_bill RECORD;
6
v_bill RECORD;
7
next_status INTEGER; -- To store the next status for bills
8
APPROVED_STATUS CONSTANT INTEGER := 3; -- Status for Approved
9
BEGIN
10
-- Process draft bills
11
FOR v_draft_bill IN
12
SELECT id, bill_status_id
13
FROM public.draft_bill_headers
14
WHERE id = ANY(p_bill_ids)
15
LOOP
16
-- Determine the next status based on the provided status or workflow
17
IF p_bill_status_id IS NOT NULL THEN
18
next_status := p_bill_status_id; -- Use the provided status
19
ELSE
20
SELECT bw.next_status
21
INTO next_status
22
FROM public.bill_workflow bw
23
WHERE bw.company_id = p_company_id
24
AND bw.status = v_draft_bill.bill_status_id;
25
END IF;
26
27
-- Update the draft bill status
28
UPDATE public.draft_bill_headers
29
SET bill_status_id = next_status,
30
modified_by = p_modified_by,
31
modified_on_utc = now()
32
WHERE id = v_draft_bill.id;
33
34
-- If the next status is 'APPROVED', transfer draft to final bill
35
IF next_status = APPROVED_STATUS THEN
36
-- Transfer draft to final bill
37
CALL transfer_draft_to_bill(v_draft_bill.id, p_modified_by);
38
39
-- Output when the draft bill is approved
40
RAISE NOTICE 'Draft Bill Approved. Transferring to Final Bill: %', v_draft_bill.id;
41
END IF;
42
END LOOP;
43
44
-- Process bills
45
FOR v_bill IN
46
SELECT id, bill_status_id
47
FROM public.bill_headers
48
WHERE id = ANY(p_bill_ids)
49
LOOP
50
-- Determine the next status based on the provided status or workflow
51
IF p_bill_status_id IS NOT NULL THEN
52
next_status := p_bill_status_id; -- Use the provided status
53
ELSE
54
SELECT bw.next_status
55
INTO next_status
56
FROM public.bill_workflow bw
57
WHERE bw.company_id = p_company_id
58
AND bw.status = v_bill.bill_status_id;
59
END IF;
60
61
-- Update the bill status
62
UPDATE public.bill_headers
63
SET bill_status_id = next_status,
64
modified_by = p_modified_by,
65
modified_on_utc = now()
66
WHERE id = v_bill.id;
67
68
-- Output the updated bill
69
RAISE NOTICE 'Bill ID: %, Current Status: %, Updated to Next Status: %',
70
v_bill.id, v_bill.bill_status_id, next_status;
71
END LOOP;
72
73
END
74
$procedure$
|
|||||
| Procedure | update_bill_next_status_for_bill_header | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_bill_next_status_for_bill_header(IN p_company_id uuid, IN p_bill_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill RECORD;
6
v_account_id uuid;
7
v_next_status integer;
8
v_next_status_name character varying;
9
v_issue TEXT;
10
v_next_temp_id int;
11
BEGIN
12
RAISE NOTICE 'START update_bill_next_status_for_bill_header for company_id=%, modified_by=%', p_company_id, p_modified_by;
13
RAISE NOTICE 'Incoming bill_ids: %', p_bill_ids;
14
15
FOR v_bill IN
16
SELECT id, bill_status_id, debit_account_id
17
FROM public.bill_headers
18
WHERE id = ANY(p_bill_ids)
19
LOOP
20
RAISE NOTICE 'Processing bill ID: %, current status: %', v_bill.id, v_bill.bill_status_id;
21
22
v_account_id := v_bill.debit_account_id;
23
24
-- Resolve next status from workflow
25
SELECT next_status INTO v_next_status
26
FROM public.bill_workflow
27
WHERE company_id = p_company_id
28
AND status = v_bill.bill_status_id
29
AND is_deleted = false
30
AND is_enabled = true
31
LIMIT 1;
32
33
RAISE NOTICE 'Resolved next_status for bill ID %: %', v_bill.id, v_next_status;
34
35
IF v_next_status IS NULL THEN
36
v_issue := 'Next status not found for company or status';
37
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
38
VALUES (v_bill.id, v_issue, now(), p_modified_by);
39
40
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
41
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
42
VALUES (v_next_temp_id, v_bill.id, 'Not found', v_issue);
43
44
RAISE NOTICE 'Bill ID % skipped: next status not found', v_bill.id;
45
CONTINUE;
46
END IF;
47
48
-- 🔔 Permission check using FUNCTION instead of EXISTS
49
PERFORM public.check_bill_approval_permissions(
50
p_company_id,
51
v_next_status,
52
p_modified_by,
53
v_account_id,
54
0
55
);
56
57
IF NOT FOUND THEN
58
v_issue := 'User not authorized to move bill to next status';
59
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
60
VALUES (v_bill.id, v_issue, now(), p_modified_by);
61
62
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
63
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
64
VALUES (v_next_temp_id, v_bill.id, 'User not authorized', v_issue);
65
66
RAISE NOTICE 'Bill ID % skipped: user not authorized', v_bill.id;
67
CONTINUE;
68
END IF;
69
70
-- Move to next status
71
UPDATE public.bill_headers
72
SET bill_status_id = v_next_status,
73
modified_by = p_modified_by,
74
modified_on_utc = now()
75
WHERE id = v_bill.id;
76
77
RAISE NOTICE 'Bill ID % updated to next_status %', v_bill.id, v_next_status;
78
79
-- Insert approval log
80
INSERT INTO public.bill_approval_logs(
81
bill_id, status_id, approved_by, approved_on, "comment",
82
created_on_utc, created_by, approval_level
83
)
84
VALUES (
85
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
86
'Approved and moved to next status',
87
now(), p_modified_by, 0
88
);
89
90
RAISE NOTICE 'Approval log inserted for bill ID %', v_bill.id;
91
92
-- Resolve next status name
93
SELECT name INTO v_next_status_name
94
FROM public.bill_statuses
95
WHERE id = v_next_status;
96
97
RAISE NOTICE 'Next status name for bill ID %: %', v_bill.id, v_next_status_name;
98
99
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
100
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
101
VALUES (v_next_temp_id, v_bill.id, v_next_status_name, NULL);
102
103
RAISE NOTICE 'Result inserted into temp_bill_next_status for bill ID %', v_bill.id;
104
END LOOP;
105
106
RAISE NOTICE 'END update_bill_next_status_for_bill_header';
107
END
108
$procedure$
|
|||||
| Procedure | upsert_bill_status_company_config_json | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_bill_status_company_config_json(IN p_updates jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_item JSONB;
6
BEGIN
7
FOR v_item IN SELECT * FROM jsonb_array_elements(p_updates)
8
LOOP
9
PERFORM public.upsert_bill_status_company_config(
10
(v_item->>'CompanyId')::UUID,
11
(v_item->>'StatusId')::INT,
12
(v_item->>'IsEnabled')::BOOLEAN,
13
(v_item->>'UserId')::UUID
14
);
15
END LOOP;
16
END;
17
$procedure$
|
|||||
| Procedure | clean_up_org_purchase | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.clean_up_org_purchase(IN p_organization_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_company_id uuid;
6
v_vendor_id uuid;
7
v_bill_header_id uuid;
8
v_bill_payment_header_id uuid;
9
v_vendor_note_header_id uuid;
10
v_gate_pass_header_id uuid;
11
v_user_id uuid;
12
BEGIN
13
FOR v_company_id IN SELECT id FROM companies WHERE organization_id = p_organization_id LOOP
14
-- Vendors and their children
15
FOR v_vendor_id IN SELECT id FROM vendors WHERE company_id = v_company_id LOOP
16
DELETE FROM vendor_contacts WHERE vendor_id = v_vendor_id;
17
DELETE FROM vendor_bank_accounts WHERE vendor_id = v_vendor_id;
18
DELETE FROM vendor_upis WHERE vendor_id = v_vendor_id;
19
DELETE FROM vendor_default_accounts WHERE vendor_id = v_vendor_id;
20
FOR v_vendor_note_header_id IN SELECT id FROM vendor_note_headers WHERE vendor_id = v_vendor_id LOOP
21
DELETE FROM vendor_note_details WHERE vendor_note_header_id = v_vendor_note_header_id;
22
END LOOP;
23
DELETE FROM vendor_note_headers WHERE vendor_id = v_vendor_id;
24
-- If you want to delete warehouses for this vendor:
25
-- DELETE FROM warehouses WHERE vendor_id = v_vendor_id;
26
END LOOP;
27
DELETE FROM vendors WHERE company_id = v_company_id;
28
DELETE FROM vendor_note_work_flows WHERE company_id = v_company_id;
29
DELETE FROM vendor_note_statuses WHERE company_id = v_company_id;
30
31
-- Bills and their children
32
FOR v_bill_header_id IN SELECT id FROM bill_headers WHERE company_id = v_company_id LOOP
33
DELETE FROM bill_details WHERE bill_header_id = v_bill_header_id;
34
DELETE FROM bill_approval_logs WHERE bill_id = v_bill_header_id;
35
DELETE FROM bill_approval_issue_logs WHERE bill_id = v_bill_header_id;
36
END LOOP;
37
DELETE FROM bill_headers WHERE company_id = v_company_id;
38
DELETE FROM bill_header_ids WHERE company_id = v_company_id;
39
DELETE FROM bill_workflow WHERE company_id = v_company_id;
40
DELETE FROM bill_status_company_configs WHERE company_id = v_company_id;
41
DELETE FROM bill_account_approval_levels WHERE company_id = v_company_id;
42
DELETE FROM bill_approval_user_account WHERE company_id = v_company_id;
43
DELETE FROM bill_approval_user_company WHERE company_id = v_company_id;
44
45
-- Bill payments
46
FOR v_bill_payment_header_id IN SELECT id FROM bill_payment_headers WHERE company_id = v_company_id LOOP
47
DELETE FROM bill_payment_details WHERE bill_payment_header_id = v_bill_payment_header_id;
48
END LOOP;
49
DELETE FROM bill_payment_headers WHERE company_id = v_company_id;
50
DELETE FROM bill_payment_header_ids WHERE company_id = v_company_id;
51
52
-- Draft bills
53
FOR v_bill_header_id IN SELECT id FROM draft_bill_headers WHERE company_id = v_company_id LOOP
54
DELETE FROM draft_bill_details WHERE bill_header_id = v_bill_header_id;
55
END LOOP;
56
DELETE FROM draft_bill_headers WHERE company_id = v_company_id;
57
DELETE FROM draft_bill_header_ids WHERE company_id = v_company_id;
58
59
-- Recurring bills
60
DELETE FROM recurring_bill_schedules WHERE company_id = v_company_id;
61
62
-- Gate pass and details
63
FOR v_gate_pass_header_id IN SELECT id FROM gate_pass_headers WHERE company_id = v_company_id LOOP
64
DELETE FROM gate_pass_details WHERE gate_pass_header_id = v_gate_pass_header_id;
65
END LOOP;
66
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
67
68
-- Users & their roles
69
FOR v_user_id IN SELECT id FROM users WHERE company_id = v_company_id LOOP
70
DELETE FROM user_roles WHERE user_id = v_user_id;
71
END LOOP;
72
DELETE FROM users WHERE company_id = v_company_id;
73
74
-- Finally, company itself
75
DELETE FROM companies WHERE id = v_company_id;
76
END LOOP;
77
78
-- Organization record itself
79
DELETE FROM organizations WHERE id = p_organization_id;
80
81
RAISE NOTICE 'Organization % and all related purchase data deleted.', p_organization_id;
82
END;
83
$procedure$
|
|||||
| Procedure | copy_bills | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.copy_bills(IN p_bill_ids uuid[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill_header RECORD;
6
v_bill_detail RECORD;
7
v_new_header_id UUID;
8
9
-- Constants for statuses and other values
10
bill_status_draft CONSTANT INTEGER := 1;
11
approved_bill_status CONSTANT INTEGER := 3;
12
BEGIN
13
-- Loop through each provided bill ID
14
FOR v_bill_header IN
15
(
16
SELECT id, company_id, vendor_id, debit_account_id, credit_account_id, bill_date, due_date,
17
payment_term, taxable_amount, cgst_amount, sgst_amount, igst_amount, tds_amount, total_amount,
18
discount, fees, round_off, currency_id, note, created_by, source_warehouse_id,
19
destination_warehouse_id, po_no, po_date, bill_type_id, bill_number, bill_status_id
20
FROM draft_bill_headers
21
WHERE id = ANY(p_bill_ids) AND is_deleted = false
22
23
UNION ALL
24
25
SELECT id, company_id, vendor_id, debit_account_id, credit_account_id, bill_date, due_date,
26
payment_term, taxable_amount, cgst_amount, sgst_amount, igst_amount, tds_amount, total_amount,
27
discount, fees, round_off, currency_id, note, created_by, source_warehouse_id,
28
destination_warehouse_id, po_no, po_date, bill_type_id, bill_number, bill_status_id
29
FROM bill_headers
30
WHERE id = ANY(p_bill_ids) AND is_deleted = false
31
)
32
LOOP
33
RAISE NOTICE 'Processing Bill ID: %', v_bill_header.id;
34
35
-- Generate a new UUID for the new bill header
36
v_new_header_id := gen_random_uuid();
37
RAISE NOTICE 'Generated new bill header ID: %', v_new_header_id;
38
39
-- Insert new draft bill header
40
BEGIN
41
CALL create_draft_bill_header(
42
v_new_header_id,
43
v_bill_header.company_id,
44
v_bill_header.vendor_id,
45
v_bill_header.debit_account_id,
46
v_bill_header.credit_account_id,
47
v_bill_header.bill_date::date,
48
v_bill_header.due_date::date,
49
v_bill_header.payment_term,
50
v_bill_header.taxable_amount,
51
v_bill_header.cgst_amount,
52
v_bill_header.sgst_amount,
53
v_bill_header.igst_amount,
54
v_bill_header.tds_amount,
55
v_bill_header.total_amount,
56
v_bill_header.discount,
57
v_bill_header.fees,
58
v_bill_header.round_off,
59
v_bill_header.currency_id,
60
v_bill_header.note,
61
bill_status_draft,
62
v_bill_header.created_by,
63
v_bill_header.source_warehouse_id,
64
v_bill_header.destination_warehouse_id,
65
v_bill_header.bill_type_id,
66
v_bill_header.bill_number,
67
v_bill_header.po_no,
68
v_bill_header.po_date::date -- Explicitly cast to date
69
);
70
71
RAISE NOTICE 'Inserted new bill header for original bill ID: %', v_bill_header.id;
72
73
EXCEPTION
74
WHEN OTHERS THEN
75
RAISE EXCEPTION 'Error inserting draft bill header for bill ID %: %', v_bill_header.id, SQLERRM;
76
END;
77
78
-- Copy bill details
79
BEGIN
80
IF v_bill_header.bill_status_id < approved_bill_status THEN
81
FOR v_bill_detail IN
82
SELECT * FROM draft_bill_details WHERE bill_header_id = v_bill_header.id
83
LOOP
84
CALL create_draft_bill_detail(
85
v_new_header_id,
86
v_bill_detail.price,
87
v_bill_detail.quantity,
88
v_bill_detail.discount,
89
v_bill_detail.fees,
90
v_bill_detail.cgst_amount,
91
v_bill_detail.igst_amount,
92
v_bill_detail.sgst_amount,
93
v_bill_detail.taxable_amount,
94
v_bill_detail.total_amount,
95
v_bill_detail.serial_number,
96
v_bill_detail.product_id
97
);
98
RAISE NOTICE 'Copied draft bill detail for new bill ID: %', v_new_header_id;
99
END LOOP;
100
ELSE
101
FOR v_bill_detail IN
102
SELECT * FROM bill_details WHERE bill_header_id = v_bill_header.id
103
LOOP
104
CALL create_draft_bill_detail(
105
v_new_header_id,
106
v_bill_detail.price,
107
v_bill_detail.quantity,
108
v_bill_detail.discount,
109
v_bill_detail.fees,
110
v_bill_detail.cgst_amount,
111
v_bill_detail.igst_amount,
112
v_bill_detail.sgst_amount,
113
v_bill_detail.taxable_amount,
114
v_bill_detail.total_amount,
115
v_bill_detail.serial_number,
116
v_bill_detail.product_id
117
);
118
RAISE NOTICE 'Copied bill detail for new bill ID: %', v_new_header_id;
119
END LOOP;
120
END IF;
121
122
EXCEPTION
123
WHEN OTHERS THEN
124
RAISE EXCEPTION 'Error copying bill details for bill ID %: %', v_bill_header.id, SQLERRM;
125
END;
126
END LOOP;
127
128
RAISE NOTICE 'Copy bills procedure completed successfully.';
129
END;
130
$procedure$
|
|||||
| Procedure | hard_delete_org_purchase | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_org_purchase(IN p_organization_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_company_id uuid;
6
BEGIN
7
FOR v_company_id IN SELECT id FROM companies WHERE organization_id = p_organization_id LOOP
8
DELETE FROM bill_workflow WHERE company_id = v_company_id;
9
DELETE FROM bill_payment_header_ids WHERE company_id = v_company_id;
10
DELETE FROM bill_payment_headers WHERE company_id = v_company_id;
11
DELETE FROM bill_header_ids WHERE company_id = v_company_id;
12
DELETE FROM bill_headers WHERE company_id = v_company_id;
13
DELETE FROM draft_bill_header_ids WHERE company_id = v_company_id;
14
DELETE FROM draft_bill_headers WHERE company_id = v_company_id;
15
DELETE FROM recurring_bill_schedules WHERE company_id = v_company_id;
16
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
17
DELETE FROM vendor_note_work_flows WHERE company_id = v_company_id;
18
DELETE FROM vendor_note_statuses WHERE company_id = v_company_id;
19
DELETE FROM vendor_note_headers WHERE company_id = v_company_id;
20
DELETE FROM vendor_categories WHERE company_id = v_company_id;
21
DELETE FROM vendors WHERE company_id = v_company_id;
22
DELETE FROM users WHERE company_id = v_company_id;
23
DELETE FROM companies WHERE id = v_company_id;
24
END LOOP;
25
26
DELETE FROM organizations WHERE id = p_organization_id;
27
28
RAISE NOTICE 'Deleted purchase data for organization_id: %', p_organization_id;
29
END;
30
$procedure$
|
|||||
| Procedure | hard_delete_organization | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_organization(IN p_organization_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
p_company_id UUID; -- Declare a variable for company IDs
6
BEGIN
7
-- Loop through the associated company IDs for the organization
8
FOR p_company_id IN
9
SELECT c.id FROM public.companies c WHERE c.organization_id = p_organization_id
10
LOOP
11
-- Delete from bill_payment_header_ids (cascading through companies)
12
DELETE FROM bill_payment_header_ids
13
WHERE bill_payment_header_ids.company_id = p_company_id;
14
15
-- Delete from bill_header_ids (cascading through companies)
16
DELETE FROM bill_header_ids
17
WHERE bill_header_ids.company_id = p_company_id;
18
19
-- Delete from draft_bill_header_ids (cascading through companies)
20
DELETE FROM draft_bill_header_ids
21
WHERE draft_bill_header_ids.company_id = p_company_id;
22
23
-- Delete from bill_workflow (cascading through companies)
24
DELETE FROM bill_workflow
25
WHERE bill_workflow.company_id = p_company_id;
26
27
-- Delete users associated with the company
28
DELETE FROM public.users
29
WHERE users.company_id = p_company_id;
30
31
-- Delete from companies (for the current company_id in the loop)
32
DELETE FROM public.companies
33
WHERE public.companies.id = p_company_id;
34
35
END LOOP;
36
37
-- Finally, delete the organization itself
38
DELETE FROM public.organizations
39
WHERE public.organizations.id = p_organization_id;
40
41
-- Log the operation
42
RAISE NOTICE 'Organization with ID % and all related data have been hard deleted.', p_organization_id;
43
44
END;
45
$procedure$
|
|||||
| Procedure | initialize_company | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_company_id uuid, IN p_org_id uuid, IN p_company_name text, IN p_is_apartment boolean, IN p_created_by uuid, IN p_default_company_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_company_exists boolean;
6
BEGIN
7
-- Check if company already exists
8
SELECT EXISTS (
9
SELECT 1 FROM public.companies WHERE id = p_company_id
10
) INTO v_company_exists;
11
12
IF NOT v_company_exists THEN
13
-- Insert into companies table
14
INSERT INTO public.companies (
15
id,
16
organization_id,
17
name,
18
is_apartment,
19
created_on_utc,
20
created_by
21
) VALUES (
22
p_company_id,
23
p_org_id,
24
p_company_name,
25
p_is_apartment,
26
NOW(),
27
p_created_by
28
);
29
RAISE NOTICE 'Company with ID % created.', p_company_id;
30
ELSE
31
RAISE NOTICE 'Company with ID % already exists. Skipping initialization.', p_company_id;
32
END IF;
33
34
CALL public.initialize_bill_workflow(
35
p_default_company_id,
36
p_company_id,
37
p_created_by
38
);
39
40
CALL public.initialize_draft_bill_header_ids(
41
p_default_company_id,
42
p_company_id
43
);
44
45
CALL public.initialize_bill_header_ids(
46
p_default_company_id,
47
p_company_id
48
);
49
CALL public.initialize_bill_payment_header_ids(
50
p_default_company_id,
51
p_company_id
52
);
53
54
END;
55
$procedure$
|
|||||
| Procedure | log_bill_approval | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.log_bill_approval(IN p_bill_id uuid, IN p_next_status integer, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
APPROVED_STATUS CONSTANT INTEGER := 3;
6
FIRST_LEVEL_APPROVAL CONSTANT INTEGER := 8;
7
SECOND_LEVEL_APPROVAL CONSTANT INTEGER := 9;
8
BEGIN
9
IF p_next_status = APPROVED_STATUS OR
10
p_next_status = FIRST_LEVEL_APPROVAL OR
11
p_next_status = SECOND_LEVEL_APPROVAL THEN
12
13
INSERT INTO public.bill_approval_logs(
14
bill_id,
15
status_id,
16
approved_by,
17
approved_on,
18
"comment",
19
created_on_utc,
20
created_by
21
)
22
VALUES(
23
p_bill_id,
24
p_next_status,
25
p_modified_by,
26
NOW(),
27
CASE
28
WHEN p_next_status = FIRST_LEVEL_APPROVAL THEN 'Level 1 Approval'
29
WHEN p_next_status = SECOND_LEVEL_APPROVAL THEN 'Level 2 Approval'
30
WHEN p_next_status = APPROVED_STATUS THEN 'Final Approval'
31
ELSE 'Status updated'
32
END,
33
NOW(),
34
p_modified_by
35
);
36
RAISE NOTICE 'Approval logged for bill %, status %', p_bill_id, p_next_status;
37
END IF;
38
END;
39
$procedure$
|
|||||
| Procedure | update_bill_account_approval_user | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_bill_account_approval_user(IN p_updates jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
update_item jsonb;
6
record_exists int;
7
v_company_id uuid;
8
v_account_id uuid;
9
v_status_id int;
10
v_user_id uuid;
11
v_modified_by uuid;
12
v_approval_level int;
13
v_company_level int;
14
BEGIN
15
-- Loop over each item in the JSON array
16
FOR update_item IN SELECT * FROM jsonb_array_elements(p_updates)
17
LOOP
18
-- Extract values
19
v_company_id := (update_item->>'CompanyId')::uuid;
20
v_account_id := (update_item->>'AccountId')::uuid;
21
v_status_id := (update_item->>'StatusId')::int;
22
v_user_id := (update_item->>'UserId')::uuid;
23
v_modified_by := (update_item->>'ModifiedBy')::uuid;
24
v_approval_level := COALESCE((update_item->>'ApprovalLevel')::int, 0);
25
26
-- Check if record exists
27
SELECT COUNT(*) INTO record_exists
28
FROM public.bill_approval_user_account
29
WHERE id = (update_item->>'Id')::int;
30
31
IF record_exists > 0 THEN
32
-- Update existing record
33
UPDATE public.bill_approval_users_account
34
SET
35
company_id = v_company_id,
36
account_id = v_account_id,
37
status_id = v_status_id,
38
user_id = v_user_id,
39
approval_level = v_approval_level,
40
modified_by = v_modified_by,
41
modified_on_utc = now()
42
WHERE
43
id = (update_item->>'Id')::int
44
AND is_deleted = false;
45
ELSE
46
-- Insert new record
47
INSERT INTO public.bill_approval_user_account (
48
company_id, account_id, status_id, user_id,
49
approval_level, created_by, created_on_utc,
50
modified_by, modified_on_utc, is_deleted
51
) VALUES (
52
v_company_id, v_account_id, v_status_id, v_user_id,
53
v_approval_level, v_modified_by, now(),
54
v_modified_by, now(), false
55
);
56
END IF;
57
58
-- Get company-level approval level
59
SELECT iw.approval_level INTO v_company_level
60
FROM public.bill_workflow iw
61
WHERE iw.company_id = v_company_id
62
AND iw.status = v_status_id
63
AND iw.is_deleted = false
64
LIMIT 1;
65
66
-- If different, upsert into bill_account_approval_levels
67
IF v_company_level IS DISTINCT FROM v_approval_level THEN
68
IF EXISTS (
69
SELECT 1 FROM public.bill_account_approval_levels
70
WHERE company_id = v_company_id
71
AND account_id = v_account_id
72
AND status_id = v_status_id
73
AND is_deleted = false
74
) THEN
75
UPDATE public.bill_account_approval_levels
76
SET approval_level_required = v_approval_level,
77
modified_on_utc = now(),
78
modified_by = v_modified_by
79
WHERE company_id = v_company_id
80
AND account_id = v_account_id
81
AND status_id = v_status_id
82
AND is_deleted = false;
83
ELSE
84
INSERT INTO public.bill_account_approval_levels (
85
company_id, account_id, status_id, approval_level_required,
86
created_on_utc, modified_on_utc, deleted_on_utc,
87
is_deleted, created_by, modified_by
88
) VALUES (
89
v_company_id, v_account_id, v_status_id, v_approval_level,
90
now(), NULL, NULL,
91
false, v_modified_by, v_modified_by
92
);
93
END IF;
94
END IF;
95
END LOOP;
96
END;
97
$procedure$
|
|||||
| Procedure | update_company_level_approval_configuration | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_company_level_approval_configuration(IN p_company_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
5
DECLARE
6
approval_item JSONB;
7
existing_id INT;
8
upsert_count INT := 0;
9
delete_count INT := 0;
10
BEGIN
11
RAISE NOTICE 'Starting update_company_level_approval_configuration for company %, status %', p_company_id, p_status_id;
12
RAISE NOTICE 'Required approval levels: %', p_required_approval_levels;
13
RAISE NOTICE 'Approvals payload: %', p_approvals;
14
15
-- 1️⃣ Soft delete old records not present in incoming approvals:
16
UPDATE bill_approval_user_company
17
SET is_deleted = TRUE,
18
deleted_on_utc = NOW(),
19
modified_on_utc = NOW(),
20
modified_by = p_modified_by
21
WHERE company_id = p_company_id
22
AND is_deleted = FALSE
23
AND NOT EXISTS (
24
SELECT 1
25
FROM jsonb_array_elements(p_approvals) AS elem
26
WHERE (elem->>'userId')::UUID = bill_approval_user_company.user_id
27
AND (elem->>'statusId')::INT = bill_approval_user_company.status_id
28
AND (elem->>'approvalLevel')::INT = bill_approval_user_company.approval_level
29
);
30
31
GET DIAGNOSTICS delete_count = ROW_COUNT;
32
RAISE NOTICE 'Soft deleted % records not present in payload', delete_count;
33
34
-- 2️⃣ Upsert incoming approvals:
35
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
36
LOOP
37
SELECT id INTO existing_id
38
FROM bill_approval_user_company
39
WHERE company_id = p_company_id
40
AND user_id = (approval_item->>'userId')::UUID
41
AND status_id = (approval_item->>'statusId')::INT
42
AND approval_level = (approval_item->>'approvalLevel')::INT
43
AND is_deleted = FALSE
44
LIMIT 1;
45
46
IF existing_id IS NOT NULL THEN
47
UPDATE bill_approval_user_company
48
SET status_id = (approval_item->>'statusId')::INT,
49
modified_on_utc = NOW(),
50
modified_by = p_modified_by
51
WHERE id = existing_id;
52
RAISE NOTICE 'Updated approval for user %, level %, status %', (approval_item->>'userId'), (approval_item->>'approvalLevel'), (approval_item->>'statusId');
53
ELSE
54
INSERT INTO bill_approval_user_company (
55
company_id,
56
status_id,
57
user_id,
58
approval_level,
59
is_deleted,
60
created_on_utc,
61
created_by
62
)
63
VALUES (
64
p_company_id,
65
(approval_item->>'statusId')::INT,
66
(approval_item->>'userId')::UUID,
67
(approval_item->>'approvalLevel')::INT,
68
FALSE,
69
NOW(),
70
p_modified_by
71
);
72
RAISE NOTICE 'Inserted approval for user %, level %, status %', (approval_item->>'userId'), (approval_item->>'approvalLevel'), (approval_item->>'statusId');
73
END IF;
74
upsert_count := upsert_count + 1;
75
END LOOP;
76
77
RAISE NOTICE 'Upserted % approval records', upsert_count;
78
79
-- 3️⃣ Update workflow approval level for the given status:
80
UPDATE bill_workflow
81
SET approval_level = p_required_approval_levels
82
WHERE company_id = p_company_id
83
AND status = p_status_id;
84
85
RAISE NOTICE 'Updated bill_workflow approval_level to % for company % and status %', p_required_approval_levels, p_company_id, p_status_id;
86
87
RAISE NOTICE 'Completed update_company_level_approval_configuration for company %', p_company_id;
88
END;
89
$procedure$
|
|||||
| Procedure | purge_purchase_organization_data | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.purge_purchase_organization_data(IN p_organization_ids uuid[] DEFAULT NULL::uuid[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
-- Retention policy
6
c_retention_hours integer := 24;
7
v_cutoff_24h timestamp := now() - make_interval(hours => GREATEST(c_retention_hours, 1));
8
9
-- Targets
10
v_orgs uuid[];
11
v_companies uuid[] := '{}';
12
13
-- Vendors and related
14
v_vendor_ids uuid[] := '{}';
15
16
-- Warehouses and their addresses
17
v_warehouse_ids uuid[] := '{}';
18
v_warehouse_address_ids uuid[] := '{}';
19
20
-- Bills (approved/final)
21
v_bill_header_ids uuid[] := '{}';
22
v_bill_detail_ids uuid[] := '{}';
23
24
-- Draft bills
25
v_draft_bill_header_ids uuid[] := '{}';
26
v_draft_bill_detail_ids uuid[] := '{}';
27
28
-- Payments
29
v_bill_payment_header_ids uuid[] := '{}';
30
v_bill_payment_detail_ids uuid[] := '{}';
31
32
-- Recurring schedules
33
v_recurring_schedule_ids uuid[] := '{}';
34
35
-- Gate passes
36
v_gate_pass_header_ids uuid[] := '{}';
37
v_gate_pass_detail_ids uuid[] := '{}';
38
39
-- Vendor notes
40
v_vendor_note_header_ids uuid[] := '{}';
41
v_vendor_note_detail_ids uuid[] := '{}';
42
BEGIN
43
START TRANSACTION;
44
45
--------------------------------------------------------------------
46
-- 1) Resolve target organizations and companies (uniform block)
47
--------------------------------------------------------------------
48
IF p_organization_ids IS NULL OR array_length(p_organization_ids, 1) IS NULL THEN
49
SELECT array_agg(id)
50
INTO v_orgs
51
FROM organizations
52
WHERE created_on_utc > '2025-05-23'
53
AND created_on_utc < (NOW() - interval '24 hours');
54
ELSE
55
v_orgs := p_organization_ids;
56
END IF;
57
58
IF v_orgs IS NULL OR array_length(v_orgs, 1) IS NULL THEN
59
RAISE NOTICE 'No organizations found for cleanup.';
60
COMMIT;
61
RETURN;
62
END IF;
63
64
-- Companies by organization
65
SELECT COALESCE(array_agg(id), '{}')
66
INTO v_companies
67
FROM companies
68
WHERE organization_id = ANY(v_orgs);
69
70
IF v_companies IS NULL OR array_length(v_companies, 1) IS NULL THEN
71
RAISE NOTICE 'No companies resolved for purchase purge. Orgs: %', v_orgs;
72
COMMIT;
73
RETURN;
74
END IF;
75
76
RAISE NOTICE 'Purchase purge targets - Organizations: %; Companies: %', v_orgs, v_companies;
77
78
--------------------------------------------------------------------
79
-- 2) Collect IDs (COALESCE to '{}' to avoid NULL-array issues)
80
--------------------------------------------------------------------
81
-- Vendors
82
SELECT COALESCE(array_agg(id), '{}')
83
INTO v_vendor_ids
84
FROM vendors
85
WHERE company_id = ANY(v_companies);
86
87
-- Warehouses (by vendor) and their addresses
88
SELECT COALESCE(array_agg(id), '{}')
89
INTO v_warehouse_ids
90
FROM warehouses
91
WHERE vendor_id = ANY(v_vendor_ids);
92
93
SELECT COALESCE(array_agg(address_id), '{}')
94
INTO v_warehouse_address_ids
95
FROM warehouses
96
WHERE id = ANY(v_warehouse_ids);
97
98
-- Draft bills
99
SELECT COALESCE(array_agg(id), '{}')
100
INTO v_draft_bill_header_ids
101
FROM draft_bill_headers
102
WHERE company_id = ANY(v_companies)
103
AND created_on_utc < v_cutoff_24h;
104
105
SELECT COALESCE(array_agg(id), '{}')
106
INTO v_draft_bill_detail_ids
107
FROM draft_bill_details
108
WHERE bill_header_id = ANY(v_draft_bill_header_ids);
109
110
-- Final bills
111
SELECT COALESCE(array_agg(id), '{}')
112
INTO v_bill_header_ids
113
FROM bill_headers
114
WHERE company_id = ANY(v_companies)
115
AND created_on_utc < v_cutoff_24h;
116
117
SELECT COALESCE(array_agg(id), '{}')
118
INTO v_bill_detail_ids
119
FROM bill_details
120
WHERE bill_header_id = ANY(v_bill_header_ids);
121
122
-- Payments
123
SELECT COALESCE(array_agg(id), '{}')
124
INTO v_bill_payment_header_ids
125
FROM bill_payment_headers
126
WHERE company_id = ANY(v_companies)
127
AND created_on_utc < v_cutoff_24h;
128
129
SELECT COALESCE(array_agg(id), '{}')
130
INTO v_bill_payment_detail_ids
131
FROM bill_payment_details
132
WHERE bill_payment_header_id = ANY(v_bill_payment_header_ids);
133
134
-- Recurring schedules
135
SELECT COALESCE(array_agg(id), '{}')
136
INTO v_recurring_schedule_ids
137
FROM recurring_bill_schedules
138
WHERE company_id = ANY(v_companies);
139
140
-- Gate passes
141
SELECT COALESCE(array_agg(id), '{}')
142
INTO v_gate_pass_header_ids
143
FROM gate_pass_headers
144
WHERE company_id = ANY(v_companies);
145
146
SELECT COALESCE(array_agg(id), '{}')
147
INTO v_gate_pass_detail_ids
148
FROM gate_pass_details
149
WHERE gate_pass_header_id = ANY(v_gate_pass_header_ids);
150
151
-- Vendor notes
152
SELECT COALESCE(array_agg(id), '{}')
153
INTO v_vendor_note_header_ids
154
FROM vendor_note_headers
155
WHERE company_id = ANY(v_companies);
156
157
SELECT COALESCE(array_agg(id), '{}')
158
INTO v_vendor_note_detail_ids
159
FROM vendor_note_details
160
WHERE vendor_note_header_id = ANY(v_vendor_note_header_ids);
161
162
--------------------------------------------------------------------
163
-- 3) Purge in strict child → parent order (avoid FK violations)
164
--------------------------------------------------------------------
165
-- Vendor advance settlements (before payments, in case of linkage)
166
DELETE FROM vendor_advance_settlements
167
WHERE company_id = ANY(v_companies)
168
OR applied_in_payment_id = ANY(v_bill_payment_header_ids);
169
170
-- Bill payments: details → headers → counters
171
DELETE FROM bill_payment_details
172
WHERE id = ANY(v_bill_payment_detail_ids);
173
174
DELETE FROM bill_payment_headers
175
WHERE id = ANY(v_bill_payment_header_ids);
176
177
DELETE FROM bill_payment_header_ids
178
WHERE company_id = ANY(v_companies);
179
180
-- Vendor advances (refunds, advances, counters)
181
DELETE FROM vendor_advance_refunds
182
WHERE company_id = ANY(v_companies);
183
184
DELETE FROM vendor_advances
185
WHERE company_id = ANY(v_companies);
186
187
DELETE FROM vendor_advance_ids
188
WHERE company_id = ANY(v_companies);
189
190
DELETE FROM vendor_advance_settlement_ids
191
WHERE company_id = ANY(v_companies);
192
193
-- Bill approval logs/issues and details → headers → counters
194
DELETE FROM bill_approval_logs
195
WHERE bill_id = ANY(v_bill_header_ids);
196
197
DELETE FROM bill_approval_issue_logs
198
WHERE bill_id = ANY(v_bill_header_ids);
199
200
DELETE FROM bill_details
201
WHERE id = ANY(v_bill_detail_ids);
202
203
-- Any temp next-status rows tied to these bills
204
DELETE FROM temp_bill_next_status
205
WHERE bill_id = ANY(v_bill_header_ids) OR bill_id = ANY(v_draft_bill_header_ids);
206
207
DELETE FROM bill_headers
208
WHERE id = ANY(v_bill_header_ids);
209
210
DELETE FROM bill_header_ids
211
WHERE company_id = ANY(v_companies);
212
213
-- Draft bills: details → headers → counters
214
DELETE FROM draft_bill_details
215
WHERE id = ANY(v_draft_bill_detail_ids);
216
217
DELETE FROM draft_bill_headers
218
WHERE id = ANY(v_draft_bill_header_ids);
219
220
DELETE FROM draft_bill_header_ids
221
WHERE company_id = ANY(v_companies);
222
223
-- Recurring schedules: details → schedules
224
DELETE FROM recurrence_bill_schedule_details
225
WHERE schedule_id = ANY(v_recurring_schedule_ids);
226
227
DELETE FROM recurring_bill_schedules
228
WHERE id = ANY(v_recurring_schedule_ids);
229
230
-- Gate passes: details → headers
231
DELETE FROM gate_pass_details
232
WHERE id = ANY(v_gate_pass_detail_ids);
233
234
DELETE FROM gate_pass_headers
235
WHERE id = ANY(v_gate_pass_header_ids);
236
237
-- Vendor notes: details → headers → configs
238
DELETE FROM vendor_note_details
239
WHERE id = ANY(v_vendor_note_detail_ids);
240
241
DELETE FROM vendor_note_headers
242
WHERE id = ANY(v_vendor_note_header_ids);
243
244
DELETE FROM vendor_note_work_flows
245
WHERE company_id = ANY(v_companies);
246
247
DELETE FROM vendor_note_statuses
248
WHERE company_id = ANY(v_companies);
249
250
-- Vendor-company scoped defaults
251
DELETE FROM vendor_default_accounts
252
WHERE company_id = ANY(v_companies);
253
254
-- Payment orders tied to vendors (child of vendors)
255
DELETE FROM payment_orders
256
WHERE vendor_id = ANY(v_vendor_ids);
257
258
-- Vendor bridges (contacts, bank accounts, upis) and warehouses
259
DELETE FROM vendor_contacts
260
WHERE vendor_id = ANY(v_vendor_ids);
261
262
DELETE FROM vendor_bank_accounts
263
WHERE vendor_id = ANY(v_vendor_ids);
264
265
DELETE FROM vendor_upis
266
WHERE vendor_id = ANY(v_vendor_ids);
267
268
-- Warehouses and their addresses (after bills and gate passes are gone)
269
DELETE FROM warehouses
270
WHERE id = ANY(v_warehouse_ids);
271
272
DELETE FROM addresses
273
WHERE id = ANY(v_warehouse_address_ids);
274
275
-- Finally, vendors
276
DELETE FROM vendors
277
WHERE id = ANY(v_vendor_ids);
278
279
-- Company-scoped bill workflow and approval configs
280
DELETE FROM bill_status_company_configs
281
WHERE company_id = ANY(v_companies);
282
283
DELETE FROM bill_account_approval_levels
284
WHERE company_id = ANY(v_companies);
285
286
DELETE FROM bill_approval_user_account
287
WHERE company_id = ANY(v_companies);
288
289
DELETE FROM bill_approval_user_company
290
WHERE company_id = ANY(v_companies);
291
292
DELETE FROM bill_workflow
293
WHERE company_id = ANY(v_companies);
294
295
RAISE NOTICE 'Purchase purge complete for companies: % (orgs: %).', v_companies, v_orgs;
296
297
COMMIT;
298
299
EXCEPTION
300
WHEN OTHERS THEN
301
ROLLBACK;
302
RAISE NOTICE 'purge_purchase_organization_data failed: %', SQLERRM;
303
-- Optionally rethrow
304
-- RAISE;
305
END;
306
$procedure$
|
|||||
| Procedure | insert_draft_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_draft_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD; -- Declaring a record variable for the loop
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_detail_id uuid;
9
BEGIN
10
-- Set default values for warehouses based on bill type
11
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
12
v_source_warehouse_id := NULL;
13
v_destination_warehouse_id := NULL;
14
ELSE
15
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
16
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
17
END IF;
18
19
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
20
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
21
22
-- Call to create_draft_bill_header procedure
23
CALL public.create_draft_bill_header(
24
p_id,
25
p_company_id,
26
p_vendor_id,
27
p_credit_account_id,
28
p_debit_account_id,
29
p_bill_date::date,
30
p_due_date::date,
31
p_payment_term,
32
p_taxable_amount,
33
p_cgst_amount,
34
p_sgst_amount,
35
p_igst_amount,
36
p_tds_amount,
37
p_total_amount,
38
p_discount,
39
p_fees,
40
p_round_off,
41
p_round_off_tds,
42
p_currency_id,
43
p_note ,
44
p_bill_status_id ,
45
p_created_by ,
46
v_source_warehouse_id ,
47
v_destination_warehouse_id ,
48
p_bill_type_id,
49
p_bill_number,
50
p_po_no,
51
p_po_date::date
52
);
53
54
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
55
56
-- Loop through the JSONB array of bill details
57
FOR detail IN
58
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
59
id uuid, product_id uuid, price numeric, quantity numeric,
60
fees numeric, discount numeric, taxable_amount numeric,
61
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
62
total_amount numeric, serial_number integer
63
)
64
LOOP
65
-- Log each field to ensure they are being read correctly
66
--RAISE NOTICE 'Detail - product_id: %, price: %, quantity: %, taxable_amount: %', detail."productId", detail.price, detail.quantity, detail."taxableAmount";
67
68
-- Insert into draft_bill_details, using "productId" from JSON
69
INSERT INTO public.draft_bill_details (
70
id, bill_header_id, product_id, price, quantity, fees, discount,
71
taxable_amount, sgst_amount, cgst_amount, igst_amount,
72
total_amount, serial_number, is_deleted
73
) VALUES (
74
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
75
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
76
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
77
);
78
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
79
END LOOP;
80
81
-- No explicit COMMIT needed
82
83
EXCEPTION
84
-- No explicit ROLLBACK needed; just raise the error
85
WHEN OTHERS THEN
86
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
87
RAISE;
88
END;
89
$procedure$
|
|||||
| Procedure | insert_bill_by_excel | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_bill_by_excel(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_bill_number text, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_created_by uuid, IN p_bill_details jsonb, IN p_type integer, IN p_setteled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_bill_voucher text;
9
v_created_by uuid;
10
BEGIN
11
-- Set default values for warehouses
12
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
13
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
14
v_bill_voucher := get_new_bill_voucher(p_company_id, p_bill_date::date);
15
16
-- Check if the bill voucher was generated successfully
17
--IF v_bill_voucher IS NULL THEN
18
-- RAISE EXCEPTION 'Failed to generate bill voucher for company ID: %', p_company_id;
19
--ELSE
20
-- RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
21
--END IF;
22
23
-- Fetch the system user ID from the users table
24
SELECT id INTO v_created_by FROM public.users
25
WHERE first_name = 'System'
26
LIMIT 1;
27
28
-- Raise an exception if the system user is not found
29
IF v_created_by IS NULL THEN
30
RAISE EXCEPTION 'System user not found in the users table';
31
END IF;
32
33
-- Insert into the bill header
34
INSERT INTO public.bill_headers(
35
id,
36
company_id,
37
vendor_id,
38
credit_account_id,
39
debit_account_id,
40
bill_voucher,
41
bill_number,
42
bill_date,
43
due_date,
44
payment_term,
45
taxable_amount,
46
cgst_amount,
47
sgst_amount,
48
igst_amount,
49
tds_amount,
50
total_amount,
51
discount,
52
fees,
53
round_off,
54
round_off_tds,
55
currency_id,
56
note,
57
bill_status_id,
58
created_by,
59
source_warehouse_id,
60
destination_warehouse_id,
61
bill_type_id,
62
created_on_utc,
63
po_no,
64
po_date
65
)
66
VALUES (
67
p_id,
68
p_company_id,
69
p_vendor_id,
70
p_credit_account_id,
71
p_debit_account_id,
72
v_bill_voucher,
73
p_bill_number,
74
p_bill_date,
75
p_due_date,
76
p_payment_term,
77
p_taxable_amount,
78
p_cgst_amount,
79
p_sgst_amount,
80
p_igst_amount,
81
0,
82
p_total_amount,
83
p_discount,
84
p_fees,
85
p_round_off,
86
p_round_off_tds,
87
p_currency_id,
88
p_note,
89
p_bill_status_id,
90
p_created_by,
91
p_source_warehouse_id,
92
p_destination_warehouse_id,
93
p_type,
94
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
95
p_po_no,
96
p_po_date
97
);
98
99
RAISE NOTICE 'bill header inserted with ID: %', p_id;
100
EXCEPTION
101
-- Handle unique violation (duplicate key)
102
WHEN unique_violation THEN
103
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
104
ROLLBACK;
105
106
-- Handle foreign key violation
107
WHEN foreign_key_violation THEN
108
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
109
ROLLBACK;
110
111
-- Catch all other exceptions
112
WHEN OTHERS THEN
113
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
114
ROLLBACK;
115
END;
116
$procedure$
|
|||||
| Procedure | insert_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD; -- Declaring a record variable for the loop
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_detail_id uuid;
9
BEGIN
10
-- Set default values for warehouses based on bill type
11
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
12
v_source_warehouse_id := NULL;
13
v_destination_warehouse_id := NULL;
14
ELSE
15
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
16
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
17
END IF;
18
19
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
20
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
21
22
-- Call to create_bill_header procedure
23
CALL public.create_bill_header(
24
p_id,
25
p_company_id,
26
p_vendor_id,
27
p_credit_account_id,
28
p_debit_account_id,
29
p_bill_date::date,
30
p_due_date::date,
31
p_payment_term,
32
p_taxable_amount,
33
p_cgst_amount,
34
p_sgst_amount,
35
p_igst_amount,
36
p_tds_amount,
37
p_total_amount,
38
p_discount,
39
p_fees,
40
p_round_off,
41
p_round_off_tds,
42
p_currency_id,
43
p_note ,
44
p_bill_status_id ,
45
p_created_by ,
46
v_source_warehouse_id ,
47
v_destination_warehouse_id ,
48
p_bill_type_id,
49
p_bill_number
50
);
51
52
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
53
54
-- Loop through the JSONB array of bill details
55
FOR detail IN
56
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
57
id uuid, product_id uuid, price numeric, quantity integer,
58
fees numeric, discount numeric, taxable_amount numeric,
59
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
60
total_amount numeric, serial_number integer
61
)
62
LOOP
63
INSERT INTO public.bill_details (
64
id, bill_header_id, product_id, price, quantity, fees, discount,
65
taxable_amount, sgst_amount, cgst_amount, igst_amount,
66
total_amount, serial_number, is_deleted
67
) VALUES (
68
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
69
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
70
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
71
);
72
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
73
END LOOP;
74
75
-- No explicit COMMIT needed
76
77
EXCEPTION
78
-- No explicit ROLLBACK needed; just raise the error
79
WHEN OTHERS THEN
80
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
81
RAISE;
82
END;
83
$procedure$
|
|||||
| Procedure | create_draft_bill_header | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_draft_bill_header(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_bill_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_currency_id integer, IN p_note character varying, IN p_bill_status_id integer, IN p_created_by uuid, IN p_source_warehouse_id uuid DEFAULT NULL::uuid, IN p_destination_warehouse_id uuid DEFAULT NULL::uuid, IN p_bill_type_id integer DEFAULT 1, IN p_bill_number character varying DEFAULT NULL::character varying, IN p_po_no text DEFAULT NULL::text, IN p_po_date date DEFAULT NULL::date)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill_voucher character varying;
6
source_warehouse_id uuid;
7
destination_warehouse_id uuid;
8
BEGIN
9
-- Generate bill voucher using the draft bill function
10
v_bill_voucher := get_new_draft_bill_voucher(p_company_id,p_bill_date);
11
12
-- Log the generated bill voucher
13
RAISE NOTICE 'Bill Voucher: %', v_bill_voucher;
14
15
-- Set default values for warehouses based on bill type
16
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
17
source_warehouse_id := NULL;
18
destination_warehouse_id := NULL;
19
ELSE
20
-- Use passed values or default to null if not provided
21
source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
22
destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
23
END IF;
24
25
-- Insert into draft_bill_headers table
26
INSERT INTO
27
public.draft_bill_headers(
28
id,
29
company_id,
30
vendor_id,
31
credit_account_id,
32
debit_account_id,
33
bill_voucher,
34
bill_number,
35
bill_date,
36
due_date,
37
payment_term,
38
taxable_amount,
39
cgst_amount,
40
sgst_amount,
41
igst_amount,
42
tds_amount,
43
total_amount,
44
discount,
45
fees,
46
round_off,
47
round_off_tds,
48
currency_id,
49
note,
50
bill_status_id,
51
created_by,
52
source_warehouse_id,
53
destination_warehouse_id,
54
bill_type_id,
55
created_on_utc,
56
po_no,
57
po_date
58
)
59
VALUES (
60
p_id,
61
p_company_id,
62
p_vendor_id,
63
p_credit_account_id,
64
p_debit_account_id,
65
v_bill_voucher,
66
p_bill_number,
67
p_bill_date,
68
p_due_date,
69
p_payment_term,
70
p_taxable_amount,
71
p_cgst_amount,
72
p_sgst_amount,
73
p_igst_amount,
74
p_tds_amount,
75
p_total_amount,
76
p_discount,
77
p_fees,
78
p_round_off,
79
p_round_off_tds,
80
p_currency_id,
81
p_note,
82
p_bill_status_id,
83
p_created_by,
84
p_source_warehouse_id,
85
p_destination_warehouse_id,
86
p_bill_type_id,
87
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
88
p_po_no,
89
p_po_date
90
);
91
92
-- Log success message
93
RAISE NOTICE 'Draft Bill header inserted successfully with Bill Voucher: % ', v_bill_voucher ;
94
95
END;
96
$procedure$
|
|||||
| Procedure | create_bill_header | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_bill_header(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_bill_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_currency_id integer, IN p_note character varying, IN p_bill_status_id integer, IN p_created_by uuid, IN p_source_warehouse_id uuid DEFAULT NULL::uuid, IN p_destination_warehouse_id uuid DEFAULT NULL::uuid, IN p_bill_type_id integer DEFAULT 1, IN p_bill_number character varying DEFAULT NULL::character varying, IN p_po_no text DEFAULT NULL::text, IN p_po_date date DEFAULT NULL::date)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill_voucher character varying;
6
source_warehouse_id uuid;
7
destination_warehouse_id uuid;
8
BEGIN
9
-- Attempt to generate bill voucher
10
v_bill_voucher := get_new_bill_voucher(p_company_id, p_bill_date);
11
12
-- Check if the bill voucher was generated successfully
13
IF v_bill_voucher IS NULL THEN
14
RAISE EXCEPTION 'Failed to generate bill voucher for company ID: %', p_company_id;
15
ELSE
16
RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
17
END IF;
18
19
-- Set default values for warehouses based on bill type
20
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
21
source_warehouse_id := NULL;
22
destination_warehouse_id := NULL;
23
ELSE
24
-- Use passed values or default to null if not provided
25
source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
26
destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
27
END IF;
28
29
-- Insert into bill_headers table
30
INSERT INTO public.bill_headers(
31
id,
32
company_id,
33
vendor_id,
34
credit_account_id,
35
debit_account_id,
36
bill_voucher,
37
bill_number,
38
bill_date,
39
due_date,
40
payment_term,
41
taxable_amount,
42
cgst_amount,
43
sgst_amount,
44
igst_amount,
45
tds_amount,
46
total_amount,
47
discount,
48
fees,
49
round_off,
50
round_off_tds,
51
currency_id,
52
note,
53
bill_status_id,
54
created_by,
55
source_warehouse_id,
56
destination_warehouse_id,
57
bill_type_id,
58
created_on_utc,
59
po_no,
60
po_date
61
)
62
VALUES (
63
p_id,
64
p_company_id,
65
p_vendor_id,
66
p_credit_account_id,
67
p_debit_account_id,
68
v_bill_voucher,
69
p_bill_number,
70
p_bill_date,
71
p_due_date,
72
p_payment_term,
73
p_taxable_amount,
74
p_cgst_amount,
75
p_sgst_amount,
76
p_igst_amount,
77
p_tds_amount,
78
p_total_amount,
79
p_discount,
80
p_fees,
81
p_round_off,
82
p_round_off_tds,
83
p_currency_id,
84
p_note,
85
p_bill_status_id,
86
p_created_by,
87
p_source_warehouse_id,
88
p_destination_warehouse_id,
89
p_bill_type_id,
90
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
91
p_po_no,
92
p_po_date
93
);
94
95
RAISE NOTICE 'Bill header inserted successfully with Bill Voucher: %', v_bill_voucher;
96
END;
97
$procedure$
|
|||||
| Procedure | run_scheduled_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.run_scheduled_bill(IN p_bill_header_id uuid, IN p_draft_bill_header_id uuid, IN p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_system_user_id uuid;
6
v_execution_log_id uuid := gen_random_uuid();
7
v_related_schedule_id uuid;
8
v_new_draft_bill_id uuid;
9
v_source_is_live boolean := false;
10
v_source_bill_id uuid; -- final chosen source id (live or draft)
11
src_header RECORD;
12
src_detail RECORD;
13
BEGIN
14
----------------------------------------------------------------
15
-- 1. Find 'System' user (same as sales side)
16
----------------------------------------------------------------
17
SELECT u.id
18
INTO v_system_user_id
19
FROM public.users AS u
20
WHERE u.is_deleted = false
21
AND lower(u.first_name) = 'system'
22
ORDER BY u.created_on_utc NULLS LAST, u.id
23
LIMIT 1;
24
25
IF v_system_user_id IS NULL THEN
26
RAISE NOTICE 'No user with first_name="System" found; proceeding with NULL triggered_by.';
27
END IF;
28
29
----------------------------------------------------------------
30
-- 2. Decide source: live bill or draft bill (same idea as invoices)
31
----------------------------------------------------------------
32
IF p_bill_header_id IS NOT NULL THEN
33
v_source_bill_id := p_bill_header_id;
34
v_source_is_live := true;
35
ELSIF p_draft_bill_header_id IS NOT NULL THEN
36
v_source_bill_id := p_draft_bill_header_id;
37
v_source_is_live := false;
38
ELSE
39
RAISE NOTICE 'Both bill_header_id and draft_bill_header_id are NULL; aborting.';
40
RETURN;
41
END IF;
42
43
----------------------------------------------------------------
44
-- 3. Get related schedule_id from recurring_bill_schedules
45
-- must match on live OR draft id, just like sales version
46
----------------------------------------------------------------
47
SELECT s.id
48
INTO v_related_schedule_id
49
FROM public.recurring_bill_schedules AS s
50
WHERE s.is_deleted = false
51
AND (
52
(v_source_is_live AND s.bill_header_id = v_source_bill_id)
53
OR (NOT v_source_is_live AND s.draft_bill_header_id = v_source_bill_id)
54
)
55
ORDER BY s.starts_from DESC
56
LIMIT 1;
57
58
----------------------------------------------------------------
59
-- 4. Fetch source header from correct table
60
----------------------------------------------------------------
61
IF v_source_is_live THEN
62
SELECT *
63
INTO src_header
64
FROM public.bill_headers
65
WHERE id = v_source_bill_id
66
AND is_deleted = false;
67
ELSE
68
SELECT *
69
INTO src_header
70
FROM public.draft_bill_headers
71
WHERE id = v_source_bill_id
72
AND is_deleted = false;
73
END IF;
74
75
-- if nothing found, log failure and stop
76
IF NOT FOUND THEN
77
INSERT INTO public.schedule_execution_logs (
78
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
79
) VALUES (
80
v_execution_log_id,
81
v_related_schedule_id,
82
(p_schedule_date::date),
83
NOW(),
84
'Failure',
85
'Source bill header not found in live or draft tables.',
86
v_system_user_id,
87
false
88
);
89
90
RAISE NOTICE 'Source bill header % not found; aborting for date %.',
91
v_source_bill_id, (p_schedule_date::date);
92
RETURN;
93
END IF;
94
95
----------------------------------------------------------------
96
-- 5. Create new draft bill header (clone header-level data)
97
----------------------------------------------------------------
98
v_new_draft_bill_id := gen_random_uuid();
99
100
CALL public.create_draft_bill_header(
101
/* p_id */ v_new_draft_bill_id,
102
/* p_company_id */ src_header.company_id,
103
/* p_vendor_id */ src_header.vendor_id,
104
/* p_credit_account_id */ src_header.credit_account_id,
105
/* p_debit_account_id */ src_header.debit_account_id,
106
/* p_bill_date */ (p_schedule_date::date),
107
/* p_due_date */ ((p_schedule_date::date) + COALESCE(src_header.payment_term, 10))::date,
108
/* p_payment_term */ src_header.payment_term,
109
/* p_taxable_amount */ src_header.taxable_amount,
110
/* p_cgst_amount */ src_header.cgst_amount,
111
/* p_sgst_amount */ src_header.sgst_amount,
112
/* p_igst_amount */ src_header.igst_amount,
113
/* p_tds_amount */ COALESCE(src_header.tds_amount, 0),
114
/* p_total_amount */ src_header.total_amount,
115
/* p_discount */ src_header.discount,
116
/* p_fees */ src_header.fees,
117
/* p_round_off */ src_header.round_off,
118
/* p_round_off_tds */ COALESCE(src_header.round_off_tds, 0),
119
/* p_currency_id */ src_header.currency_id,
120
/* p_note (varchar) */ COALESCE(src_header.note, '')::varchar,
121
/* p_bill_status_id */ 1,
122
/* p_created_by */ v_system_user_id,
123
/* p_source_wh_id */ src_header.source_warehouse_id,
124
/* p_destination_wh_id */ src_header.destination_warehouse_id,
125
/* p_bill_type_id */ src_header.bill_type_id,
126
/* p_bill_number */ COALESCE(src_header.bill_number, '')::varchar,
127
/* p_po_no */ src_header.po_no::text,
128
/* p_po_date */ (src_header.po_date)::date
129
);
130
131
----------------------------------------------------------------
132
-- 6. Clone all details from correct source table
133
----------------------------------------------------------------
134
IF v_source_is_live THEN
135
FOR src_detail IN
136
SELECT *
137
FROM public.bill_details
138
WHERE bill_header_id = v_source_bill_id
139
LOOP
140
CALL public.create_draft_bill_detail(
141
v_new_draft_bill_id,
142
src_detail.price,
143
src_detail.quantity,
144
src_detail.discount,
145
src_detail.fees,
146
src_detail.cgst_amount,
147
src_detail.igst_amount,
148
src_detail.sgst_amount,
149
src_detail.taxable_amount,
150
src_detail.total_amount,
151
src_detail.serial_number,
152
src_detail.product_id
153
);
154
END LOOP;
155
ELSE
156
FOR src_detail IN
157
SELECT *
158
FROM public.draft_bill_details
159
WHERE bill_header_id = v_source_bill_id
160
LOOP
161
CALL public.create_draft_bill_detail(
162
v_new_draft_bill_id,
163
src_detail.price,
164
src_detail.quantity,
165
src_detail.discount,
166
src_detail.fees,
167
src_detail.cgst_amount,
168
src_detail.igst_amount,
169
src_detail.sgst_amount,
170
src_detail.taxable_amount,
171
src_detail.total_amount,
172
src_detail.serial_number,
173
src_detail.product_id
174
);
175
END LOOP;
176
END IF;
177
178
----------------------------------------------------------------
179
-- 7. Log success
180
----------------------------------------------------------------
181
INSERT INTO public.schedule_execution_logs (
182
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
183
) VALUES (
184
v_execution_log_id,
185
v_related_schedule_id,
186
(p_schedule_date::date),
187
NOW(),
188
'Success',
189
'',
190
v_system_user_id,
191
false
192
);
193
194
RAISE NOTICE 'Draft bill % created from % (live=%), schedule_id %, on %.',
195
v_new_draft_bill_id, v_source_bill_id, v_source_is_live, v_related_schedule_id, (p_schedule_date::date);
196
197
EXCEPTION WHEN OTHERS THEN
198
INSERT INTO public.schedule_execution_logs (
199
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
200
) VALUES (
201
COALESCE(v_execution_log_id, gen_random_uuid()),
202
v_related_schedule_id,
203
(p_schedule_date::date),
204
NOW(),
205
'Failure',
206
SQLERRM,
207
v_system_user_id,
208
false
209
);
210
211
RAISE NOTICE 'Error while cloning bill % on %: %',
212
v_source_bill_id, (p_schedule_date::date), SQLERRM;
213
END;
214
$procedure$
|
|||||
| Procedure | update_draft_bill_next_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_draft_bill_next_status(IN p_company_id uuid, IN p_bill_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill RECORD;
6
v_account_id uuid;
7
v_current_approval_level integer;
8
v_approval_level_required integer;
9
v_issue TEXT;
10
APPROVED_STATUS CONSTANT INTEGER := 3;
11
v_debug_next_status TEXT;
12
v_next_temp_id int;
13
BEGIN
14
RAISE NOTICE 'START update_draft_bill_next_status for company %, user %', p_company_id, p_modified_by;
15
RAISE NOTICE 'Input bill IDs: %', p_bill_ids;
16
17
FOR v_bill IN
18
SELECT id, bill_status_id, debit_account_id, current_approval_level
19
FROM public.draft_bill_headers
20
WHERE id = ANY(p_bill_ids) AND bill_status_id = 2
21
LOOP
22
RAISE NOTICE 'Processing Bill ID: %', v_bill.id;
23
24
v_account_id := v_bill.debit_account_id;
25
v_current_approval_level := v_bill.current_approval_level;
26
27
SELECT approval_level_required
28
INTO v_approval_level_required
29
FROM public.bill_approval_level_view
30
WHERE company_id = p_company_id
31
AND status_id = v_bill.bill_status_id
32
AND (account_id = v_account_id OR account_id IS NULL)
33
ORDER BY account_id ASC
34
LIMIT 1;
35
36
RAISE NOTICE 'Bill % current level: %, required: %', v_bill.id, v_current_approval_level, v_approval_level_required;
37
38
IF v_approval_level_required IS NULL THEN
39
v_issue := 'Approval level not found';
40
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
41
VALUES (v_bill.id, v_issue, now(), p_modified_by);
42
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
43
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
44
VALUES (v_next_temp_id, v_bill.id, 'Not found', v_issue);
45
RAISE NOTICE 'Bill % skipped: approval level not found', v_bill.id;
46
CONTINUE;
47
END IF;
48
49
-- 🔔 Replace this with call to `check_bill_approval_permissions`
50
PERFORM public.check_bill_approval_permissions(
51
p_company_id,
52
v_bill.bill_status_id,
53
p_modified_by,
54
v_account_id,
55
v_current_approval_level + 1
56
);
57
58
IF NOT FOUND THEN
59
v_issue := 'User not authorized to approve at the next level';
60
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
61
VALUES (v_bill.id, v_issue, now(), p_modified_by);
62
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
63
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
64
VALUES (v_next_temp_id, v_bill.id, 'User not authorized', v_issue);
65
RAISE NOTICE 'Bill % skipped: user not authorized', v_bill.id;
66
CONTINUE;
67
END IF;
68
69
IF v_current_approval_level + 1 < v_approval_level_required THEN
70
v_debug_next_status := CONCAT('Level ', v_current_approval_level + 1);
71
ELSE
72
v_debug_next_status := 'Approved';
73
END IF;
74
75
RAISE NOTICE 'Bill % next computed status: %', v_bill.id, v_debug_next_status;
76
77
IF v_approval_level_required > (v_current_approval_level + 1) THEN
78
UPDATE public.draft_bill_headers
79
SET current_approval_level = v_current_approval_level + 1,
80
modified_by = p_modified_by,
81
modified_on_utc = now()
82
WHERE id = v_bill.id;
83
84
INSERT INTO public.bill_approval_logs(
85
bill_id, status_id, approved_by, approved_on, "comment",
86
created_on_utc, created_by, approval_level
87
)
88
VALUES (
89
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
90
CONCAT('Approved at level ', v_current_approval_level + 1),
91
now(), p_modified_by, v_current_approval_level + 1
92
);
93
94
RAISE NOTICE 'Bill % moved to next approval level: %', v_bill.id, v_current_approval_level + 1;
95
96
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
97
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
98
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
99
ELSE
100
UPDATE public.draft_bill_headers
101
SET bill_status_id = APPROVED_STATUS,
102
modified_by = p_modified_by,
103
modified_on_utc = now(),
104
current_approval_level = v_current_approval_level + 1
105
WHERE id = v_bill.id;
106
107
INSERT INTO public.bill_approval_logs(
108
bill_id, status_id, approved_by, approved_on, "comment",
109
created_on_utc, created_by, approval_level
110
)
111
VALUES (
112
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
113
'Final Approval',
114
now(), p_modified_by, v_current_approval_level + 1
115
);
116
117
CALL transfer_draft_to_bill(v_bill.id, p_modified_by);
118
119
RAISE NOTICE 'Bill % approved and transferred', v_bill.id;
120
121
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
122
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
123
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
124
END IF;
125
126
RAISE NOTICE '--- Finished processing Bill: % ---', v_bill.id;
127
END LOOP;
128
129
RAISE NOTICE 'END update_draft_bill_next_status';
130
END
131
$procedure$
|
|||||
| Procedure | save_bill_and_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.save_bill_and_details(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_created_by uuid, IN p_currency_id integer, IN p_po_date timestamp without time zone, IN p_po_no text, IN p_destination_warehouse_id uuid, IN p_source_warehouse_id uuid, IN p_bill_type_id integer, IN p_bill_number text, IN p_tds_amount numeric, IN p_details jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill_voucher text;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
BEGIN
9
-- Set default values for warehouses
10
v_source_warehouse_id := COALESCE(p_source_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
11
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
12
13
-- Generate the new bill voucher number
14
v_bill_voucher := public.get_new_bill_voucher(p_company_id, p_bill_date::date);
15
RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
16
17
-- Validate p_details JSONB
18
IF p_details IS NULL OR jsonb_array_length(p_details) = 0 THEN
19
RAISE EXCEPTION 'Details JSONB parameter (p_details) cannot be empty or NULL';
20
END IF;
21
22
-- Insert into bill_headers table
23
INSERT INTO public.bill_headers (
24
id, company_id, vendor_id, discount, bill_voucher, bill_date, payment_term,
25
bill_status_id, due_date, total_amount, taxable_amount, fees, sgst_amount,
26
cgst_amount, igst_amount, note, round_off, round_off_tds, debit_account_id, credit_account_id,
27
created_on_utc, created_by, currency_id, po_date, po_no, destination_warehouse_id,
28
source_warehouse_id, bill_type_id, bill_number, tds_amount
29
) VALUES (
30
p_id, p_company_id, p_vendor_id, p_discount, v_bill_voucher, p_bill_date,
31
p_payment_term, p_bill_status_id, p_due_date, p_total_amount, p_taxable_amount, p_fees,
32
p_sgst_amount, p_cgst_amount, p_igst_amount, p_note, p_round_off, p_round_off_tds, p_debit_account_id,
33
p_credit_account_id, NOW(), p_created_by, p_currency_id, p_po_date, p_po_no,
34
v_destination_warehouse_id, v_source_warehouse_id, p_bill_type_id, p_bill_number, p_tds_amount
35
);
36
37
-- Insert into bill_details table
38
INSERT INTO public.bill_details (
39
id, bill_header_id, price, quantity, cgst_amount, discount, fees, igst_amount,
40
sgst_amount, taxable_amount, total_amount, serial_number, product_id
41
)
42
SELECT
43
gen_random_uuid(), p_id, (detail->>'price')::numeric, (detail->>'quantity')::numeric,
44
(detail->>'cgst_amount')::numeric, (detail->>'discount')::numeric, (detail->>'fees')::numeric,
45
(detail->>'igst_amount')::numeric, (detail->>'sgst_amount')::numeric, (detail->>'taxable_amount')::numeric,
46
(detail->>'total_amount')::numeric, (detail->>'serial_number')::integer, (detail->>'product_id')::uuid
47
FROM jsonb_array_elements(p_details) AS detail;
48
49
EXCEPTION
50
WHEN OTHERS THEN
51
RAISE EXCEPTION 'An error occurred: %', SQLERRM;
52
END;
53
$procedure$
|
|||||
| Procedure | update_draft_bill_next_status_1 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_draft_bill_next_status_1(IN p_company_id uuid, IN p_bill_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill RECORD;
6
v_account_id uuid;
7
v_current_approval_level integer;
8
v_approval_level_required integer;
9
v_issue TEXT;
10
APPROVED_STATUS CONSTANT INTEGER := 3;
11
v_debug_next_status TEXT;
12
v_next_temp_id int;
13
BEGIN
14
RAISE NOTICE 'START update_draft_bill_next_status_1 for company %, user %', p_company_id, p_modified_by;
15
RAISE NOTICE 'Input bill IDs: %', p_bill_ids;
16
17
FOR v_bill IN
18
SELECT id, bill_status_id, debit_account_id, current_approval_level
19
FROM public.draft_bill_headers
20
WHERE id = ANY(p_bill_ids) AND bill_status_id = 2
21
LOOP
22
RAISE NOTICE 'Processing Bill ID: %', v_bill.id;
23
24
v_account_id := v_bill.debit_account_id;
25
v_current_approval_level := v_bill.current_approval_level;
26
27
SELECT approval_level_required
28
INTO v_approval_level_required
29
FROM public.bill_approval_level_view
30
WHERE company_id = p_company_id
31
AND status_id = v_bill.bill_status_id
32
AND (account_id = v_account_id OR account_id IS NULL)
33
ORDER BY account_id ASC
34
LIMIT 1;
35
36
RAISE NOTICE 'Bill % current level: %, required: %', v_bill.id, v_current_approval_level, v_approval_level_required;
37
38
IF v_approval_level_required IS NULL THEN
39
v_issue := 'Approval level not found';
40
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
41
VALUES (v_bill.id, v_issue, now(), p_modified_by);
42
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
43
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
44
VALUES (v_next_temp_id, v_bill.id, 'Not found', v_issue);
45
RAISE NOTICE 'Bill % skipped: approval level not found', v_bill.id;
46
CONTINUE;
47
END IF;
48
49
-- 🔔 Replace this with call to `check_bill_approval_permissions`
50
PERFORM public.check_bill_approval_permissions(
51
52
p_company_id,
53
v_bill.bill_status_id,
54
p_modified_by,
55
v_account_id,
56
v_current_approval_level + 1
57
);
58
59
IF NOT FOUND THEN
60
v_issue := 'User not authorized to approve at the next level';
61
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
62
VALUES (v_bill.id, v_issue, now(), p_modified_by);
63
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
64
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
65
VALUES (v_next_temp_id, v_bill.id, 'User not authorized', v_issue);
66
RAISE NOTICE 'Bill % skipped: user not authorized', v_bill.id;
67
CONTINUE;
68
END IF;
69
70
IF v_current_approval_level + 1 < v_approval_level_required THEN
71
v_debug_next_status := CONCAT('Level ', v_current_approval_level + 1);
72
ELSE
73
v_debug_next_status := 'Approved';
74
END IF;
75
76
RAISE NOTICE 'Bill % next computed status: %', v_bill.id, v_debug_next_status;
77
78
IF v_approval_level_required > (v_current_approval_level + 1) THEN
79
UPDATE public.draft_bill_headers
80
SET current_approval_level = v_current_approval_level + 1,
81
modified_by = p_modified_by,
82
modified_on_utc = now()
83
WHERE id = v_bill.id;
84
85
INSERT INTO public.bill_approval_logs(
86
bill_id, status_id, approved_by, approved_on, "comment",
87
created_on_utc, created_by, approval_level
88
)
89
VALUES (
90
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
91
CONCAT('Approved at level ', v_current_approval_level + 1),
92
now(), p_modified_by, v_current_approval_level + 1
93
);
94
95
RAISE NOTICE 'Bill % moved to next approval level: %', v_bill.id, v_current_approval_level + 1;
96
97
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
98
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
99
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
100
ELSE
101
UPDATE public.draft_bill_headers
102
SET bill_status_id = APPROVED_STATUS,
103
modified_by = p_modified_by,
104
modified_on_utc = now(),
105
current_approval_level = v_current_approval_level + 1
106
WHERE id = v_bill.id;
107
108
INSERT INTO public.bill_approval_logs(
109
bill_id, status_id, approved_by, approved_on, "comment",
110
created_on_utc, created_by, approval_level
111
)
112
VALUES (
113
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
114
'Final Approval',
115
now(), p_modified_by, v_current_approval_level + 1
116
);
117
118
CALL transfer_draft_to_bill(v_bill.id, p_modified_by);
119
120
RAISE NOTICE 'Bill % approved and transferred', v_bill.id;
121
122
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
123
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
124
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
125
END IF;
126
127
RAISE NOTICE '--- Finished processing Bill: % ---', v_bill.id;
128
END LOOP;
129
130
RAISE NOTICE 'END update_draft_bill_next_status';
131
END
132
$procedure$
|
|||||
| Procedure | insert_multiple_bills_by_excel1 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_multiple_bills_by_excel1(IN p_bills jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
bill RECORD;
6
v_created_by uuid;
7
BEGIN
8
-- Validate the input JSONB structure
9
BEGIN
10
PERFORM *
11
FROM jsonb_to_recordset(p_bills) AS (
12
bill_id uuid,
13
company_id uuid,
14
vendor_id uuid,
15
debit_account_id uuid,
16
credit_account_id uuid,
17
discount numeric,
18
bill_number text,
19
currency_id integer,
20
bill_date timestamp without time zone,
21
payment_term integer,
22
bill_status_id integer,
23
due_date timestamp without time zone,
24
total_amount numeric,
25
note text,
26
taxable_amount numeric,
27
fees numeric,
28
sgst_amount numeric,
29
cgst_amount numeric,
30
igst_amount numeric,
31
tds_amount numeric,
32
round_off numeric,
33
po_no text,
34
po_date timestamp without time zone,
35
source_warehouse_id uuid,
36
destination_warehouse_id uuid,
37
bill_type_id integer,
38
lines jsonb,
39
created_by uuid
40
);
41
EXCEPTION
42
WHEN OTHERS THEN
43
RAISE EXCEPTION 'Invalid JSON input provided: %', SQLERRM;
44
RETURN;
45
END;
46
47
-- Get 'System' user as created_by
48
SELECT id INTO v_created_by
49
FROM public.users
50
WHERE first_name = 'System'
51
LIMIT 1;
52
53
-- Loop through each bill and process
54
FOR bill IN
55
SELECT * FROM jsonb_to_recordset(p_bills) AS (
56
bill_id uuid,
57
company_id uuid,
58
vendor_id uuid,
59
debit_account_id uuid,
60
credit_account_id uuid,
61
discount numeric,
62
bill_number text,
63
currency_id integer,
64
bill_date timestamp without time zone,
65
payment_term integer,
66
bill_status_id integer,
67
due_date timestamp without time zone,
68
total_amount numeric,
69
note text,
70
taxable_amount numeric,
71
fees numeric,
72
sgst_amount numeric,
73
cgst_amount numeric,
74
igst_amount numeric,
75
tds_amount numeric,
76
round_off numeric,
77
po_no text,
78
po_date timestamp without time zone,
79
source_warehouse_id uuid,
80
destination_warehouse_id uuid,
81
bill_type_id integer,
82
lines jsonb,
83
created_by uuid
84
)
85
LOOP
86
BEGIN
87
-- Check for duplicate
88
IF EXISTS (
89
SELECT 1
90
FROM public.bill_headers
91
WHERE bill_number = bill.bill_number
92
AND company_id = bill.company_id
93
) THEN
94
-- If duplicate found, log and skip
95
RAISE NOTICE 'Skipping duplicate bill_number: %, company_id: %', bill.bill_number, bill.company_id;
96
CONTINUE;
97
END IF;
98
99
-- Insert the bill if no duplicate
100
CALL public.save_bill_and_details(
101
bill.bill_id,
102
bill.company_id,
103
bill.vendor_id,
104
bill.discount,
105
bill.bill_date,
106
bill.payment_term,
107
bill.bill_status_id,
108
bill.due_date,
109
bill.total_amount,
110
bill.taxable_amount,
111
bill.fees,
112
bill.sgst_amount,
113
bill.cgst_amount,
114
bill.igst_amount,
115
bill.note,
116
bill.round_off,
117
bill.debit_account_id,
118
bill.credit_account_id,
119
v_created_by,
120
bill.currency_id,
121
bill.po_date,
122
bill.po_no,
123
bill.destination_warehouse_id,
124
bill.source_warehouse_id,
125
bill.bill_type_id,
126
bill.bill_number,
127
bill.tds_amount,
128
bill.lines
129
);
130
131
EXCEPTION
132
WHEN OTHERS THEN
133
-- Log error for this bill, but continue
134
RAISE NOTICE 'Error processing bill ID %: %', bill.bill_id, SQLERRM;
135
CONTINUE;
136
END;
137
END LOOP;
138
139
RAISE NOTICE 'All bills processed successfully (excluding duplicates/errors)';
140
END;
141
$procedure$
|
|||||
| Procedure | insert_draft_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_draft_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD; -- Declaring a record variable for the loop
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_detail_id uuid;
9
BEGIN
10
-- Set default values for warehouses based on bill type
11
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
12
v_source_warehouse_id := NULL;
13
v_destination_warehouse_id := NULL;
14
ELSE
15
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
16
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
17
END IF;
18
19
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
20
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
21
22
-- Call to create_draft_bill_header procedure
23
CALL public.create_draft_bill_header(
24
p_id,
25
p_company_id,
26
p_vendor_id,
27
p_credit_account_id,
28
p_debit_account_id,
29
p_bill_date::date,
30
p_due_date::date,
31
p_payment_term,
32
p_taxable_amount,
33
p_cgst_amount,
34
p_sgst_amount,
35
p_igst_amount,
36
p_tds_amount,
37
p_total_amount,
38
p_discount,
39
p_fees,
40
p_round_off,
41
p_currency_id,
42
p_note ,
43
p_bill_status_id ,
44
p_created_by ,
45
v_source_warehouse_id ,
46
v_destination_warehouse_id ,
47
p_bill_type_id,
48
p_bill_number,
49
p_po_no,
50
p_po_date::date
51
);
52
53
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
54
55
-- Loop through the JSONB array of bill details
56
FOR detail IN
57
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
58
id uuid, product_id uuid, price numeric, quantity numeric,
59
fees numeric, discount numeric, taxable_amount numeric,
60
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
61
total_amount numeric, serial_number integer
62
)
63
LOOP
64
-- Log each field to ensure they are being read correctly
65
--RAISE NOTICE 'Detail - product_id: %, price: %, quantity: %, taxable_amount: %', detail."productId", detail.price, detail.quantity, detail."taxableAmount";
66
67
-- Insert into draft_bill_details, using "productId" from JSON
68
INSERT INTO public.draft_bill_details (
69
id, bill_header_id, product_id, price, quantity, fees, discount,
70
taxable_amount, sgst_amount, cgst_amount, igst_amount,
71
total_amount, serial_number, is_deleted
72
) VALUES (
73
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
74
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
75
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
76
);
77
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
78
END LOOP;
79
80
-- No explicit COMMIT needed
81
82
EXCEPTION
83
-- No explicit ROLLBACK needed; just raise the error
84
WHEN OTHERS THEN
85
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
86
RAISE;
87
END;
88
$procedure$
|
|||||
| Procedure | insert_bill_by_excel | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_bill_by_excel(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_bill_number text, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_created_by uuid, IN p_bill_details jsonb, IN p_type integer, IN p_setteled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_bill_voucher text;
9
v_created_by uuid;
10
BEGIN
11
-- Set default values for warehouses
12
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
13
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
14
v_bill_voucher := get_new_bill_voucher(p_company_id, p_bill_date::date);
15
16
-- Check if the bill voucher was generated successfully
17
--IF v_bill_voucher IS NULL THEN
18
-- RAISE EXCEPTION 'Failed to generate bill voucher for company ID: %', p_company_id;
19
--ELSE
20
-- RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
21
--END IF;
22
23
-- Fetch the system user ID from the users table
24
SELECT id INTO v_created_by FROM public.users
25
WHERE first_name = 'System'
26
LIMIT 1;
27
28
-- Raise an exception if the system user is not found
29
IF v_created_by IS NULL THEN
30
RAISE EXCEPTION 'System user not found in the users table';
31
END IF;
32
33
-- Insert into the bill header
34
INSERT INTO public.bill_headers(
35
id,
36
company_id,
37
vendor_id,
38
credit_account_id,
39
debit_account_id,
40
bill_voucher,
41
bill_number,
42
bill_date,
43
due_date,
44
payment_term,
45
taxable_amount,
46
cgst_amount,
47
sgst_amount,
48
igst_amount,
49
tds_amount,
50
total_amount,
51
discount,
52
fees,
53
round_off,
54
currency_id,
55
note,
56
bill_status_id,
57
created_by,
58
source_warehouse_id,
59
destination_warehouse_id,
60
bill_type_id,
61
created_on_utc,
62
po_no,
63
po_date
64
)
65
VALUES (
66
p_id,
67
p_company_id,
68
p_vendor_id,
69
p_credit_account_id,
70
p_debit_account_id,
71
v_bill_voucher,
72
p_bill_number,
73
p_bill_date,
74
p_due_date,
75
p_payment_term,
76
p_taxable_amount,
77
p_cgst_amount,
78
p_sgst_amount,
79
p_igst_amount,
80
0,
81
p_total_amount,
82
p_discount,
83
p_fees,
84
p_round_off,
85
p_currency_id,
86
p_note,
87
p_bill_status_id,
88
p_created_by,
89
p_source_warehouse_id,
90
p_destination_warehouse_id,
91
p_type,
92
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
93
p_po_no,
94
p_po_date
95
);
96
97
RAISE NOTICE 'bill header inserted with ID: %', p_id;
98
EXCEPTION
99
-- Handle unique violation (duplicate key)
100
WHEN unique_violation THEN
101
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
102
ROLLBACK;
103
104
-- Handle foreign key violation
105
WHEN foreign_key_violation THEN
106
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
107
ROLLBACK;
108
109
-- Catch all other exceptions
110
WHEN OTHERS THEN
111
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
112
ROLLBACK;
113
END;
114
$procedure$
|
|||||
| Procedure | insert_bill | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
detail RECORD; -- Declaring a record variable for the loop
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
v_detail_id uuid;
9
BEGIN
10
-- Set default values for warehouses based on bill type
11
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
12
v_source_warehouse_id := NULL;
13
v_destination_warehouse_id := NULL;
14
ELSE
15
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
16
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
17
END IF;
18
19
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
20
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
21
22
-- Call to create_bill_header procedure
23
CALL public.create_bill_header(
24
p_id,
25
p_company_id,
26
p_vendor_id,
27
p_credit_account_id,
28
p_debit_account_id,
29
p_bill_date::date,
30
p_due_date::date,
31
p_payment_term,
32
p_taxable_amount,
33
p_cgst_amount,
34
p_sgst_amount,
35
p_igst_amount,
36
p_tds_amount,
37
p_total_amount,
38
p_discount,
39
p_fees,
40
p_round_off,
41
p_currency_id,
42
p_note ,
43
p_bill_status_id ,
44
p_created_by ,
45
v_source_warehouse_id ,
46
v_destination_warehouse_id ,
47
p_bill_type_id,
48
p_bill_number
49
);
50
51
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
52
53
-- Loop through the JSONB array of bill details
54
FOR detail IN
55
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
56
id uuid, product_id uuid, price numeric, quantity integer,
57
fees numeric, discount numeric, taxable_amount numeric,
58
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
59
total_amount numeric, serial_number integer
60
)
61
LOOP
62
INSERT INTO public.bill_details (
63
id, bill_header_id, product_id, price, quantity, fees, discount,
64
taxable_amount, sgst_amount, cgst_amount, igst_amount,
65
total_amount, serial_number, is_deleted
66
) VALUES (
67
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
68
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
69
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
70
);
71
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
72
END LOOP;
73
74
-- No explicit COMMIT needed
75
76
EXCEPTION
77
-- No explicit ROLLBACK needed; just raise the error
78
WHEN OTHERS THEN
79
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
80
RAISE;
81
END;
82
$procedure$
|
|||||
| Procedure | save_bill_and_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.save_bill_and_details(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_created_by uuid, IN p_currency_id integer, IN p_po_date timestamp without time zone, IN p_po_no text, IN p_destination_warehouse_id uuid, IN p_source_warehouse_id uuid, IN p_bill_type_id integer, IN p_bill_number text, IN p_tds_amount numeric, IN p_details jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill_voucher text;
6
v_source_warehouse_id uuid;
7
v_destination_warehouse_id uuid;
8
BEGIN
9
-- Set default values for warehouses
10
v_source_warehouse_id := COALESCE(p_source_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
11
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
12
13
-- Generate the new bill voucher number
14
v_bill_voucher := public.get_new_bill_voucher(p_company_id, p_bill_date::date);
15
RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
16
17
-- Validate p_details JSONB
18
IF p_details IS NULL OR jsonb_array_length(p_details) = 0 THEN
19
RAISE EXCEPTION 'Details JSONB parameter (p_details) cannot be empty or NULL';
20
END IF;
21
22
-- Insert into bill_headers table
23
INSERT INTO public.bill_headers (
24
id, company_id, vendor_id, discount, bill_voucher, bill_date, payment_term,
25
bill_status_id, due_date, total_amount, taxable_amount, fees, sgst_amount,
26
cgst_amount, igst_amount, note, round_off, debit_account_id, credit_account_id,
27
created_on_utc, created_by, currency_id, po_date, po_no, destination_warehouse_id,
28
source_warehouse_id, bill_type_id, bill_number, tds_amount
29
) VALUES (
30
p_id, p_company_id, p_vendor_id, p_discount, v_bill_voucher, p_bill_date,
31
p_payment_term, p_bill_status_id, p_due_date, p_total_amount, p_taxable_amount, p_fees,
32
p_sgst_amount, p_cgst_amount, p_igst_amount, p_note, p_round_off, p_debit_account_id,
33
p_credit_account_id, NOW(), p_created_by, p_currency_id, p_po_date, p_po_no,
34
v_destination_warehouse_id, v_source_warehouse_id, p_bill_type_id, p_bill_number, p_tds_amount
35
);
36
37
-- Insert into bill_details table
38
INSERT INTO public.bill_details (
39
id, bill_header_id, price, quantity, cgst_amount, discount, fees, igst_amount,
40
sgst_amount, taxable_amount, total_amount, serial_number, product_id
41
)
42
SELECT
43
gen_random_uuid(), p_id, (detail->>'price')::numeric, (detail->>'quantity')::numeric,
44
(detail->>'cgst_amount')::numeric, (detail->>'discount')::numeric, (detail->>'fees')::numeric,
45
(detail->>'igst_amount')::numeric, (detail->>'sgst_amount')::numeric, (detail->>'taxable_amount')::numeric,
46
(detail->>'total_amount')::numeric, (detail->>'serial_number')::integer, (detail->>'product_id')::uuid
47
FROM jsonb_array_elements(p_details) AS detail;
48
49
EXCEPTION
50
WHEN OTHERS THEN
51
RAISE EXCEPTION 'An error occurred: %', SQLERRM;
52
END;
53
$procedure$
|
|||||
| Procedure | create_draft_bill_header | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_draft_bill_header(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_bill_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_currency_id integer, IN p_note character varying, IN p_bill_status_id integer, IN p_created_by uuid, IN p_source_warehouse_id uuid DEFAULT NULL::uuid, IN p_destination_warehouse_id uuid DEFAULT NULL::uuid, IN p_bill_type_id integer DEFAULT 1, IN p_bill_number character varying DEFAULT NULL::character varying, IN p_po_no text DEFAULT NULL::text, IN p_po_date date DEFAULT NULL::date)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_bill_voucher character varying;
6
source_warehouse_id uuid;
7
destination_warehouse_id uuid;
8
BEGIN
9
-- Generate bill voucher using the draft bill function
10
v_bill_voucher := get_new_draft_bill_voucher(p_company_id,p_bill_date);
11
12
-- Log the generated bill voucher
13
RAISE NOTICE 'Bill Voucher: %', v_bill_voucher;
14
15
-- Set default values for warehouses based on bill type
16
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
17
source_warehouse_id := NULL;
18
destination_warehouse_id := NULL;
19
ELSE
20
-- Use passed values or default to null if not provided
21
source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
22
destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
23
END IF;
24
25
-- Insert into draft_bill_headers table
26
INSERT INTO
27
public.draft_bill_headers(
28
id,
29
company_id,
30
vendor_id,
31
credit_account_id,
32
debit_account_id,
33
bill_voucher,
34
bill_number,
35
bill_date,
36
due_date,
37
payment_term,
38
taxable_amount,
39
cgst_amount,
40
sgst_amount,
41
igst_amount,
42
tds_amount,
43
total_amount,
44
discount,
45
fees,
46
round_off,
47
currency_id,
48
note,
49
bill_status_id,
50
created_by,
51
source_warehouse_id,
52
destination_warehouse_id,
53
bill_type_id,
54
created_on_utc,
55
po_no,
56
po_date
57
)
58
VALUES (
59
p_id,
60
p_company_id,
61
p_vendor_id,
62
p_credit_account_id,
63
p_debit_account_id,
64
v_bill_voucher,
65
p_bill_number,
66
p_bill_date,
67
p_due_date,
68
p_payment_term,
69
p_taxable_amount,
70
p_cgst_amount,
71
p_sgst_amount,
72
p_igst_amount,
73
p_tds_amount,
74
p_total_amount,
75
p_discount,
76
p_fees,
77
p_round_off,
78
p_currency_id,
79
p_note,
80
p_bill_status_id,
81
p_created_by,
82
p_source_warehouse_id,
83
p_destination_warehouse_id,
84
p_bill_type_id,
85
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
86
p_po_no,
87
p_po_date
88
);
89
90
-- Log success message
91
RAISE NOTICE 'Draft Bill header inserted successfully with Bill Voucher: % ', v_bill_voucher ;
92
93
END;
94
$procedure$
|
|||||
| Procedure | create_schedule_bills | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_schedule_bills(IN p_company_id uuid, IN p_frequency_cycle text, IN p_starts_from date, IN p_end_date date, IN p_created_by uuid, IN p_bill_header_id uuid DEFAULT NULL::uuid, IN p_draft_bill_header_id uuid DEFAULT NULL::uuid, IN p_month_of_year integer DEFAULT NULL::integer, IN p_day_of_month integer DEFAULT NULL::integer, IN p_day_of_week integer DEFAULT NULL::integer, IN p_time_of_day interval DEFAULT '00:00:00'::interval)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_recurring_schedule_id uuid := gen_random_uuid(); -- New ID for recurring_bill_schedules
6
v_schedule_status text;
7
8
-- Normalized copies (treat all-zero GUID as NULL)
9
v_bill_header_id uuid := NULLIF(p_bill_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
10
v_draft_bill_header_id uuid := NULLIF(p_draft_bill_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
11
12
v_day_of_week integer := COALESCE(p_day_of_week, 0);
13
v_day_of_month integer := COALESCE(p_day_of_month, 0);
14
v_month_of_year integer := COALESCE(p_month_of_year, 0);
15
v_time_of_day interval := COALESCE(p_time_of_day, '00:00:00'::interval);
16
BEGIN
17
----------------------------------------------------------------
18
-- Validate: exactly ONE of bill_header_id / draft_bill_header_id
19
----------------------------------------------------------------
20
IF (v_bill_header_id IS NULL AND v_draft_bill_header_id IS NULL) THEN
21
RAISE EXCEPTION 'Either bill_header_id or draft_bill_header_id must be provided.';
22
ELSIF (v_bill_header_id IS NOT NULL AND v_draft_bill_header_id IS NOT NULL) THEN
23
RAISE EXCEPTION 'Provide only one of bill_header_id or draft_bill_header_id, not both.';
24
END IF;
25
26
----------------------------------------------------------------
27
-- Determine the initial schedule status
28
----------------------------------------------------------------
29
IF p_starts_from > CURRENT_DATE THEN
30
v_schedule_status := 'inactive';
31
ELSE
32
v_schedule_status := 'active';
33
END IF;
34
35
----------------------------------------------------------------
36
-- Insert into the recurring_bill_schedules table
37
----------------------------------------------------------------
38
INSERT INTO public.recurring_bill_schedules (
39
id,
40
bill_header_id,
41
draft_bill_header_id,
42
company_id,
43
frequency_cycle,
44
schedule_status,
45
starts_from,
46
end_date,
47
created_on_utc,
48
created_by,
49
is_deleted
50
)
51
VALUES (
52
v_recurring_schedule_id,
53
v_bill_header_id,
54
v_draft_bill_header_id,
55
p_company_id,
56
p_frequency_cycle,
57
v_schedule_status,
58
p_starts_from,
59
p_end_date,
60
NOW(),
61
p_created_by,
62
false
63
);
64
65
----------------------------------------------------------------
66
-- Insert into recurrence_bill_schedule_details
67
----------------------------------------------------------------
68
IF p_frequency_cycle = 'Daily' THEN
69
INSERT INTO public.recurrence_bill_schedule_details (
70
schedule_id,
71
frequency_cycle,
72
time_of_day,
73
is_deleted
74
)
75
VALUES (
76
v_recurring_schedule_id,
77
p_frequency_cycle,
78
v_time_of_day,
79
false
80
);
81
82
ELSIF p_frequency_cycle = 'Weekly' THEN
83
INSERT INTO public.recurrence_bill_schedule_details (
84
schedule_id,
85
frequency_cycle,
86
day_of_week,
87
time_of_day,
88
is_deleted
89
)
90
VALUES (
91
v_recurring_schedule_id,
92
p_frequency_cycle,
93
v_day_of_week,
94
v_time_of_day,
95
false
96
);
97
98
ELSIF p_frequency_cycle = 'Monthly' THEN
99
INSERT INTO public.recurrence_bill_schedule_details (
100
schedule_id,
101
frequency_cycle,
102
day_of_month,
103
time_of_day,
104
is_deleted
105
)
106
VALUES (
107
v_recurring_schedule_id,
108
p_frequency_cycle,
109
v_day_of_month,
110
v_time_of_day,
111
false
112
);
113
114
ELSIF p_frequency_cycle = 'Yearly' THEN
115
INSERT INTO public.recurrence_bill_schedule_details (
116
schedule_id,
117
frequency_cycle,
118
month_of_year,
119
day_of_month,
120
time_of_day,
121
is_deleted
122
)
123
VALUES (
124
v_recurring_schedule_id,
125
p_frequency_cycle,
126
v_month_of_year,
127
v_day_of_month,
128
v_time_of_day,
129
false
130
);
131
132
ELSE
133
RAISE NOTICE 'Invalid frequency cycle: %', p_frequency_cycle;
134
END IF;
135
136
RAISE NOTICE 'Recurring bill schedule created successfully with ID: %, Status: %',
137
v_recurring_schedule_id, v_schedule_status;
138
END;
139
$procedure$
|
|||||
| View | bill_details_with_payments | Missing in Target |
Source Script
Target Script
1
SELECT bh.bill_number,
2
bh.bill_date,
3
bd.product_id,
4
bd.quantity,
5
bd.price,
6
bd.total_amount AS bill_amount,
7
COALESCE(bp.paid_amount, (0)::numeric) AS paid_amount,
8
(bd.total_amount - COALESCE(bp.paid_amount, (0)::numeric)) AS remaining_amount,
9
bd.cgst_amount,
10
bd.sgst_amount,
11
bd.igst_amount,
12
bd.taxable_amount,
13
bd.discount,
14
bd.fees
15
FROM (((bill_details bd
16
JOIN bill_headers bh ON ((bd.bill_header_id = bh.id)))
17
LEFT JOIN bill_payment_details bpd ON ((bd.bill_header_id = bpd.bill_header_id)))
18
LEFT JOIN bill_payment_headers bp ON ((bpd.bill_payment_header_id = bp.id)))
19
WHERE ((bh.is_deleted = false) AND (bd.is_deleted = false));
|
|||||
| View | bill_approval_level_view | Missing in Target |
Source Script
Target Script
1
SELECT company_id,
2
account_id,
3
status_id,
4
COALESCE(( SELECT bill_account_approval_levels.approval_level_required
5
FROM bill_account_approval_levels
6
WHERE ((bill_account_approval_levels.company_id = i.company_id) AND (bill_account_approval_levels.account_id = i.account_id) AND (bill_account_approval_levels.status_id = i.status_id) AND (bill_account_approval_levels.is_deleted = false))
7
LIMIT 1), ( SELECT bill_workflow.approval_level
8
FROM bill_workflow
9
WHERE ((bill_workflow.company_id = i.company_id) AND (bill_workflow.status = i.status_id) AND (bill_workflow.is_deleted = false) AND (bill_workflow.is_enabled = true))
10
LIMIT 1)) AS approval_level_required
11
FROM ( SELECT bill_account_approval_levels.company_id,
12
bill_account_approval_levels.account_id,
13
bill_account_approval_levels.status_id
14
FROM bill_account_approval_levels
15
UNION
16
SELECT bill_workflow.company_id,
17
NULL::uuid AS account_id,
18
bill_workflow.status AS status_id
19
FROM bill_workflow) i;
|
|||||
| View | bill_with_details | Missing in Target |
Source Script
Target Script
1
SELECT bh.bill_number,
2
bh.bill_date,
3
bd.product_id,
4
bd.quantity,
5
bd.price,
6
bd.total_amount AS bill_amount,
7
COALESCE(bp.paid_amount, (0)::numeric) AS paid_amount,
8
(bd.total_amount - COALESCE(bp.paid_amount, (0)::numeric)) AS remaining_amount,
9
bd.cgst_amount,
10
bd.sgst_amount,
11
bd.igst_amount,
12
bd.taxable_amount,
13
bd.discount,
14
bd.fees
15
FROM (((bill_details bd
16
JOIN bill_headers bh ON ((bd.bill_header_id = bh.id)))
17
LEFT JOIN bill_payment_details bpd ON ((bd.bill_header_id = bpd.bill_header_id)))
18
LEFT JOIN bill_payment_headers bp ON ((bpd.bill_payment_header_id = bp.id)))
19
WHERE ((bh.is_deleted = false) AND (bd.is_deleted = false));
|
|||||
| View | vw_bill_approval_permissions | Missing in Target |
Source Script
Target Script
1
SELECT company_id,
2
status_id,
3
user_id,
4
approval_level,
5
account_id,
6
account_approval_level,
7
account_status_id
8
FROM ( SELECT baua.company_id,
9
baua.status_id,
10
baua.user_id,
11
baua.approval_level,
12
baua.account_id,
13
baua.approval_level AS account_approval_level,
14
baua.status_id AS account_status_id
15
FROM bill_approval_user_account baua
16
WHERE (baua.is_deleted = false)
17
UNION ALL
18
SELECT bauc.company_id,
19
bauc.status_id,
20
bauc.user_id,
21
bauc.approval_level,
22
NULL::uuid AS account_id,
23
NULL::integer AS account_approval_level,
24
bauc.status_id AS account_status_id
25
FROM bill_approval_user_company bauc
26
WHERE ((bauc.is_deleted = false) AND (NOT (EXISTS ( SELECT 1
27
FROM bill_approval_user_account baua
28
WHERE ((baua.company_id = bauc.company_id) AND (baua.status_id = bauc.status_id) AND (baua.user_id = bauc.user_id) AND (baua.is_deleted = false))))))) sub;
|
-- Table: bill_account_approval_levels
Exists in source, missing in target
-- Table: bill_workflow
Exists in source, missing in target
-- Table: temp_bill_next_status
Exists in source, missing in target
-- Table: vendor_advance_refunds
Exists in source, missing in target
-- Table: vendor_advance_settlement_ids
Exists in source, missing in target
-- Table: vendor_advance_settlements
Exists in source, missing in target
-- Table: v_organization_id
Exists in source, missing in target
-- Table: user_roles
Exists in source, missing in target
-- Table: schema_versions
Exists in source, missing in target
-- Table: vendor_note_statuses
Exists in source, missing in target
-- Table: vendor_categories
Exists in source, missing in target
-- Table: vendor_note_work_flows
Exists in source, missing in target
-- Table: bill_header_ids
Exists in source, missing in target
-- Table: bill_status_company_configs
Exists in source, missing in target
-- Table: vendor_advances
Exists in source, missing in target
-- Table: vendor_note_details
Exists in source, missing in target
-- Table: bill_payment_details
Exists in source, missing in target
-- Table: cities
Exists in source, missing in target
-- Table: draft_bill_details
Exists in source, missing in target
-- Table: companies
Exists in source, missing in target
-- Table: countries
Exists in source, missing in target
-- Table: gate_pass_details
Exists in source, missing in target
-- Table: organizations
Exists in source, missing in target
-- Table: schedule_execution_logs
Exists in source, missing in target
-- Table: roles
Exists in source, missing in target
-- Table: vendor_contacts
Exists in source, missing in target
-- Table: vendor_bank_accounts
Exists in source, missing in target
-- Table: banks
Exists in source, missing in target
-- Table: bill_details
Exists in source, missing in target
-- Table: bill_approval_user_company
Exists in source, missing in target
-- Table: bill_approval_logs
Exists in source, missing in target
-- Table: bill_approval_user_account
Exists in source, missing in target
-- Table: addresses
Exists in source, missing in target
-- Table: bill_payment_headers
Exists in source, missing in target
-- Table: bill_headers
Exists in source, missing in target
-- Table: bill_approval_issue_logs
Exists in source, missing in target
-- Table: __EFMigrationsHistory
Exists in source, missing in target
-- Table: bank_accounts
Exists in source, missing in target
-- Table: bill_payment_header_ids
Exists in source, missing in target
-- Table: bill_statuses
Exists in source, missing in target
-- Table: draft_bill_headers
Exists in source, missing in target
-- Table: gate_pass_statuses
Exists in source, missing in target
-- Table: draft_bill_header_ids
Exists in source, missing in target
-- Table: contacts
Exists in source, missing in target
-- Table: bill_types
Exists in source, missing in target
-- Table: payment_orders
Exists in source, missing in target
-- Table: warehouses
Exists in source, missing in target
-- Table: gate_pass_headers
Exists in source, missing in target
-- Table: payment_statuses
Exists in source, missing in target
-- Table: vendor_advance_ids
Exists in source, missing in target
-- Table: tds_statuses
Exists in source, missing in target
-- Table: upis
Exists in source, missing in target
-- Table: recurrence_bill_schedule_details
Exists in source, missing in target
-- Table: procedure_logs
Exists in source, missing in target
-- Table: states
Exists in source, missing in target
-- Table: users
Exists in source, missing in target
-- Table: vendor_default_accounts
Exists in source, missing in target
-- Table: vendor_note_headers
Exists in source, missing in target
-- Table: vendor_upis
Exists in source, missing in target
-- Table: vendors
Exists in source, missing in target
-- Table: recurring_bill_schedules
Exists in source, missing in target
-- Function: checkin_service_provider
CREATE OR REPLACE FUNCTION public.checkin_service_provider(p_apartment_id uuid, p_pin text, p_created_by uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_service_provider_id int;
v_log_id int;
BEGIN
-- Step 1: Validate service provider by apartmentId and pin
SELECT id INTO v_service_provider_id
FROM public.service_providers
WHERE apartment_id = p_apartment_id
AND pin = p_pin
AND is_deleted = false
LIMIT 1;
IF v_service_provider_id IS NULL THEN
RAISE EXCEPTION 'Service provider not found for apartment % with pin %', p_apartment_id, p_pin
USING ERRCODE = 'no_data_found';
END IF;
-- Step 2: Insert service provider log (CheckedIn)
INSERT INTO public.service_provider_logs (
service_provider_id,
current_status_id,
entry_time,
created_on_utc,
created_by
)
VALUES (
v_service_provider_id,
1, -- CheckedIn
now(),
now(),
p_created_by
)
RETURNING id INTO v_log_id;
RETURN v_log_id;
END;
$function$
-- Function: bulk_delete_vendors_with_flag
CREATE OR REPLACE FUNCTION public.bulk_delete_vendors_with_flag(p_vendor_ids uuid[], p_modified_by uuid, p_deleted_on_utc timestamp without time zone, p_is_all_possible_delete boolean)
RETURNS TABLE(id integer, vendor_id uuid, status text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_id UUID;
v_blocked_count INT;
row_index INT := 1;
BEGIN
FOREACH v_id IN ARRAY p_vendor_ids LOOP
-- 1. If `isAllPossibleDelete` is true, proceed with deletion for eligible vendors and skip blocked vendors
IF p_is_all_possible_delete THEN
-- 1.1. Skip if vendor already deleted
IF NOT EXISTS (
SELECT 1 FROM public.vendors v WHERE v.id = v_id AND v.is_deleted = false
) THEN
id := row_index;
bulk_delete_vendors_with_flag.vendor_id := v_id;
bulk_delete_vendors_with_flag.status := 'Skipped - Vendor Not Found or Already Deleted';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 1.2. Check if any financial bills exist
SELECT COUNT(*) INTO v_blocked_count
FROM (
SELECT 1 FROM public.bill_headers bh
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id IN (3, 4)
UNION ALL
SELECT 1 FROM public.draft_bill_headers dbh
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id IN (3, 4)
) AS financial_bills;
IF v_blocked_count > 0 THEN
-- If blocked, skip deletion and mark the status as blocked
id := row_index;
bulk_delete_vendors_with_flag.vendor_id := v_id;
bulk_delete_vendors_with_flag.status := 'Blocked - Financial Bills Exist (Approved/Partially Paid/Paid)';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 1.3. Soft delete vendor and associated records
UPDATE public.vendors v
SET is_deleted = true,
modified_by = p_modified_by,
deleted_on_utc = p_deleted_on_utc
WHERE v.id = v_id;
-- 1.4. Return success
id := row_index;
bulk_delete_vendors_with_flag.vendor_id := v_id;
bulk_delete_vendors_with_flag.status := 'Deleted';
row_index := row_index + 1;
RETURN NEXT;
ELSE
-- 2. If `isAllPossibleDelete` is false, just return the status without deletion
-- 2.1. Skip if vendor already deleted
IF NOT EXISTS (
SELECT 1 FROM public.vendors v WHERE v.id = v_id AND v.is_deleted = false
) THEN
id := row_index;
bulk_delete_vendors_with_flag.vendor_id := v_id;
bulk_delete_vendors_with_flag.status := 'Skipped - Vendor Not Found or Already Deleted';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 2.2. Check if any financial bills exist
SELECT COUNT(*) INTO v_blocked_count
FROM (
SELECT 1 FROM public.bill_headers bh
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id IN (3, 4)
UNION ALL
SELECT 1 FROM public.draft_bill_headers dbh
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id IN (3, 4)
) AS financial_bills;
IF v_blocked_count > 0 THEN
-- If blocked, return blocked status
id := row_index;
bulk_delete_vendors_with_flag.vendor_id := v_id;
bulk_delete_vendors_with_flag.status := 'Blocked - Financial Bills Exist (Approved/Partially Paid/Paid)';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 2.3. If no issues, return that vendor is eligible for deletion
id := row_index;
bulk_delete_vendors_with_flag.vendor_id := v_id;
bulk_delete_vendors_with_flag.status := 'Eligible for Deletion';
row_index := row_index + 1;
RETURN NEXT;
END IF;
END LOOP;
END;
$function$
-- Function: create_vendor_advance_settlements
CREATE OR REPLACE FUNCTION public.create_vendor_advance_settlements(p_company_id uuid, p_vendor_id uuid, p_bill_payment_header_id uuid, p_created_by uuid, p_settlements jsonb)
RETURNS TABLE(id uuid, advance_settlement_number text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_settlement_number text;
v_row jsonb;
v_id uuid;
BEGIN
FOR v_row IN SELECT * FROM jsonb_array_elements(p_settlements)
LOOP
v_settlement_number := public.get_new_vendor_advance_settlement_number(
p_company_id,
COALESCE((v_row->>'settled_date')::date, CURRENT_DATE)
);
v_id := (v_row->>'id')::uuid;
INSERT INTO vendor_advance_settlements
(
id,
company_id,
vendor_id,
advance_id,
bill_id,
apply_amount,
applied_in_payment_id,
settled_date,
note,
created_on_utc,
created_by,
is_deleted,
advance_settlement_number
)
VALUES
(
v_id,
p_company_id,
p_vendor_id,
(v_row->>'advance_id')::uuid,
(v_row->>'bill_id')::uuid,
(v_row->>'apply_amount')::numeric,
p_bill_payment_header_id,
COALESCE((v_row->>'settled_date')::timestamp, now()),
NULLIF(v_row->>'note',''),
now(),
p_created_by,
false,
v_settlement_number
);
UPDATE vendor_advances va
SET utilized_amount = utilized_amount + (v_row->>'apply_amount')::numeric,
modified_by = p_created_by,
modified_on_utc = now()
WHERE va.id = (v_row->>'advance_id')::uuid
AND va.company_id = p_company_id
AND va.vendor_id = p_vendor_id
AND va.is_deleted = false;
-- Assign to output variables, then RETURN NEXT;
id := v_id;
advance_settlement_number := v_settlement_number;
RETURN NEXT;
END LOOP;
END;
$function$
-- Function: create_user
CREATE OR REPLACE FUNCTION public.create_user(p_user_id uuid, p_email text, p_phone_number text, p_first_name text, p_last_name text, p_created_by uuid, p_company_id uuid)
RETURNS uuid
LANGUAGE plpgsql
AS $function$
DECLARE
v_existing_user_id UUID; -- Variable to store the user ID
BEGIN
-- Step 1: Check if the user already exists based on email
SELECT id INTO v_existing_user_id
FROM public.users
WHERE email = p_email;
-- If user already exists, return the existing user_id
IF v_existing_user_id IS NOT NULL THEN
RETURN v_existing_user_id;
END IF;
-- Step 2: Insert into users table
INSERT INTO public.users (
id,
email,
phone_number,
first_name,
last_name,
password_hash,
created_on_utc,
created_by,
company_id
) VALUES (
p_user_id, -- Generate a new UUID for the user ID
p_email, -- User email
p_phone_number, -- User phone number
p_first_name, -- User first name
p_last_name, -- Default empty last name
'', -- Default empty password hash
NOW(), -- Current timestamp
p_created_by, -- Created by
p_company_id -- Link to the provided company
);
-- Return the new user ID
RETURN p_user_id;
END;
$function$
-- Function: get_all_bill_company_approvals
CREATE OR REPLACE FUNCTION public.get_all_bill_company_approvals(p_company_id uuid)
RETURNS TABLE(id integer, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
MIN(ba.id) AS id,
ba.status_id,
ba.user_id,
ba.approval_level,
bw.approval_level AS required_approval_level
FROM bill_approval_user_company ba
JOIN bill_workflow bw
ON ba.company_id = bw.company_id
WHERE ba.company_id = p_company_id
--AND ba.status = 2
AND ba.is_deleted = FALSE
AND bw.is_deleted = FALSE
AND bw.is_enabled = TRUE
AND bw.status = 2
GROUP BY ba.status_id, ba.user_id, ba.approval_level, bw.approval_level
ORDER BY ba.status_id, ba.user_id;
END;
$function$
-- Function: get_all_unit_names_by_apartment_id
CREATE OR REPLACE FUNCTION public.get_all_unit_names_by_apartment_id(p_apartment_id uuid)
RETURNS TABLE(id integer, unit_id integer, unit_name text, building_name text, floor_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
CAST(ROW_NUMBER() OVER (ORDER BY u.id) AS INT) AS id,
u.id AS unit_id,
u."name" AS unit_name,
b."name" AS building_name,
f."name" AS floor_name
FROM units u
INNER JOIN buildings b ON u.building_id = b.id
INNER JOIN floors f ON u.floor_id = f.id
WHERE u.apartment_id = p_apartment_id
AND u.is_deleted = false
AND b.is_deleted = false
AND f.is_deleted = false;
END;
$function$
-- Function: get_all_schedule_bills
CREATE OR REPLACE FUNCTION public.get_all_schedule_bills(p_company_id uuid)
RETURNS TABLE(schedule_id uuid, bill_header_id uuid, company_id uuid, frequency_cycle text, schedule_status text, starts_from timestamp without time zone, end_date timestamp without time zone, created_by uuid, bill_number text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
rbs.id AS schedule_id,
rbs.bill_header_id,
rbs.company_id,
rbs.frequency_cycle,
rbs.schedule_status,
rbs.starts_from, -- TIMESTAMP type
rbs.end_date, -- TIMESTAMP type
rbs.created_by,
bh.bill_number
FROM public.recurring_bill_schedules rbs
LEFT JOIN public.bill_headers bh ON bh.id = rbs.bill_header_id
WHERE rbs.company_id = p_company_id
ORDER BY rbs.starts_from DESC;
END;
$function$
-- Function: get_all_bills_from_span
CREATE OR REPLACE FUNCTION public.get_all_bills_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_bill_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id uuid, bill_voucher text, bill_number text, vendor_name character varying, vendor_id uuid, due_date date, bill_date date, payment_term integer, total_amount numeric, total_paid_amount numeric, currency_id integer, bill_status character varying, bill_status_id integer, payment_status character varying, payment_status_id integer, tds_status character varying, tds_status_id integer, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, created_by_name character varying, modified_by_name character varying, bill_type character varying, bill_type_id integer, tds_amount numeric, has_next_status_approval boolean, next_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
DRAFT_STATUS CONSTANT integer := 1;
BEGIN
RETURN QUERY
WITH bill_data AS (
SELECT
bh.id,
bh.bill_voucher,
bh.bill_number,
v.name AS vendor_name,
bh.vendor_id,
bh.due_date,
bh.bill_date,
bh.payment_term,
bh.total_amount,
bh.total_paid_amount,
bh.currency_id,
bs.name AS bill_status,
bh.bill_status_id,
ps.name AS payment_status, -- ✅ Added
bh.payment_status_id, -- ✅ Added
ts.name AS tds_status, -- ✅ Added
bh.tds_status_id, -- ✅ Added
bh.created_by,
bh.created_on_utc,
bh.modified_by,
bh.modified_on_utc,
CONCAT(cu.first_name, ' ', cu.last_name)::character varying AS created_by_name,
CONCAT(mu.first_name, ' ', mu.last_name)::character varying AS modified_by_name,
bt.name AS bill_type,
bh.bill_type_id,
bh.tds_amount,
bh.debit_account_id
FROM public.bill_headers bh
LEFT JOIN public.vendors v ON v.id = bh.vendor_id
LEFT JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
LEFT JOIN public.payment_statuses ps ON ps.id = bh.payment_status_id
LEFT JOIN public.tds_statuses ts ON ts.id = bh.tds_status_id -- ✅ Added
LEFT JOIN public.bill_types bt ON bt.id = bh.bill_type_id
LEFT JOIN public.users cu ON cu.id = bh.created_by
LEFT JOIN public.users mu ON mu.id = bh.modified_by
WHERE bh.company_id = p_company_id
AND bh.is_deleted = false
AND (p_bill_type_id IS NULL OR p_bill_type_id = 0 OR bh.bill_type_id = p_bill_type_id)
AND bh.bill_date BETWEEN v_start_date AND v_end_date
UNION ALL
SELECT
dbh.id,
dbh.bill_voucher,
dbh.bill_number,
v.name AS vendor_name,
dbh.vendor_id,
dbh.due_date,
dbh.bill_date,
dbh.payment_term,
dbh.total_amount,
0::numeric(18,2) AS total_paid_amount,
dbh.currency_id,
bs.name AS bill_status,
dbh.bill_status_id,
ps.name AS payment_status, -- ✅ Added
dbh.payment_status_id, -- ✅ Added
ts.name AS tds_status, -- ✅ Added
dbh.tds_status_id, -- ✅ Added
dbh.created_by,
dbh.created_on_utc,
dbh.modified_by,
dbh.modified_on_utc,
CONCAT(cu.first_name, ' ', cu.last_name)::character varying AS created_by_name,
CONCAT(mu.first_name, ' ', mu.last_name)::character varying AS modified_by_name,
bt.name AS bill_type,
dbh.bill_type_id,
dbh.tds_amount,
dbh.debit_account_id
FROM public.draft_bill_headers dbh
LEFT JOIN public.vendors v ON v.id = dbh.vendor_id
LEFT JOIN public.bill_statuses bs ON bs.id = dbh.bill_status_id
LEFT JOIN public.payment_statuses ps ON ps.id = dbh.payment_status_id
LEFT JOIN public.tds_statuses ts ON ts.id = dbh.tds_status_id -- ✅ Added
LEFT JOIN public.bill_types bt ON bt.id = dbh.bill_type_id
LEFT JOIN public.users cu ON cu.id = dbh.created_by
LEFT JOIN public.users mu ON mu.id = dbh.modified_by
WHERE dbh.company_id = p_company_id
AND dbh.is_deleted = false
AND (p_bill_type_id IS NULL OR p_bill_type_id = 0 OR dbh.bill_type_id = p_bill_type_id)
AND dbh.bill_date BETWEEN v_start_date AND v_end_date
),
enriched AS (
SELECT
bd.*,
COALESCE(
(
SELECT bw.next_status
FROM bill_workflow bw
WHERE bw.company_id = p_company_id
AND bw.status = bd.bill_status_id
AND bw.is_deleted = false
AND (bw.is_enabled IS DISTINCT FROM FALSE)
ORDER BY bw.approval_level ASC NULLS LAST
LIMIT 1
),
0
) AS next_status_id
FROM bill_data bd
)
SELECT
e.id,
e.bill_voucher,
e.bill_number,
e.vendor_name,
e.vendor_id,
e.due_date::date,
e.bill_date::date,
e.payment_term,
e.total_amount,
e.total_paid_amount,
e.currency_id,
e.bill_status,
e.bill_status_id,
e.payment_status,
e.payment_status_id,
e.tds_status,
e.tds_status_id,
e.created_by,
e.created_on_utc,
e.modified_by,
e.modified_on_utc,
e.created_by_name,
e.modified_by_name,
e.bill_type::character varying,
e.bill_type_id,
e.tds_amount,
CASE
WHEN e.bill_status_id = DRAFT_STATUS THEN true
WHEN e.next_status_id = 0 THEN false
WHEN EXISTS (
SELECT 1
FROM bill_approval_user_account a
WHERE a.account_id = e.debit_account_id
AND a.status_id = e.bill_status_id
AND a.user_id = p_user_id
AND a.is_deleted = false
) THEN true
WHEN EXISTS (
SELECT 1
FROM bill_approval_user_company c
WHERE c.company_id = p_company_id
AND c.status_id = e.bill_status_id
AND c.user_id = p_user_id
AND c.is_deleted = false
) THEN true
ELSE false
END AS has_next_status_approval,
e.next_status_id
FROM enriched e;
END;
$function$
-- Function: get_all_vendors
CREATE OR REPLACE FUNCTION public.get_all_vendors(p_company_id uuid)
RETURNS TABLE(id uuid, name character varying, contact_name text, gst_in text, mobile_number text, email text, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, billing_address jsonb, contact_count integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id,
v.name,
con.first_name || ' ' || con.last_name AS contact_name,
v.gstin AS gst_in,
con.mobile_number,
con.email,
v.created_by,
u_created.first_name || ' ' || u_created.last_name AS created_name,
v.modified_by,
u_modified.first_name || ' ' || u_modified.last_name AS modified_name,
v.created_on_utc,
v.modified_on_utc,
CASE
WHEN ba.id IS NOT NULL
THEN jsonb_build_object(
'AddressLine1', ba.address_line1,
'AddressLine2', ba.address_line2,
'ZipCode', ba.zip_code,
'CountryId', ba.country_id,
'StateId', ba.state_id,
'CityId', ba.city_id
)
ELSE NULL
END AS billing_address,
(SELECT COUNT(*)::integer -- Cast to integer
FROM public.vendor_contacts vc_count
WHERE vc_count.vendor_id = v.id) AS contact_count -- Contact count subquery
FROM
public.vendors v
JOIN
public.vendor_contacts vc ON v.id = vc.vendor_id
JOIN
public.contacts con ON vc.contact_id = con.id
LEFT JOIN
public.users u_created ON v.created_by = u_created.id
LEFT JOIN
public.users u_modified ON v.modified_by = u_modified.id
LEFT JOIN
public.addresses ba ON v.billing_address_id = ba.id
WHERE
v.company_id = p_company_id
AND v.is_deleted = false
AND con.is_primary = true;
END;
$function$
-- Function: get_bill_by_id_json
CREATE OR REPLACE FUNCTION public.get_bill_by_id_json(p_bill_header_id uuid)
RETURNS json
LANGUAGE plpgsql
AS $function$
DECLARE
v_bill_status text;
v_bill_json json;
BEGIN
-- Get bill status name
SELECT bs.name INTO v_bill_status
FROM bill_statuses bs
JOIN bill_headers bh ON bh.bill_status_id = bs.id
WHERE bh.id = p_bill_header_id;
-- Construct the JSON output
SELECT json_build_object(
'id', bh.id,
'billNumber', bh.bill_number,
'billDate', bh.bill_date,
'paymentTerm', bh.payment_term,
'billStatus', v_bill_status,
'billStatusId', bh.bill_status_id,
'vendor', json_build_object(
'id', v.id,
'name', v.name,
'email', c.email,
'mobileNumber', c.mobile_number,
'phoneNumber', c.phone_number,
'gstIn', v.gstin,
'shortName', v.short_name,
'pan', v.pan,
'tan', v.tan
),
'debitAccountId', bh.debit_account_id,
'currencyId', bh.currency_id,
'dueDate', bh.due_date,
'taxableAmount', bh.taxable_amount,
'fees', bh.fees,
'cgstAmount', bh.cgst_amount,
'sgstAmount', bh.sgst_amount,
'igstAmount', bh.igst_amount,
'totalAmount', bh.total_amount,
'discount', bh.discount,
'note', bh.note,
'roundOff', bh.round_off,
'sourceWarehouse', NULL,
'sourceWarehouseId', bh.source_warehouse_id,
'destinationWarehouseId', bh.destination_warehouse_id,
'poNo', bh.po_no,
'poDate', bh.po_date,
'lines', (
SELECT json_agg(json_build_object(
'id', bl.id,
'productId', bl.product_id,
'price', bl.price,
'quantity', bl.quantity,
'fees', bl.fees,
'discount', bl.discount,
'taxableAmount', bl.taxable_amount,
'sgstAmount', bl.sgst_amount,
'cgstAmount', bl.cgst_amount,
'igstAmount', bl.igst_amount,
'totalAmount', bl.total_amount,
'serialNumber', bl.serial_number
))
FROM bill_details bl
WHERE bl.bill_header_id = p_bill_header_id
),
'billTypeId', bh.bill_type_id
) INTO v_bill_json
FROM bill_headers bh
LEFT JOIN vendors v ON v.id = bh.vendor_id
LEFT JOIN vendor_contacts vc ON vc.vendor_id = v.id
LEFT JOIN contacts c ON c.id = vc.contact_id
WHERE bh.id = p_bill_header_id;
-- Return the JSON object
RETURN v_bill_json;
END;
$function$
-- Function: get_new_bill_voucher
CREATE OR REPLACE FUNCTION public.get_new_bill_voucher(p_company_id uuid, p_date date)
RETURNS character varying
LANGUAGE plpgsql
AS $function$
DECLARE
v_finance_year integer;
new_bill_id integer;
p_prefix varchar;
bill_voucher varchar;
BEGIN
-- Determine the start year of the financial year based on p_date
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
v_finance_year := EXTRACT(YEAR FROM p_date);
ELSE
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
END IF;
-- Try to update existing row or insert a new one if not found
WITH x AS (
UPDATE public.bill_header_ids
SET last_bill_id = last_bill_id + 1
WHERE company_id = p_company_id AND fin_year = v_finance_year
RETURNING last_bill_id, bill_prefix
),
insert_x AS (
INSERT INTO public.bill_header_ids (
company_id, fin_year, bill_prefix, last_bill_id, bill_length
)
SELECT p_company_id, v_finance_year, 'BN', 1, 8
WHERE NOT EXISTS (SELECT 1 FROM x)
RETURNING last_bill_id, bill_prefix
)
SELECT
COALESCE((SELECT last_bill_id FROM x LIMIT 1), (SELECT last_bill_id FROM insert_x LIMIT 1)),
COALESCE((SELECT bill_prefix FROM x LIMIT 1), (SELECT bill_prefix FROM insert_x LIMIT 1))
INTO new_bill_id, p_prefix;
-- Construct the bill voucher number
bill_voucher := p_prefix || LPAD(new_bill_id::text, 4, '0');
RETURN bill_voucher;
END;
$function$
-- Function: get_vendor_advances_by_vendor
CREATE OR REPLACE FUNCTION public.get_vendor_advances_by_vendor(p_company_id uuid, p_vendor_id uuid, only_unutilized_advances boolean DEFAULT false)
RETURNS TABLE(id uuid, vendor_id uuid, vendor_name character varying, advance_number text, paid_date timestamp without time zone, amount numeric, utilized_amount numeric, mode_of_payment text, reference text, description text, currency_id integer)
LANGUAGE sql
AS $function$
SELECT
va.id,
va.vendor_id,
v.name, -- Add vendor name
va.advance_number,
va.paid_date,
va.amount,
va.utilized_amount,
va.mode_of_payment,
va.reference,
va.description,
va.currency_id
FROM public.vendor_advances va
LEFT JOIN public.vendors v ON va.vendor_id = v.id -- Join here to get the name
WHERE va.company_id = p_company_id
AND va.vendor_id = p_vendor_id
AND va.is_deleted = false
AND v.is_deleted = false
AND (
-- If only_unutilized_advances = true → filter unsettled
(only_unutilized_advances = true AND va.amount > va.utilized_amount)
-- If false or null → return all
OR (only_unutilized_advances IS DISTINCT FROM true)
);
$function$
-- Function: get_payment_distributions_for_bill
CREATE OR REPLACE FUNCTION public.get_payment_distributions_for_bill(p_bill_header_id uuid)
RETURNS TABLE(payment_detail_id uuid, bill_payment_header_id uuid, payment_number text, bill_header_id uuid, paid_amount numeric, tds_amount numeric, payment_is_deleted boolean, payment_created_by uuid, payment_created_by_name text, payment_created_on timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
bpd.id AS payment_detail_id,
bpd.bill_payment_header_id,
bph.payment_number,
bpd.bill_header_id,
bpd.paid_amount,
bpd.tds_amount,
bpd.is_deleted AS payment_is_deleted,
bpd.created_by AS payment_created_by,
CONCAT(u.first_name, ' ', u.last_name) AS payment_created_by_name,
bpd.created_on_utc AS payment_created_on
FROM
public.bill_payment_details bpd
JOIN public.bill_payment_headers bph ON bph.id = bpd.bill_payment_header_id
LEFT JOIN public.users u ON u.id = bpd.created_by
WHERE
bpd.bill_header_id = p_bill_header_id
AND bpd.is_deleted = false
ORDER BY
bpd.created_on_utc ASC;
END;
$function$
-- Function: get_vendor_details
CREATE OR REPLACE FUNCTION public.get_vendor_details(p_vendor_id uuid)
RETURNS TABLE(id uuid, name character varying, email text, mobile_number text, phone_number text, gstin text, short_name text, pan text, tan text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id,
v.name,
ct.email,
ct.mobile_number,
ct.phone_number,
v.gstin,
v.short_name,
v.pan,
v.tan
FROM vendors v
LEFT JOIN vendor_contacts vc ON vc.vendor_id = v.id
LEFT JOIN contacts ct ON vc.contact_id = ct.id
WHERE v.id = p_vendor_id
ORDER BY ct.is_primary DESC
LIMIT 1;
END;
$function$
-- Function: grant_full_schema_access
CREATE OR REPLACE FUNCTION public.grant_full_schema_access(p_schema text, p_user text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
obj RECORD;
BEGIN
-- Grant on tables
FOR obj IN
SELECT table_name
FROM information_schema.tables
WHERE table_schema = p_schema
AND table_type = 'BASE TABLE'
LOOP
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.%I TO %I;', p_schema, obj.table_name, p_user);
RAISE NOTICE 'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %.% TO %;', p_schema, obj.table_name, p_user;
END LOOP;
-- Grant on sequences (USAGE + SELECT + UPDATE: full coverage)
FOR obj IN
SELECT c.relname AS sequence_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'S'
AND n.nspname = p_schema
LOOP
EXECUTE format('GRANT USAGE, SELECT, UPDATE ON SEQUENCE %I.%I TO %I;', p_schema, obj.sequence_name, p_user);
RAISE NOTICE 'GRANT USAGE, SELECT, UPDATE ON SEQUENCE %.% TO %;', p_schema, obj.sequence_name, p_user;
END LOOP;
-- Grant on all functions (handles all argument types)
FOR obj IN
SELECT
p.proname AS function_name,
pg_get_function_identity_arguments(p.oid) AS args
FROM
pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE
n.nspname = p_schema
AND p.prokind = 'f' -- f = function
LOOP
EXECUTE format(
'GRANT EXECUTE ON FUNCTION %I.%I(%s) TO %I;',
p_schema, obj.function_name, obj.args, p_user
);
RAISE NOTICE 'GRANT EXECUTE ON FUNCTION %.%(%) TO %;', p_schema, obj.function_name, obj.args, p_user;
END LOOP;
-- Grant on all procedures (Postgres 11+)
FOR obj IN
SELECT
p.proname AS procedure_name,
pg_get_function_identity_arguments(p.oid) AS args
FROM
pg_proc p
JOIN pg_namespace n ON p.pronamespace = n.oid
WHERE
n.nspname = p_schema
AND p.prokind = 'p' -- p = procedure
LOOP
EXECUTE format(
'GRANT EXECUTE ON PROCEDURE %I.%I(%s) TO %I;',
p_schema, obj.procedure_name, obj.args, p_user
);
RAISE NOTICE 'GRANT EXECUTE ON PROCEDURE %.%(%) TO %;', p_schema, obj.procedure_name, obj.args, p_user;
END LOOP;
END;
$function$
-- Function: get_vendor_default_accounts
CREATE OR REPLACE FUNCTION public.get_vendor_default_accounts(p_company_id uuid)
RETURNS TABLE(id integer, vendor_id uuid, account_id uuid)
LANGUAGE sql
AS $function$
SELECT
vda.id,
vda.vendor_id,
vda.account_id
FROM vendor_default_accounts vda
WHERE vda.company_id = p_company_id
AND vda.is_deleted = false;
$function$
-- Function: update_bill_next_status_test
CREATE OR REPLACE FUNCTION public.update_bill_next_status_test(p_company_id uuid, p_bill_status_id integer, p_bill_ids uuid[], p_modified_by uuid)
RETURNS TABLE(bill_id uuid, status text, error text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_draft_bill RECORD;
v_next_status integer;
v_account_id uuid;
v_user_level INTEGER;
v_required_levels INTEGER;
v_current_level INTEGER;
v_is_account_level BOOLEAN := false;
APPROVED_STATUS CONSTANT INTEGER := 3;
SENDING_FOR_APPROVAL_STATUS CONSTANT INTEGER := 2;
BEGIN
CREATE TEMP TABLE temp_approval_results (
bill_id uuid,
status text,
error text
) ON COMMIT DROP;
FOR v_draft_bill IN
SELECT ph.id, ph.bill_status_id, ph.debit_account_id, ph.current_approval_level
FROM public.draft_bill_headers ph
WHERE id = ANY(p_bill_ids)
LOOP
BEGIN
RAISE NOTICE 'Processing bill ID: %', v_draft_bill.id;
v_account_id := v_draft_bill.debit_account_id;
v_next_status := p_bill_status_id;
v_current_level := COALESCE(v_draft_bill.current_approval_level, 1);
-- Direct status update for SENDING_FOR_APPROVAL_STATUS
IF p_bill_status_id = SENDING_FOR_APPROVAL_STATUS THEN
WITH updated AS (
UPDATE public.draft_bill_headers
SET bill_status_id = p_bill_status_id,
modified_by = p_modified_by,
modified_on_utc = now(),
current_approval_level = 1
WHERE id = v_draft_bill.id
RETURNING id
)
INSERT INTO temp_approval_results
SELECT
u.id,
'pending',
NULL
FROM updated u;
CONTINUE;
END IF;
-- Check account-level approval
SELECT approval_level INTO v_user_level
FROM public.bill_approval_user_account
WHERE company_id = p_company_id
AND account_id = v_account_id
AND status_id = v_next_status
AND user_id = p_modified_by
AND is_deleted = false
LIMIT 1;
IF FOUND THEN
v_is_account_level := true;
ELSE
-- Check company-level approval
SELECT approval_level INTO v_user_level
FROM public.bill_approval_user_company
WHERE company_id = p_company_id
AND status_id = v_next_status
AND user_id = p_modified_by
AND is_deleted = false
LIMIT 1;
IF NOT FOUND THEN
RAISE NOTICE 'User % is not authorized for any approval level.', p_modified_by;
INSERT INTO temp_approval_results VALUES (
v_draft_bill.id, 'rejected', 'User not authorized for approval'
);
CONTINUE;
END IF;
END IF;
-- Get required approval levels
SELECT approval_level_required INTO v_required_levels
FROM public.bill_account_approval_levels
WHERE company_id = p_company_id
AND account_id = v_account_id
AND status_id = v_next_status
LIMIT 1;
v_required_levels := COALESCE(v_required_levels, 1);
RAISE NOTICE 'User level: %, Required: %, Current: %', v_user_level, v_required_levels, v_current_level;
IF v_user_level <> v_current_level THEN
INSERT INTO temp_approval_results VALUES (
v_draft_bill.id, 'rejected', FORMAT('Approval level mismatch. Expected: %, You have: %', v_current_level, v_user_level)
);
CONTINUE;
END IF;
-- If not final level, just increment approval level
IF v_current_level < v_required_levels THEN
RAISE NOTICE 'Incrementing approval level...';
UPDATE draft_bill_headers
SET current_approval_level = v_current_level + 1,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_draft_bill.id;
INSERT INTO temp_approval_results VALUES (
v_draft_bill.id, 'pending', FORMAT('Moved to level %', v_current_level + 1)
);
ELSE
-- Final approval and status change
RAISE NOTICE 'Final approval and updating status...';
UPDATE draft_bill_headers
SET bill_status_id = v_next_status,
current_approval_level = NULL,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_draft_bill.id;
CALL public.log_bill_approval(
v_draft_bill.id,
v_next_status,
p_modified_by
);
IF v_next_status = APPROVED_STATUS THEN
CALL public.transfer_draft_to_bill(
v_draft_bill.id,
p_modified_by
);
END IF;
INSERT INTO temp_approval_results VALUES (
v_draft_bill.id, 'approved', NULL
);
END IF;
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Error processing bill ID %: %', v_draft_bill.id, SQLERRM;
INSERT INTO temp_approval_results VALUES (
v_draft_bill.id, 'error', SQLERRM
);
END;
END LOOP;
RETURN QUERY SELECT * FROM temp_approval_results;
END;
$function$
-- Function: upsert_vendor_default_account
CREATE OR REPLACE FUNCTION public.upsert_vendor_default_account(p_updates jsonb)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
update_record JSONB;
v_vendor_id UUID;
v_account_id UUID;
v_company_id UUID;
v_user_id UUID;
BEGIN
FOR update_record IN SELECT * FROM jsonb_array_elements(p_updates)
LOOP
v_vendor_id := update_record->>'VendorId';
v_account_id := update_record->>'AccountId';
v_company_id := update_record->>'CompanyId';
v_user_id := update_record->>'UserId';
INSERT INTO vendor_default_accounts (
vendor_id, account_id, company_id, created_by, created_on_utc, is_deleted
)
VALUES (
v_vendor_id, v_account_id, v_company_id, v_user_id, now(), false
)
ON CONFLICT (vendor_id, company_id) DO UPDATE
SET
account_id = EXCLUDED.account_id,
modified_by = EXCLUDED.created_by,
modified_on_utc = now(),
is_deleted = false;
END LOOP;
END;
$function$
-- Function: bulk_delete_vendors
CREATE OR REPLACE FUNCTION public.bulk_delete_vendors(p_vendor_ids uuid[], p_modified_by uuid, p_deleted_on_utc timestamp without time zone)
RETURNS TABLE(id integer, vendor_id uuid, status text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_id UUID;
v_blocked_count INT;
row_index INT := 1;
BEGIN
FOREACH v_id IN ARRAY p_vendor_ids LOOP
-- 1. Skip if vendor already deleted
IF NOT EXISTS (
SELECT 1 FROM public.vendors v WHERE v.id = v_id AND v.is_deleted = false
) THEN
id := row_index;
bulk_delete_vendors.vendor_id := v_id;
bulk_delete_vendors.status := 'Skipped - Vendor Not Found or Already Deleted';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 2. Check if any financial bills exist
SELECT COUNT(*) INTO v_blocked_count
FROM (
SELECT 1 FROM public.bill_headers bh
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id IN (3, 4, 5)
UNION ALL
SELECT 1 FROM public.draft_bill_headers dbh
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id IN (3, 4, 5)
) AS financial_bills;
IF v_blocked_count > 0 THEN
id := row_index;
bulk_delete_vendors.vendor_id := v_id;
bulk_delete_vendors.status := 'Blocked - Financial Bills Exist (Approved/Partially Paid/Paid)';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 3. Soft delete eligible draft_bill_details
UPDATE public.draft_bill_details dbd
SET is_deleted = true,
deleted_on_utc = p_deleted_on_utc
WHERE dbd.bill_header_id IN (
SELECT dbh.id FROM public.draft_bill_headers dbh
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id NOT IN (3, 4, 5)
);
-- 4. Soft delete eligible draft_bill_headers
UPDATE public.draft_bill_headers dbh
SET is_deleted = true,
modified_by = p_modified_by,
deleted_on_utc = p_deleted_on_utc
WHERE dbh.vendor_id = v_id AND dbh.is_deleted = false AND dbh.bill_status_id NOT IN (3, 4, 5);
-- 5. Soft delete eligible bill_details
UPDATE public.bill_details bd
SET is_deleted = true,
deleted_on_utc = p_deleted_on_utc
WHERE bd.bill_header_id IN (
SELECT bh.id FROM public.bill_headers bh
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id NOT IN (3, 4, 5)
);
-- 6. Soft delete eligible bill_headers
UPDATE public.bill_headers bh
SET is_deleted = true,
modified_by = p_modified_by,
deleted_on_utc = p_deleted_on_utc
WHERE bh.vendor_id = v_id AND bh.is_deleted = false AND bh.bill_status_id NOT IN (3, 4, 5);
-- 7. Soft delete vendor
UPDATE public.vendors v
SET is_deleted = true,
modified_by = p_modified_by,
deleted_on_utc = p_deleted_on_utc
WHERE v.id = v_id;
-- 8. Return success
id := row_index;
bulk_delete_vendors.vendor_id := v_id;
bulk_delete_vendors.status := 'Deleted';
row_index := row_index + 1;
RETURN NEXT;
END LOOP;
END;
$function$
-- Function: get_all_bill_headers
CREATE OR REPLACE FUNCTION public.get_all_bill_headers(p_company_id uuid)
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, vendor_id uuid, vendor_name text, due_date timestamp with time zone, bill_date timestamp with time zone, payment_term integer, total_amount numeric, setteled_amount numeric, discount numeric, note text, currency_id integer, bill_status_id integer, bill_status text, bill_type_id integer, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp with time zone, modified_on_utc timestamp with time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
-- Query BillHeaders
SELECT
bh.id, bh.bill_number, bh.bill_voucher, bh.vendor_id,
v.name::text AS vendor_name,
bh.due_date::timestamptz, bh.bill_date::timestamptz, bh.payment_term,
bh.total_amount, bh.setteled_amount, bh.discount, bh.note,
bh.currency_id, bh.bill_status_id, bs.name::text AS bill_status, bh.bill_type_id,
bh.created_by,
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name,
bh.modified_by,
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name,
bh.created_on_utc::timestamptz,
bh.modified_on_utc::timestamptz
FROM
bill_headers bh
JOIN vendors v ON v.id = bh.vendor_id
JOIN bill_statuses bs ON bs.id = bh.bill_status_id
LEFT JOIN users u_created ON u_created.id = bh.created_by
LEFT JOIN users u_modified ON u_modified.id = bh.modified_by
WHERE
bh.company_id = p_company_id
AND bh.is_deleted = false
UNION ALL
-- Query DraftBillHeaders
SELECT
dbh.id, dbh.bill_number, dbh.bill_voucher, dbh.vendor_id,
v.name::text AS vendor_name,
dbh.due_date::timestamptz, dbh.bill_date::timestamptz, dbh.payment_term,
dbh.total_amount, dbh.setteled_amount, dbh.discount, dbh.note,
dbh.currency_id, dbh.bill_status_id, bs.name::text AS bill_status, dbh.bill_type_id,
dbh.created_by,
COALESCE(u_created.first_name || ' ' || u_created.last_name, 'N/A') AS created_by_name,
dbh.modified_by,
COALESCE(u_modified.first_name || ' ' || u_modified.last_name, 'N/A') AS modified_by_name,
dbh.created_on_utc::timestamptz,
dbh.modified_on_utc::timestamptz
FROM
draft_bill_headers dbh
JOIN vendors v ON v.id = dbh.vendor_id
JOIN bill_statuses bs ON bs.id = dbh.bill_status_id
LEFT JOIN users u_created ON u_created.id = dbh.created_by
LEFT JOIN users u_modified ON u_modified.id = dbh.modified_by
WHERE
dbh.company_id = p_company_id
AND dbh.is_deleted = false;
END;
$function$
-- Function: get_new_payment_number
CREATE OR REPLACE FUNCTION public.get_new_payment_number(p_company_id uuid, p_date date)
RETURNS character varying
LANGUAGE plpgsql
AS $function$
DECLARE
v_finance_year integer;
new_payment_id integer;
p_prefix varchar;
payment_number varchar;
BEGIN
-- Determine the start year of the financial year based on p_date
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
v_finance_year := EXTRACT(YEAR FROM p_date);
ELSE
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
END IF;
-- Attempt to update existing ID row or insert if not found
WITH x AS (
UPDATE public.bill_payment_header_ids
SET last_payment_id = last_payment_id + 1
WHERE company_id = p_company_id AND fin_year = v_finance_year
RETURNING last_payment_id, payment_prefix
),
insert_x AS (
INSERT INTO public.bill_payment_header_ids (
company_id, fin_year, payment_prefix, last_payment_id, payment_length
)
SELECT p_company_id, v_finance_year, 'BPV', 1, 8
WHERE NOT EXISTS (SELECT 1 FROM x)
RETURNING last_payment_id, payment_prefix
)
SELECT
COALESCE((SELECT last_payment_id FROM x LIMIT 1), (SELECT last_payment_id FROM insert_x LIMIT 1)),
COALESCE((SELECT payment_prefix FROM x LIMIT 1), (SELECT payment_prefix FROM insert_x LIMIT 1))
INTO new_payment_id, p_prefix;
-- Construct the final payment number (e.g., BPV0001)
payment_number := p_prefix || LPAD(new_payment_id::text, 4, '0');
RETURN payment_number;
END;
$function$
-- Function: get_bill_workflow_config
CREATE OR REPLACE FUNCTION public.get_bill_workflow_config(p_company_id uuid)
RETURNS TABLE(id integer, name text, is_enabled boolean, next_statuses integer[], previous_statuses integer[])
LANGUAGE sql
AS $function$
SELECT
s.id,
s.name,
COALESCE(cfg.is_enabled, false) AS is_enabled,
-- Forward links (next_statuses)
COALESCE(ARRAY(
SELECT w.next_status
FROM bill_workflow w
WHERE w.company_id = p_company_id
AND w.status = s.id
AND w.is_deleted = false
ORDER BY w.next_status
), ARRAY[]::INT[]) AS next_statuses,
-- Backward links (previous_statuses)
COALESCE(ARRAY(
SELECT DISTINCT w.previous_status
FROM bill_workflow w
WHERE w.company_id = p_company_id
AND w.status = s.id
AND w.is_deleted = false
AND w.previous_status IS NOT NULL
ORDER BY w.previous_status
), ARRAY[]::INT[]) AS previous_statuses
FROM bill_statuses s
LEFT JOIN bill_status_company_configs cfg
ON cfg.status_id = s.id AND cfg.company_id = p_company_id
WHERE s.is_deleted = false;
$function$
-- Function: get_warehouse_details
CREATE OR REPLACE FUNCTION public.get_warehouse_details(p_warehouse_id uuid)
RETURNS TABLE(id uuid, vendor_id uuid, name text, address_id uuid, country_name text, country_id uuid, state_name text, state_id uuid, city_name text, city_id uuid, address_line1 text, address_line2 text, zip_code text, gstin text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
wh.id,
wh.vendor_id, -- Assuming vendor_id is relevant for bills
wh.name,
wh.address_id,
(SELECT cou.name FROM countries cou WHERE cou.id = a.country_id) AS country_name,
a.country_id,
(SELECT st.name FROM states st WHERE st.id = a.state_id) AS state_name,
a.state_id,
(SELECT ci.name FROM cities ci WHERE ci.id = a.city_id) AS city_name,
a.city_id,
a.address_line1,
a.address_line2,
a.zip_code,
wh.gstin
FROM warehouses wh
JOIN addresses a ON wh.address_id = a.id
WHERE wh.id = p_warehouse_id;
IF NOT FOUND THEN
RETURN; -- Return an empty result set if no warehouse is found
END IF;
END;
$function$
-- Function: get_bill_status
CREATE OR REPLACE FUNCTION public.get_bill_status(p_bill_id uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
bill_status_id INTEGER;
BEGIN
-- First, try to find the status in the draft_bill_headers table
SELECT dbh.bill_status_id
INTO bill_status_id
FROM public.draft_bill_headers dbh
WHERE dbh.id = p_bill_id;
-- If not found in draft_bill_headers, check bill_headers
IF bill_status_id IS NULL THEN
SELECT bh.bill_status_id
INTO bill_status_id
FROM public.bill_headers bh
WHERE bh.id = p_bill_id;
END IF;
RETURN bill_status_id;
END;
$function$
-- Function: insert_multiple_bills_by_excel
CREATE OR REPLACE FUNCTION public.insert_multiple_bills_by_excel(p_bills jsonb)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
bill RECORD;
v_created_by UUID;
v_status TEXT := 'success';
BEGIN
-- Validate the input JSONB structure
BEGIN
PERFORM *
FROM jsonb_to_recordset(p_bills) AS (
bill_id UUID,
company_id UUID,
vendor_id UUID,
debit_account_id UUID,
credit_account_id UUID,
discount NUMERIC,
bill_number TEXT,
currency_id INTEGER,
bill_date TIMESTAMP,
payment_term INTEGER,
bill_status_id INTEGER,
due_date TIMESTAMP,
total_amount NUMERIC,
note TEXT,
taxable_amount NUMERIC,
fees NUMERIC,
sgst_amount NUMERIC,
cgst_amount NUMERIC,
igst_amount NUMERIC,
tds_amount NUMERIC,
round_off NUMERIC,
round_off_tds NUMERIC,
po_no TEXT,
po_date TIMESTAMP,
source_warehouse_id UUID,
destination_warehouse_id UUID,
bill_type_id INTEGER,
lines JSONB,
created_by UUID
);
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Invalid JSON input: %', SQLERRM;
RETURN 'error';
END;
-- Get 'System' user as created_by fallback
SELECT id INTO v_created_by
FROM public.users
WHERE first_name = 'System'
LIMIT 1;
-- Loop through each bill
FOR bill IN
SELECT * FROM jsonb_to_recordset(p_bills) AS (
bill_id UUID,
company_id UUID,
vendor_id UUID,
debit_account_id UUID,
credit_account_id UUID,
discount NUMERIC,
bill_number TEXT,
currency_id INTEGER,
bill_date TIMESTAMP,
payment_term INTEGER,
bill_status_id INTEGER,
due_date TIMESTAMP,
total_amount NUMERIC,
note TEXT,
taxable_amount NUMERIC,
fees NUMERIC,
sgst_amount NUMERIC,
cgst_amount NUMERIC,
igst_amount NUMERIC,
tds_amount NUMERIC,
round_off NUMERIC,
round_off_tds NUMERIC,
po_no TEXT,
po_date TIMESTAMP,
source_warehouse_id UUID,
destination_warehouse_id UUID,
bill_type_id INTEGER,
lines JSONB,
created_by UUID
)
LOOP
BEGIN
-- Duplicate check
IF EXISTS (
SELECT 1
FROM public.bill_headers
WHERE bill_number = bill.bill_number
AND company_id = bill.company_id
) THEN
RAISE NOTICE 'Duplicate: %, %', bill.bill_number, bill.company_id;
v_status := 'duplicate';
CONTINUE;
END IF;
-- Call the save routine
CALL public.save_bill_and_details(
bill.bill_id,
bill.company_id,
bill.vendor_id,
bill.discount,
bill.bill_date,
bill.payment_term,
bill.bill_status_id,
bill.due_date,
bill.total_amount,
bill.taxable_amount,
bill.fees,
bill.sgst_amount,
bill.cgst_amount,
bill.igst_amount,
bill.note,
bill.round_off,
bill.round_off_tds,
bill.debit_account_id,
bill.credit_account_id,
v_created_by,
bill.currency_id,
bill.po_date,
bill.po_no,
bill.destination_warehouse_id,
bill.source_warehouse_id,
bill.bill_type_id,
bill.bill_number,
bill.tds_amount,
bill.lines
);
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'Error bill %: %', bill.bill_id, SQLERRM;
v_status := 'error';
CONTINUE;
END;
END LOOP;
RETURN v_status;
END;
$function$
-- Function: pg_get_tabledef
CREATE OR REPLACE FUNCTION public.pg_get_tabledef(p_table_name text)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
col RECORD;
col_defs TEXT := '';
pk_cols TEXT := '';
result TEXT;
BEGIN
FOR col IN
SELECT
column_name,
data_type,
character_maximum_length,
numeric_precision,
numeric_scale,
is_nullable,
column_default
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = p_table_name
ORDER BY ordinal_position
LOOP
col_defs := col_defs ||
format('"%s" %s%s%s%s, ',
col.column_name,
CASE
WHEN col.data_type = 'character varying' THEN format('varchar(%s)', col.character_maximum_length)
WHEN col.data_type = 'numeric' THEN format('numeric(%s,%s)', col.numeric_precision, col.numeric_scale)
ELSE col.data_type
END,
CASE WHEN col.column_default IS NOT NULL THEN ' DEFAULT ' || col.column_default ELSE '' END,
CASE WHEN col.is_nullable = 'NO' THEN ' NOT NULL' ELSE '' END,
''
);
END LOOP;
-- Get primary key columns
SELECT string_agg(format('"%s"', kcu.column_name), ', ')
INTO pk_cols
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
ON tc.constraint_name = kcu.constraint_name
WHERE tc.table_schema = 'public'
AND tc.table_name = p_table_name
AND tc.constraint_type = 'PRIMARY KEY';
IF pk_cols IS NOT NULL THEN
col_defs := col_defs || format('PRIMARY KEY (%s), ', pk_cols);
END IF;
col_defs := left(col_defs, length(col_defs) - 2);
result := format('CREATE TABLE "%s" (%s);', p_table_name, col_defs);
RETURN result;
END;
$function$
-- Function: bulk_delete_bills_with_flag
CREATE OR REPLACE FUNCTION public.bulk_delete_bills_with_flag(p_bill_ids uuid[], p_modified_by uuid, p_deleted_on_utc timestamp without time zone, p_is_all_possible_delete boolean)
RETURNS TABLE(id integer, bill_id uuid, status text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_id UUID;
v_status_id INT;
row_index INT := 1;
BEGIN
FOREACH v_id IN ARRAY p_bill_ids LOOP
-- 1. Check if bill exists and is not deleted
SELECT bh.bill_status_id INTO v_status_id
FROM public.bill_headers bh
WHERE bh.id = v_id AND bh.is_deleted = false;
-- If not found, skip this bill
IF NOT FOUND THEN
id := row_index;
bulk_delete_bills_with_flag.bill_id := v_id;
bulk_delete_bills_with_flag.status := 'Skipped - Bill Not Found or Already Deleted';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 2. If `isAllPossibleDelete` is true, delete if allowed
IF p_is_all_possible_delete THEN
-- Check if the bill status is "Blocked"
IF v_status_id IN (3, 4) THEN
id := row_index;
bulk_delete_bills_with_flag.bill_id := v_id;
bulk_delete_bills_with_flag.status := 'Blocked - Bill Status Prevents Deletion (Approved/Partially Paid)';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 2.1 Soft delete from bill_headers
UPDATE public.bill_headers bh
SET is_deleted = true,
modified_by = p_modified_by,
deleted_on_utc = p_deleted_on_utc
WHERE bh.id = v_id;
-- 2.2 Soft delete from bill_details
UPDATE public.bill_details bd
SET is_deleted = true,
deleted_on_utc = p_deleted_on_utc
WHERE bd.bill_header_id = v_id;
-- 2.3 Soft delete from draft_bill_headers
UPDATE public.draft_bill_headers dbh
SET is_deleted = true,
modified_by = p_modified_by,
deleted_on_utc = p_deleted_on_utc
WHERE dbh.id = v_id;
-- 2.4 Soft delete from draft_bill_details
UPDATE public.draft_bill_details dbd
SET is_deleted = true,
deleted_on_utc = p_deleted_on_utc
WHERE dbd.bill_header_id = v_id;
-- 2.5 Return success
id := row_index;
bulk_delete_bills_with_flag.bill_id := v_id;
bulk_delete_bills_with_flag.status := 'Deleted';
row_index := row_index + 1;
RETURN NEXT;
ELSE
-- 3. If `isAllPossibleDelete` is false, only check eligibility
IF v_status_id IN (3, 4) THEN
id := row_index;
bulk_delete_bills_with_flag.bill_id := v_id;
bulk_delete_bills_with_flag.status := 'Blocked - Bill Status Prevents Deletion (Approved/Partially Paid)';
row_index := row_index + 1;
RETURN NEXT;
CONTINUE;
END IF;
-- 3.1 Eligible
id := row_index;
bulk_delete_bills_with_flag.bill_id := v_id;
bulk_delete_bills_with_flag.status := 'Eligible for Deletion';
row_index := row_index + 1;
RETURN NEXT;
END IF;
END LOOP;
END;
$function$
-- Function: get_bill_by_id
CREATE OR REPLACE FUNCTION public.get_bill_by_id(p_bill_header_id uuid)
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, debit_account_id uuid, credit_account_id uuid, bill_date timestamp with time zone, payment_term integer, vendor_id uuid, vendor_name text, vendor_gstin text, vendor_short_name text, vendor_pan text, vendor_tan text, vendor_email text, vendor_phone_number text, vendor_mobile_number text, vendor_address_line1 text, vendor_address_line2 text, vendor_city_name text, vendor_state_name text, vendor_country_name text, vendor_zip_code text, due_date timestamp with time zone, total_amount numeric, taxable_amount numeric, fees numeric, cgst_amount numeric, sgst_amount numeric, igst_amount numeric, currency_id integer, bill_status_id integer, bill_status text, discount numeric, note text, round_off numeric, round_off_tds numeric, po_no text, po_date timestamp without time zone, destination_warehouse_id uuid, source_warehouse_id uuid, source_vendor_id uuid, source_warehouse_name text, source_address_id uuid, source_country_name text, source_country_id uuid, source_state_name text, source_state_id uuid, source_city_name text, source_city_id uuid, source_address_line1 text, source_address_line2 text, source_zip_code text, source_gstin text, bill_lines jsonb, bill_type_id integer, current_approval_level integer, tds_amount numeric, created_on timestamp with time zone, modified_on timestamp with time zone, created_by uuid, modified_by uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
v_bill_status_id INTEGER;
BEGIN
v_bill_status_id := public.get_bill_status(p_bill_header_id);
IF v_bill_status_id >= 3 THEN
RETURN QUERY
SELECT
bh.id,
bh.bill_number::text,
bh.bill_voucher::text,
bh.debit_account_id,
bh.credit_account_id,
bh.bill_date::TIMESTAMP WITH TIME ZONE,
bh.payment_term,
bh.vendor_id,
v.name::text AS vendor_name,
v.gstin::text AS vendor_gstin,
v.short_name::text AS vendor_short_name,
v.pan::text AS vendor_pan,
v.tan::text AS vendor_tan,
co.email::text AS vendor_email,
co.phone_number::text AS vendor_phone_number,
co.mobile_number::text AS vendor_mobile_number,
addr.address_line1::text AS vendor_address_line1,
addr.address_line2::text AS vendor_address_line2,
cit.name::text AS vendor_city_name,
st.name::text AS vendor_state_name,
ctry.name::text AS vendor_country_name,
addr.zip_code::text AS vendor_zip_code,
bh.due_date::TIMESTAMP WITH TIME ZONE,
bh.total_amount,
bh.taxable_amount,
bh.fees,
bh.cgst_amount,
bh.sgst_amount,
bh.igst_amount,
bh.currency_id,
bh.bill_status_id,
blst.name::text,
bh.discount,
bh.note::text,
bh.round_off,
bh.round_off_tds,
bh.po_no::text,
bh.po_date::TIMESTAMP WITHOUT TIME ZONE,
bh.destination_warehouse_id,
wh.id AS source_warehouse_id,
wh.vendor_id AS source_vendor_id,
wh.name::text AS source_warehouse_name,
wh.address_id AS source_address_id,
wh.country_name::text AS source_country_name,
wh.country_id AS source_country_id,
wh.state_name::text AS source_state_name,
wh.state_id AS source_state_id,
wh.city_name::text AS source_city_name,
wh.city_id AS source_city_id,
wh.address_line1::text AS source_address_line1,
wh.address_line2::text AS source_address_line2,
wh.zip_code::text AS source_zip_code,
wh.gstin::text AS source_gstin,
jsonb_agg(jsonb_build_object(
'bill_line_id', bl.id,
'product_id', bl.product_id,
'price', bl.price,
'quantity', bl.quantity,
'fees', bl.fees,
'discount', bl.discount,
'taxable_amount', bl.taxable_amount,
'sgst_amount', bl.sgst_amount,
'cgst_amount', bl.cgst_amount,
'igst_amount', bl.igst_amount,
'total_amount', bl.total_amount,
'serial_number', bl.serial_number
) ORDER BY bl.serial_number) AS bill_lines,
bh.bill_type_id,
bh.current_approval_level,
bh.tds_amount,
bh.created_on_utc::TIMESTAMP WITH TIME ZONE,
bh.modified_on_utc::TIMESTAMP WITH TIME ZONE,
bh.created_by,
bh.modified_by
FROM bill_headers bh
LEFT JOIN public.vendors v ON bh.vendor_id = v.id
LEFT JOIN public.vendor_contacts vc ON v.id = vc.vendor_id AND vc.is_deleted = false
LEFT JOIN public.contacts co ON vc.contact_id = co.id AND co.is_deleted = false AND co.is_primary = true
LEFT JOIN public.addresses addr ON v.billing_address_id = addr.id
LEFT JOIN public.cities cit ON addr.city_id = cit.id
LEFT JOIN public.states st ON addr.state_id = st.id
LEFT JOIN public.countries ctry ON addr.country_id = ctry.id
LEFT JOIN public.get_warehouse_details(bh.source_warehouse_id) wh ON TRUE
LEFT JOIN public.get_bill_lines(bh.id, bh.bill_status_id) bl ON TRUE
JOIN public.bill_statuses blst ON bh.bill_status_id = blst.id
WHERE bh.id = p_bill_header_id
GROUP BY
bh.id,
bh.bill_number,
bh.bill_voucher,
bh.bill_date,
bh.payment_term,
bh.vendor_id,
v.name,
v.gstin,
v.short_name,
v.pan,
v.tan,
co.email,
co.phone_number,
co.mobile_number,
addr.address_line1,
addr.address_line2,
cit.name,
st.name,
ctry.name,
addr.zip_code,
bh.due_date,
bh.total_amount,
bh.taxable_amount,
bh.fees,
bh.cgst_amount,
bh.sgst_amount,
bh.igst_amount,
bh.currency_id,
bh.bill_status_id,
blst.name,
bh.discount,
bh.note,
bh.round_off,
bh.round_off_tds,
bh.po_no,
bh.po_date,
bh.destination_warehouse_id,
bh.source_warehouse_id,
wh.id,
wh.vendor_id,
wh.name,
wh.address_id,
wh.country_name,
wh.country_id,
wh.state_name,
wh.state_id,
wh.city_name,
wh.city_id,
wh.address_line1,
wh.address_line2,
wh.zip_code,
wh.gstin,
bh.bill_type_id,
bh.current_approval_level,
bh.tds_amount,
bh.created_on_utc,
bh.modified_on_utc,
bh.created_by,
bh.modified_by;
ELSE
-- You can add the similar query for draft bill_headers here
RETURN QUERY
SELECT
dbh.id,
dbh.bill_number::text,
dbh.bill_voucher::text,
dbh.debit_account_id,
dbh.credit_account_id,
dbh.bill_date::TIMESTAMP WITH TIME ZONE,
dbh.payment_term,
dbh.vendor_id,
v.name::text AS vendor_name,
v.gstin::text AS vendor_gstin,
v.short_name::text AS vendor_short_name,
v.pan::text AS vendor_pan,
v.tan::text AS vendor_tan,
co.email::text AS vendor_email,
co.phone_number::text AS vendor_phone_number,
co.mobile_number::text AS vendor_mobile_number,
addr.address_line1::text,
addr.address_line2::text,
cit.name::text AS vendor_city_name,
st.name::text AS vendor_state_name,
ctry.name::text AS vendor_country_name,
addr.zip_code::text,
dbh.due_date::TIMESTAMP WITH TIME ZONE,
dbh.total_amount,
dbh.taxable_amount,
dbh.fees,
dbh.cgst_amount,
dbh.sgst_amount,
dbh.igst_amount,
dbh.currency_id,
dbh.bill_status_id,
dblst.name::text,
dbh.discount,
dbh.note::text,
dbh.round_off,
dbh.round_off_tds,
dbh.po_no::text,
dbh.po_date::TIMESTAMP WITHOUT TIME ZONE,
dbh.destination_warehouse_id,
wh.id AS source_warehouse_id,
wh.vendor_id AS source_vendor_id,
wh.name::text AS source_warehouse_name,
wh.address_id AS source_address_id,
wh.country_name::text AS source_country_name,
wh.country_id AS source_country_id,
wh.state_name::text AS source_state_name,
wh.state_id AS source_state_id,
wh.city_name::text AS source_city_name,
wh.city_id AS source_city_id,
wh.address_line1::text AS source_address_line1,
wh.address_line2::text AS source_address_line2,
wh.zip_code::text AS source_zip_code,
wh.gstin::text AS source_gstin,
jsonb_agg(jsonb_build_object(
'bill_line_id', dbl.id,
'product_id', dbl.product_id,
'price', dbl.price,
'quantity', dbl.quantity,
'fees', dbl.fees,
'discount', dbl.discount,
'taxable_amount', dbl.taxable_amount,
'sgst_amount', dbl.sgst_amount,
'cgst_amount', dbl.cgst_amount,
'igst_amount', dbl.igst_amount,
'total_amount', dbl.total_amount,
'serial_number', dbl.serial_number
) ORDER BY dbl.serial_number) AS bill_lines,
dbh.bill_type_id,
dbh.current_approval_level,
dbh.tds_amount,
dbh.created_on_utc::TIMESTAMP WITH TIME ZONE,
dbh.modified_on_utc::TIMESTAMP WITH TIME ZONE,
dbh.created_by,
dbh.modified_by
FROM draft_bill_headers dbh
LEFT JOIN public.vendors v ON dbh.vendor_id = v.id
LEFT JOIN public.vendor_contacts vc ON v.id = vc.vendor_id AND vc.is_deleted = false
LEFT JOIN public.contacts co ON vc.contact_id = co.id AND co.is_deleted = false AND co.is_primary = true
LEFT JOIN public.addresses addr ON v.billing_address_id = addr.id
LEFT JOIN public.cities cit ON addr.city_id = cit.id
LEFT JOIN public.states st ON addr.state_id = st.id
LEFT JOIN public.countries ctry ON addr.country_id = ctry.id
LEFT JOIN public.get_warehouse_details(dbh.source_warehouse_id) wh ON TRUE
LEFT JOIN public.get_bill_lines(dbh.id, dbh.bill_status_id) dbl ON TRUE
JOIN public.bill_statuses dblst ON dbh.bill_status_id = dblst.id
WHERE dbh.id = p_bill_header_id
GROUP BY
dbh.id,
dbh.bill_number,
dbh.bill_voucher,
dbh.bill_date,
dbh.payment_term,
dbh.vendor_id,
v.name,
v.gstin,
v.short_name,
v.pan,
v.tan,
co.email,
co.phone_number,
co.mobile_number,
addr.address_line1,
addr.address_line2,
cit.name,
st.name,
ctry.name,
addr.zip_code,
dbh.due_date,
dbh.total_amount,
dbh.taxable_amount,
dbh.fees,
dbh.cgst_amount,
dbh.sgst_amount,
dbh.igst_amount,
dbh.currency_id,
dbh.bill_status_id,
dblst.name,
dbh.discount,
dbh.note,
dbh.round_off,
dbh.round_off_tds,
dbh.destination_warehouse_id,
dbh.po_no,
dbh.po_date,
dbh.source_warehouse_id,
wh.id,
wh.vendor_id,
wh.name,
wh.address_id,
wh.country_name,
wh.country_id,
wh.state_name,
wh.state_id,
wh.city_name,
wh.city_id,
wh.address_line1,
wh.address_line2,
wh.zip_code,
wh.gstin,
dbh.bill_type_id,
dbh.current_approval_level,
dbh.tds_amount,
dbh.created_on_utc,
dbh.modified_on_utc,
dbh.created_by,
dbh.modified_by;
END IF;
END;
$function$
-- Function: update_bill_next_status_main
CREATE OR REPLACE FUNCTION public.update_bill_next_status_main(p_company_id uuid, p_bill_ids uuid[], p_modified_by uuid)
RETURNS TABLE(bill_id uuid, status_name text, error_msg text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_error_message text;
v_bill_ids_draft uuid[];
v_bill_ids_pending uuid[];
v_bill_ids_other uuid[];
v_next_temp_id int;
BEGIN
RAISE NOTICE 'START update_bill_next_status_main: company_id=%, modified_by=%', p_company_id, p_modified_by;
RAISE NOTICE 'Incoming Bill IDs: %', p_bill_ids;
-- Clean up temp_bill_next_status for these bill IDs to avoid duplicate entries
DELETE FROM temp_bill_next_status tbnv
WHERE tbnv.bill_id = ANY(p_bill_ids);
-- Classify bills by current status
SELECT array_agg(id) INTO v_bill_ids_draft
FROM public.draft_bill_headers dbh
WHERE dbh.id = ANY(p_bill_ids) AND dbh.bill_status_id = 1;
SELECT array_agg(id) INTO v_bill_ids_pending
FROM public.draft_bill_headers dbh
WHERE dbh.id = ANY(p_bill_ids) AND dbh.bill_status_id = 2;
SELECT array_agg(id) INTO v_bill_ids_other
FROM public.bill_headers bh
WHERE bh.id = ANY(p_bill_ids) AND bh.bill_status_id >= 3;
RAISE NOTICE 'Draft bills: %', COALESCE(v_bill_ids_draft, '{}');
RAISE NOTICE 'Pending Approval bills: %', COALESCE(v_bill_ids_pending, '{}');
RAISE NOTICE 'Other (Approved or higher) bills: %', COALESCE(v_bill_ids_other, '{}');
BEGIN
-- Directly update draft bills to Pending Approval
IF array_length(v_bill_ids_draft, 1) > 0 THEN
RAISE NOTICE 'Directly updating Draft bills to Pending Approval';
UPDATE public.draft_bill_headers
SET bill_status_id = 2,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = ANY(v_bill_ids_draft);
-- Insert approval logs
INSERT INTO public.bill_approval_logs(
bill_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
SELECT
dbh.id, 2, p_modified_by, now(),
'Direct update from Draft to Pending Approval',
now(), p_modified_by, 0
FROM public.draft_bill_headers dbh
WHERE dbh.id = ANY(v_bill_ids_draft);
-- Generate next id for temp_bill_next_status
SELECT COALESCE(MAX(id), 0) INTO v_next_temp_id FROM temp_bill_next_status;
-- Insert into temp_bill_next_status
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
SELECT
row_number() OVER () + v_next_temp_id AS id,
dbh.id,
'Pending Approval',
NULL
FROM public.draft_bill_headers dbh
WHERE dbh.id = ANY(v_bill_ids_draft);
END IF;
-- Call update_draft_bill_next_status if Pending Approval bills found and status_id = 2
IF array_length(v_bill_ids_pending, 1) > 0 THEN
RAISE NOTICE 'Calling update_draft_bill_next_status for Pending Approval bills';
CALL public.update_draft_bill_next_status(p_company_id, v_bill_ids_pending, p_modified_by);
END IF;
-- Call update_bill_next_status_for_bill_header for Approved or higher bills
IF array_length(v_bill_ids_other, 1) > 0 THEN
RAISE NOTICE 'Calling update_bill_next_status_for_bill_header for Approved or higher bills';
CALL public.update_bill_next_status_for_bill_header(p_company_id, v_bill_ids_other, p_modified_by);
END IF;
EXCEPTION WHEN OTHERS THEN
v_error_message := SQLERRM;
RAISE NOTICE 'Exception in child procedures: %', v_error_message;
END;
-- Return rows from temp_bill_next_status
RAISE NOTICE 'Preparing to return rows from temp_bill_next_status';
RETURN QUERY
SELECT tbn.bill_id, tbn.status AS status_name, tbn.error AS error_msg
FROM temp_bill_next_status tbn
WHERE tbn.bill_id = ANY(p_bill_ids);
RAISE NOTICE 'END update_bill_next_status_main';
END;
$function$
-- Function: list_scheduled_bill_ids
CREATE OR REPLACE FUNCTION public.list_scheduled_bill_ids(p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
RETURNS TABLE(bill_header_id uuid, draft_bill_header_id uuid, company_id uuid, organization_id uuid)
LANGUAGE sql
AS $function$
WITH base AS (
SELECT
s.id AS schedule_id,
s.bill_header_id AS bill_header_id,
s.draft_bill_header_id AS draft_bill_header_id,
s.company_id AS company_id,
c.organization_id AS organization_id,
d.frequency_cycle,
d.day_of_week,
d.day_of_month,
d.month_of_year,
s.starts_from,
s.end_date
FROM public.recurring_bill_schedules s
JOIN public.recurrence_bill_schedule_details d
ON d.schedule_id = s.id
AND d.is_deleted = false
JOIN public.companies c
ON c.id = s.company_id
WHERE s.is_deleted = false
AND s.schedule_status = 'active'
AND s.starts_from <= (p_schedule_date::date)
AND (s.end_date IS NULL OR s.end_date >= (p_schedule_date::date))
),
aligned AS (
SELECT
b.schedule_id,
b.bill_header_id,
b.draft_bill_header_id,
b.company_id,
b.organization_id
FROM base b
WHERE CASE lower(b.frequency_cycle)
WHEN 'daily' THEN TRUE
WHEN 'weekly' THEN b.day_of_week IS NOT NULL
AND b.day_of_week = EXTRACT(ISODOW FROM p_schedule_date)::int
WHEN 'monthly' THEN b.day_of_month IS NOT NULL
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
WHEN 'yearly' THEN b.month_of_year IS NOT NULL AND b.day_of_month IS NOT NULL
AND b.month_of_year = EXTRACT(MONTH FROM p_schedule_date)::int
AND b.day_of_month = EXTRACT(DAY FROM p_schedule_date)::int
ELSE FALSE
END
)
SELECT DISTINCT -- 👈 this was missing in your version
a.bill_header_id,
a.draft_bill_header_id,
a.company_id,
a.organization_id
FROM aligned a
WHERE NOT EXISTS (
SELECT 1
FROM public.schedule_execution_logs l
WHERE l.schedule_id = a.schedule_id
AND l.execution_date = (p_schedule_date::date)
AND l.is_deleted = false
);
$function$
-- Function: create_vendor_advance_settlements_123
CREATE OR REPLACE FUNCTION public.create_vendor_advance_settlements_123(p_company_id uuid, p_vendor_id uuid, p_bill_payment_header_id uuid, p_created_by uuid, p_settlements jsonb)
RETURNS TABLE(id uuid, advance_settlement_number text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_settlement_number text;
v_row jsonb;
v_id uuid;
BEGIN
FOR v_row IN SELECT * FROM jsonb_array_elements(p_settlements)
LOOP
v_settlement_number := public.get_new_vendor_advance_settlement_number(
p_company_id,
COALESCE((v_row->>'settled_date')::date, CURRENT_DATE)
);
v_id := (v_row->>'id')::uuid;
INSERT INTO vendor_advance_settlements
(
id,
company_id,
vendor_id,
advance_id,
bill_id,
apply_amount,
applied_in_payment_id,
settled_date,
note,
created_on_utc,
created_by,
is_deleted,
advance_settlement_number
)
VALUES
(
v_id,
p_company_id,
p_vendor_id,
(v_row->>'advance_id')::uuid,
(v_row->>'bill_id')::uuid,
(v_row->>'apply_amount')::numeric,
p_bill_payment_header_id,
COALESCE((v_row->>'settled_date')::timestamp, now()),
NULLIF(v_row->>'note',''),
now(),
p_created_by,
false,
v_settlement_number
);
UPDATE vendor_advances va
SET utilized_amount = utilized_amount + (v_row->>'apply_amount')::numeric,
modified_by = p_created_by,
modified_on_utc = now()
WHERE va.id = (v_row->>'advance_id')::uuid
AND va.company_id = p_company_id
AND va.vendor_id = p_vendor_id
AND va.is_deleted = false;
-- Assign to output variables, then RETURN NEXT;
id := v_id;
advance_settlement_number := v_settlement_number;
RETURN NEXT;
END LOOP;
END;
$function$
-- Function: get_vendor_by_id
CREATE OR REPLACE FUNCTION public.get_vendor_by_id(p_vendor_id uuid)
RETURNS TABLE(id uuid, name text, gstin text, short_name text, pan text, tan text, proprietor_name text, outstanding_limit numeric, is_non_work boolean, interest_percentage numeric, billing_address_id uuid, billing_address jsonb, shipping_address_id uuid, shipping_address jsonb, bank_accounts jsonb, contacts jsonb, contact jsonb)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id,
v.name::text,
v.gstin::text,
v.short_name::text,
v.pan::text,
v.tan::text,
v.proprietor_name::text,
v.outstanding_limit,
v.is_non_work,
v.interest_percentage,
v.billing_address_id,
-- Billing Address as JSONB
CASE
WHEN ba.id IS NOT NULL THEN jsonb_build_object(
'AddressLine1', ba.address_line1,
'AddressLine2', ba.address_line2,
'ZipCode', ba.zip_code,
'CountryId', ba.country_id,
'StateId', ba.state_id,
'StateName', s.name,
'CityId', ba.city_id,
'CityName', ct.name
)
ELSE NULL
END AS billing_address,
v.shipping_address_id,
-- Shipping Address as JSONB
CASE
WHEN sa.id IS NOT NULL THEN jsonb_build_object(
'AddressLine1', sa.address_line1,
'AddressLine2', sa.address_line2,
'ZipCode', sa.zip_code,
'CountryId', sa.country_id,
'StateId', sa.state_id,
'StateName', s2.name,
'CityId', sa.city_id,
'CityName', ct2.name
)
ELSE NULL
END AS shipping_address,
-- Bank Accounts as JSONB Array
COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'Id', ba.id,
'BankId', ba.bank_id,
'BranchName', ba.branch_name,
'BankName', b.name,
'AccountNumber', ba.account_number,
'IFSC', ba.ifsc
)
)
FROM vendor_bank_accounts vba
JOIN bank_accounts ba ON vba.bank_account_id = ba.id
JOIN banks b ON ba.bank_id = b.id
WHERE vba.vendor_id = p_vendor_id AND ba.is_deleted = false
),
'[]'::jsonb
) AS bank_accounts,
-- Vendor Contacts as JSONB Array
COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'Id', con.id,
'Salutation', con.salutation,
'FirstName', con.first_name,
'LastName', con.last_name,
'Email', con.email,
'PhoneNumber', con.phone_number,
'MobileNumber', con.mobile_number,
'IsPrimary', con.is_primary
)
)
FROM vendor_contacts vc
JOIN contacts con ON vc.contact_id = con.id
WHERE vc.vendor_id = p_vendor_id AND con.is_deleted = false
),
'[]'::jsonb
) AS contacts,
-- Primary Contact as JSONB
COALESCE(
(
SELECT jsonb_build_object(
'Id', con.id,
'Salutation', con.salutation,
'FirstName', con.first_name,
'LastName', con.last_name,
'Email', con.email,
'PhoneNumber', con.phone_number,
'MobileNumber', con.mobile_number
)
FROM vendor_contacts vc
JOIN contacts con ON vc.contact_id = con.id
WHERE vc.vendor_id = p_vendor_id AND con.is_primary = TRUE AND con.is_deleted = false
),
NULL
) AS contact
FROM vendors v
LEFT JOIN addresses ba ON v.billing_address_id = ba.id
LEFT JOIN addresses sa ON v.shipping_address_id = sa.id
LEFT JOIN states s ON ba.state_id = s.id
LEFT JOIN cities ct ON ba.city_id = ct.id
LEFT JOIN states s2 ON sa.state_id = s2.id
LEFT JOIN cities ct2 ON sa.city_id = ct2.id
WHERE v.id = p_vendor_id AND v.is_deleted = false;
END;
$function$
-- Function: get_new_draft_bill_voucher
CREATE OR REPLACE FUNCTION public.get_new_draft_bill_voucher(p_company_id uuid, p_date date)
RETURNS character varying
LANGUAGE plpgsql
AS $function$
DECLARE
v_finance_year integer;
new_bill_id integer;
p_prefix varchar;
bill_voucher varchar;
BEGIN
-- Determine the start year of the financial year based on p_date
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
v_finance_year := EXTRACT(YEAR FROM p_date);
ELSE
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
END IF;
-- Attempt to update existing ID row or insert if not found
WITH x AS (
UPDATE public.draft_bill_header_ids
SET last_bill_id = last_bill_id + 1
WHERE company_id = p_company_id AND fin_year = v_finance_year
RETURNING last_bill_id, bill_prefix
),
insert_x AS (
INSERT INTO public.draft_bill_header_ids (
company_id, fin_year, bill_prefix, last_bill_id, bill_length
)
SELECT p_company_id, v_finance_year, 'DBV', 1, 8
WHERE NOT EXISTS (SELECT 1 FROM x)
RETURNING last_bill_id, bill_prefix
)
SELECT
COALESCE((SELECT last_bill_id FROM x LIMIT 1), (SELECT last_bill_id FROM insert_x LIMIT 1)),
COALESCE((SELECT bill_prefix FROM x LIMIT 1), (SELECT bill_prefix FROM insert_x LIMIT 1))
INTO new_bill_id, p_prefix;
-- Construct the final draft bill voucher number (e.g., DBV0001)
bill_voucher := p_prefix || LPAD(new_bill_id::text, 4, '0');
RETURN bill_voucher;
END;
$function$
-- Function: get_bill_payments_by_company
CREATE OR REPLACE FUNCTION public.get_bill_payments_by_company(p_company_id uuid)
RETURNS TABLE(id uuid, vendor_name text, payment_number text, vendor_id uuid, paid_amount numeric, grand_total_amount numeric, advance_amount numeric, credit_account_id uuid, description text, mode_of_payment text, paid_date timestamp without time zone, reference text, created_by uuid, modified_by uuid, created_by_name text, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
bph.id,
v.name :: text AS vendor_name,
bph.payment_number,
bph.vendor_id,
bph.paid_amount,
bph.grand_total_amount,
bph.advance_amount,
bph.credit_account_id,
bph.description,
bph.mode_of_payment,
bph.paid_date,
bph.reference,
bph.created_by,
bph.modified_by,
COALESCE(u1.first_name || ' ' || u1.last_name, '') AS created_by_name,
COALESCE(u2.first_name || ' ' || u2.last_name, '') AS modified_by_name,
bph.created_on_utc,
bph.modified_on_utc
FROM bill_payment_headers bph
LEFT JOIN vendors v ON v.id = bph.vendor_id
LEFT JOIN users u1 ON u1.id = bph.created_by
LEFT JOIN users u2 ON u2.id = bph.modified_by
WHERE bph.company_id = p_company_id
AND (bph.is_deleted IS FALSE OR bph.is_deleted IS NULL);
END;
$function$
-- Function: get_all_bill_account_approvals
CREATE OR REPLACE FUNCTION public.get_all_bill_account_approvals(p_company_id uuid)
RETURNS TABLE(id integer, account_id uuid, status_id integer, user_id uuid, approval_level integer, required_approval_levels integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
baua.id,
baua.account_id,
baua.status_id,
baua.user_id,
baua.approval_level,
baal.approval_level_required AS required_approval_levels
FROM bill_approval_user_account baua
JOIN public.bill_account_approval_levels baal
ON baua.company_id = baal.company_id
AND baal.account_id = baua.account_id
WHERE baua.company_id = p_company_id
--AND baal.account_id = ba.account_id
AND baua.is_deleted = false
AND baal.is_deleted = false
AND baal.status_id in(2)
ORDER BY baua.account_id, baua.status_id;
END;
$function$
-- Function: upsert_bill_status_company_config
CREATE OR REPLACE FUNCTION public.upsert_bill_status_company_config(p_company_id uuid, p_status_id integer, p_is_enabled boolean, p_user_id uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
-- Try update first
UPDATE bill_status_company_configs
SET is_enabled = p_is_enabled,
modified_by = p_user_id,
modified_on_utc = NOW()
WHERE company_id = p_company_id AND status_id = p_status_id AND is_deleted = FALSE;
IF NOT FOUND THEN
-- Insert if update affected no rows
INSERT INTO bill_status_company_configs (
company_id,
status_id,
is_enabled,
created_by,
created_on_utc,
is_deleted
) VALUES (
p_company_id,
p_status_id,
p_is_enabled,
p_user_id,
NOW(),
FALSE
);
END IF;
END;
$function$
-- Function: get_all_vendors
CREATE OR REPLACE FUNCTION public.get_all_vendors(p_company_id uuid, p_fin_year_id integer, p_is_get_all boolean)
RETURNS TABLE(id uuid, name character varying, contact_name text, gst_in text, mobile_number text, email text, created_by uuid, created_by_name text, modified_by uuid, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, billing_address jsonb, contact_count integer, total_billed_amount numeric, total_vendor_paid_amount numeric, total_tds_paid_amount numeric)
LANGUAGE plpgsql
AS $function$
DECLARE
v_organization_id uuid;
v_start_date date := MAKE_DATE(p_fin_year_id, 4, 1);
v_end_date date := MAKE_DATE(p_fin_year_id + 1, 3, 31);
BEGIN
SELECT com.organization_id INTO v_organization_id
FROM public.companies com
WHERE com.id = p_company_id;
RETURN QUERY
SELECT
v.id,
v.name,
con.first_name || ' ' || con.last_name AS contact_name,
v.gstin AS gst_in,
con.mobile_number,
con.email,
v.created_by,
u_created.first_name || ' ' || u_created.last_name AS created_by_name,
v.modified_by,
u_modified.first_name || ' ' || u_modified.last_name AS modified_by_name,
v.created_on_utc,
v.modified_on_utc,
CASE
WHEN ba.id IS NOT NULL THEN jsonb_build_object(
'AddressLine1', ba.address_line1,
'AddressLine2', ba.address_line2,
'ZipCode', ba.zip_code,
'CountryId', ba.country_id,
'StateId', ba.state_id,
'CityId', ba.city_id
)
ELSE NULL
END AS billing_address,
(SELECT COUNT(*)::integer
FROM public.vendor_contacts vc_count
WHERE vc_count.vendor_id = v.id) AS contact_count,
COALESCE(bill_sums.total_billed_amount, 0) AS total_billed_amount,
COALESCE(bill_sums.total_vendor_paid_amount, 0) AS total_vendor_paid_amount,
COALESCE(bill_sums.total_tds_paid_amount, 0) AS total_tds_paid_amount
FROM public.vendors v
JOIN public.vendor_contacts vc ON v.id = vc.vendor_id
JOIN public.contacts con ON vc.contact_id = con.id
LEFT JOIN public.users u_created ON v.created_by = u_created.id
LEFT JOIN public.users u_modified ON v.modified_by = u_modified.id
LEFT JOIN public.addresses ba ON v.billing_address_id = ba.id
LEFT JOIN (
SELECT
b.vendor_id,
SUM(b.total_amount) AS total_billed_amount,
SUM(b.vendor_paid_amount) AS total_vendor_paid_amount,
SUM(b.tds_paid_amount) AS total_tds_paid_amount
FROM public.bill_headers b
WHERE b.company_id = p_company_id
AND b.is_deleted = false
AND b.bill_date >= v_start_date
AND b.bill_date <= v_end_date
GROUP BY b.vendor_id
) bill_sums ON bill_sums.vendor_id = v.id
WHERE v.company_id IN (
SELECT c.id FROM public.companies c
WHERE (p_is_get_all AND c.organization_id = v_organization_id)
OR (NOT p_is_get_all AND c.id = p_company_id)
)
AND v.is_deleted = false
AND con.is_primary = true;
END;
$function$
-- Function: get_bill_analytics
CREATE OR REPLACE FUNCTION public.get_bill_analytics(billing_company_id uuid, p_finance_year_id integer, p_bill_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(status text, count integer, total_amount numeric, month_start_date date)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start DATE;
v_financial_year_end DATE;
v_date_start DATE;
v_date_end DATE;
BEGIN
-- Set default financial year range
v_financial_year_start := TO_DATE(p_finance_year_id || '-04-01', 'YYYY-MM-DD');
v_financial_year_end := TO_DATE((p_finance_year_id + 1) || '-03-31', 'YYYY-MM-DD');
-- Use date range override if provided, otherwise use financial year range
v_date_start := COALESCE(p_start_date::DATE, v_financial_year_start);
v_date_end := COALESCE(p_end_date::DATE, v_financial_year_end);
RETURN QUERY
-- PAID
SELECT 'Paid' AS status,
COUNT(*)::INTEGER AS count,
COALESCE(SUM(b.vendor_paid_amount), 0) AS total_amount,
DATE_TRUNC('month', b.bill_date)::DATE AS month_start_date
FROM bill_headers b
WHERE b.company_id = billing_company_id
AND b.bill_status_id IN (4, 5)
AND b.is_deleted = false
AND b.bill_date BETWEEN v_date_start AND v_date_end
AND (p_bill_type_id IS NULL OR b.bill_type_id = p_bill_type_id)
GROUP BY month_start_date
UNION ALL
-- PENDING
SELECT 'Pending' AS status,
COUNT(*)::INTEGER AS count,
COALESCE(SUM(b.total_amount - b.vendor_paid_amount), 0) AS total_amount,
DATE_TRUNC('month', b.bill_date)::DATE AS month_start_date
FROM bill_headers b
WHERE b.company_id = billing_company_id
AND b.bill_status_id IN (3, 4)
AND b.is_deleted = false
AND b.bill_date BETWEEN v_date_start AND v_date_end
AND (p_bill_type_id IS NULL OR b.bill_type_id = p_bill_type_id)
GROUP BY month_start_date
UNION ALL
-- OVERDUE
SELECT 'Overdue' AS status,
COUNT(*)::INTEGER AS count,
COALESCE(SUM(b.total_amount - b.vendor_paid_amount), 0) AS total_amount,
DATE_TRUNC('month', b.bill_date)::DATE AS month_start_date
FROM bill_headers b
WHERE b.company_id = billing_company_id
AND b.bill_status_id IN (3, 4)
AND b.is_deleted = false
AND b.due_date < CURRENT_DATE
AND b.bill_date BETWEEN v_date_start AND v_date_end
AND (p_bill_type_id IS NULL OR b.bill_type_id = p_bill_type_id)
GROUP BY month_start_date;
END;
$function$
-- Function: get_bill_approval_log
CREATE OR REPLACE FUNCTION public.get_bill_approval_log(p_bill_id uuid)
RETURNS TABLE(status integer, status_name text, next_status integer, next_status_name text, previous_status integer, previous_status_name text, is_initbal boolean, approver_user_id uuid, approver_user_name text, approved_by uuid, approved_by_name text, approved_on timestamp without time zone, bill_id uuid, sales_account uuid, created_by_name text, approval_level integer)
LANGUAGE plpgsql
AS $function$
BEGIN
IF EXISTS (SELECT 1 FROM public.bill_headers WHERE id = p_bill_id) THEN
RETURN QUERY
SELECT
bw.status,
cs.name::TEXT AS status_name,
bw.next_status,
ns.name::TEXT AS next_status_name,
bw.previous_status,
ps.name::TEXT AS previous_status_name,
bw.is_initial,
COALESCE(baua.user_id, bauc.user_id) AS approver_user_id,
CONCAT(COALESCE(uaa.first_name, uca.first_name), ' ', COALESCE(uaa.last_name, uca.last_name))::TEXT AS approver_user_name,
bal.approved_by,
CONCAT(approved_by_user.first_name, ' ', approved_by_user.last_name)::TEXT AS approved_by_name,
bal.approved_on,
bh.id AS bill_id,
bh.credit_account_id AS sales_account,
CONCAT(created_by_user.first_name, ' ', created_by_user.last_name)::TEXT AS created_by_name,
bw.approval_level
FROM bill_workflow bw
JOIN bill_headers bh ON bh.id = p_bill_id
LEFT JOIN bill_statuses cs ON cs.id = bw.status
LEFT JOIN bill_statuses ns ON ns.id = bw.next_status
LEFT JOIN bill_statuses ps ON ps.id = bw.previous_status
LEFT JOIN bill_approval_user_company bauc
ON bauc.company_id = bh.company_id
AND bauc.status_id = bw.status
AND bauc.approval_level = bw.approval_level
LEFT JOIN bill_approval_user_account baua
ON baua.company_id = bh.company_id
AND baua.account_id = bh.credit_account_id
AND baua.status_id = bw.status
AND baua.approval_level = bw.approval_level
LEFT JOIN users uca ON uca.id = bauc.user_id
LEFT JOIN users uaa ON uaa.id = baua.user_id
LEFT JOIN users created_by_user ON created_by_user.id = bh.created_by
LEFT JOIN LATERAL (
SELECT * FROM bill_approval_logs log
WHERE log.bill_id = bh.id
AND log.status_id = bw.status
AND log.approval_level = bw.approval_level
ORDER BY log.approved_on DESC
LIMIT 1
) bal ON true
LEFT JOIN users approved_by_user ON approved_by_user.id = bal.approved_by
WHERE bw.company_id = bh.company_id
AND bw.is_deleted = false
AND bw.is_enabled = true
ORDER BY bw.approval_level;
ELSE
RETURN QUERY
SELECT
bw.status,
cs.name::TEXT AS status_name,
bw.next_status,
ns.name::TEXT AS next_status_name,
bw.previous_status,
ps.name::TEXT AS previous_status_name,
bw.is_initial,
COALESCE(baua.user_id, bauc.user_id) AS approver_user_id,
CONCAT(COALESCE(uaa.first_name, uca.first_name), ' ', COALESCE(uaa.last_name, uca.last_name))::TEXT AS approver_user_name,
NULL::UUID AS approved_by,
NULL::TEXT AS approved_by_name,
NULL::TIMESTAMP AS approved_on,
dbh.id AS bill_id,
dbh.credit_account_id AS sales_account,
CONCAT(created_by_user.first_name, ' ', created_by_user.last_name)::TEXT AS created_by_name,
bw.approval_level
FROM bill_workflow bw
JOIN draft_bill_headers dbh ON dbh.id = p_bill_id
LEFT JOIN bill_statuses cs ON cs.id = bw.status
LEFT JOIN bill_statuses ns ON ns.id = bw.next_status
LEFT JOIN bill_statuses ps ON ps.id = bw.previous_status
LEFT JOIN bill_approval_user_company bauc
ON bauc.company_id = dbh.company_id
AND bauc.status_id = bw.status
AND bauc.approval_level = bw.approval_level
LEFT JOIN bill_approval_user_account baua
ON baua.company_id = dbh.company_id
AND baua.account_id = dbh.credit_account_id
AND baua.status_id = bw.status
AND baua.approval_level = bw.approval_level
LEFT JOIN users uca ON uca.id = bauc.user_id
LEFT JOIN users uaa ON uaa.id = baua.user_id
LEFT JOIN users created_by_user ON created_by_user.id = dbh.created_by
WHERE bw.company_id = dbh.company_id
AND bw.is_deleted = false
AND bw.is_enabled = true
ORDER BY bw.approval_level;
END IF;
END;
$function$
-- Function: get_new_vendor_advance_number
CREATE OR REPLACE FUNCTION public.get_new_vendor_advance_number(p_company_id uuid, p_date date)
RETURNS character varying
LANGUAGE plpgsql
AS $function$
DECLARE
v_finance_year integer;
new_advance_id integer;
p_prefix varchar;
advance_number varchar;
BEGIN
-- Determine the start year of the financial year based on p_date
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
v_finance_year := EXTRACT(YEAR FROM p_date);
ELSE
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
END IF;
-- Attempt to update existing row or insert if not found
WITH x AS (
UPDATE public.vendor_advance_ids
SET last_advance_id = last_advance_id + 1
WHERE company_id = p_company_id AND fin_year = v_finance_year
RETURNING last_advance_id, advance_prefix
),
insert_x AS (
INSERT INTO public.vendor_advance_ids (
company_id, fin_year, advance_prefix, last_advance_id, advance_length
)
SELECT p_company_id, v_finance_year, 'VADV', 1, 8
WHERE NOT EXISTS (SELECT 1 FROM x)
RETURNING last_advance_id, advance_prefix
)
SELECT
COALESCE((SELECT last_advance_id FROM x LIMIT 1), (SELECT last_advance_id FROM insert_x LIMIT 1)),
COALESCE((SELECT advance_prefix FROM x LIMIT 1), (SELECT advance_prefix FROM insert_x LIMIT 1))
INTO new_advance_id, p_prefix;
-- Construct the final advance number (e.g., VADV0001)
advance_number := p_prefix || LPAD(new_advance_id::text, 4, '0');
RETURN advance_number;
END;
$function$
-- Function: upsert_bill_workflow_and_config
CREATE OR REPLACE FUNCTION public.upsert_bill_workflow_and_config(p_company_id uuid, p_config jsonb, p_user_id uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
item JSONB;
v_status INT;
BEGIN
FOR item IN SELECT * FROM jsonb_array_elements(p_config)
LOOP
-- Convert status to INT (used in both tables)
v_status := (item->>'Status')::INT;
-- First: Upsert into bill_workflow
IF EXISTS (
SELECT 1 FROM bill_workflow
WHERE company_id = p_company_id
AND status = (item->>'Status')::INT
AND next_status = (item->>'NextStatus')::INT
AND previous_status = (item->>'PreviousStatus')::INT
AND is_deleted = false
) THEN
UPDATE bill_workflow
SET
is_initial = (item->>'is_initial')::BOOL,
modified_on_utc = now(),
modified_by = p_user_id
WHERE company_id = p_company_id
AND status = (item->>'Status')::INT
AND next_status = (item->>'NextStatus')::INT
AND previous_status = (item->>'PreviousStatus')::INT
AND is_deleted = false;
ELSE
INSERT INTO public.bill_workflow
(company_id, status, next_status, previous_status, is_initial, created_on_utc, modified_on_utc,
deleted_on_utc, is_deleted, created_by, modified_by)
VALUES (
p_company_id,
(item->>'Status')::INT,
(item->>'NextStatus')::INT,
(item->>'PreviousStatus')::INT,
(item->>'IsInitial')::BOOL,
now(),
null,
null,
false,
p_user_id,
null
);
END IF;
-- Second: Upsert into bill_status_company_configs
INSERT INTO public.bill_status_company_configs
(company_id, status_id, is_enabled, created_on_utc, modified_on_utc, created_by, modified_by, is_deleted)
VALUES (
p_company_id,
v_status,
(item->>'IsEnabled')::BOOL,
now(),
null,
p_user_id,
null,
false
)
ON CONFLICT (company_id, status_id)
DO UPDATE SET
is_enabled = EXCLUDED.is_enabled,
modified_on_utc = now(),
modified_by = p_user_id;
END LOOP;
END;
$function$
-- Function: check_bill_approval_permissions
CREATE OR REPLACE FUNCTION public.check_bill_approval_permissions(p_company_id uuid, p_status_id integer, p_user_id uuid, p_account_id uuid, p_approval_level integer)
RETURNS TABLE(company_id uuid, status_id integer, user_id uuid, approval_level integer, account_id uuid, account_status_id integer, permission_check text)
LANGUAGE plpgsql
AS $function$
DECLARE
v_has_permission BOOLEAN;
BEGIN
-- First check for account-level permissions
RETURN QUERY
SELECT
baua.company_id,
baua.status_id,
baua.user_id,
baua.approval_level,
baua.account_id,
baua.status_id AS account_status_id,
'Account-level permission granted' AS permission_check
FROM public.bill_approval_user_account baua
WHERE baua.company_id = p_company_id
AND baua.status_id = p_status_id
AND baua.user_id = p_user_id
AND baua.account_id = p_account_id
AND baua.approval_level = p_approval_level
AND baua.is_deleted = false;
-- If no rows found at account-level, check company-level
IF NOT FOUND THEN
RETURN QUERY
SELECT
bauc.company_id,
bauc.status_id,
bauc.user_id,
bauc.approval_level,
NULL::uuid AS account_id,
bauc.status_id AS account_status_id,
'Company-level permission granted' AS permission_check
FROM public.bill_approval_user_company bauc
WHERE bauc.company_id = p_company_id
AND bauc.status_id = p_status_id
AND bauc.user_id = p_user_id
AND bauc.approval_level = p_approval_level
AND bauc.is_deleted = false;
END IF;
END;
$function$
-- Function: get_all_bill_headers_by_vendor_id
CREATE OR REPLACE FUNCTION public.get_all_bill_headers_by_vendor_id(p_vendor_id uuid, p_financial_year integer)
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, debit_account_id uuid, bill_date timestamp without time zone, vendor_id uuid, vendor_name text, due_date timestamp without time zone, payment_term integer, note text, currency_id integer, discount numeric, bill_status_id integer, bill_status text, total_amount numeric, tds_amount numeric, vendor_paid_amount numeric, tds_paid_amount numeric, is_vendor_paid boolean, is_tds_paid boolean, is_closed boolean, total_paid_amount numeric, vendor_net_amount numeric, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, bill_type_id integer, current_approval_level integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_is_tds_vendor boolean;
start_date date := make_date(p_financial_year, 4, 1);
end_date date := make_date(p_financial_year + 1, 3, 31);
v_company_id uuid;
v_org_id uuid;
BEGIN
-- Get is_tds_vendor flag and company_id for the vendor
SELECT v.is_tds_vendor,
v.company_id
INTO v_is_tds_vendor, v_company_id
FROM public.vendors v
WHERE v.id = p_vendor_id;
-- Get organization_id of the company related to the vendor
SELECT c.organization_id
INTO v_org_id
FROM public.companies c
WHERE c.id = v_company_id;
IF NOT v_is_tds_vendor THEN
RETURN QUERY
SELECT
bh.id AS id,
bh.bill_number AS bill_number,
bh.bill_voucher AS bill_voucher,
bh.debit_account_id AS debit_account_id,
bh.bill_date AS bill_date,
v.id AS vendor_id,
v.name::text AS vendor_name,
bh.due_date AS due_date,
bh.payment_term AS payment_term,
bh.note AS note,
bh.currency_id AS currency_id,
bh.discount AS discount,
bs.id AS bill_status_id,
bs.name::text AS bill_status,
bh.total_amount AS total_amount,
COALESCE(bh.tds_amount, 0) AS tds_amount,
COALESCE(bh.vendor_paid_amount, 0) AS vendor_paid_amount,
COALESCE(bh.tds_paid_amount, 0) AS tds_paid_amount,
COALESCE(bh.is_vendor_paid, false) AS is_vendor_paid,
COALESCE(bh.is_tds_paid, false) AS is_tds_paid,
COALESCE(bh.is_closed, false) AS is_closed,
COALESCE(bh.total_paid_amount, 0) AS total_paid_amount,
COALESCE(bh.vendor_net_amount, 0) AS vendor_net_amount,
bh.created_by AS created_by,
bh.created_on_utc AS created_on_utc,
bh.modified_by AS modified_by,
bh.modified_on_utc AS modified_on_utc,
bh.bill_type_id AS bill_type_id,
bh.current_approval_level AS current_approval_level
FROM public.bill_headers bh
INNER JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
INNER JOIN public.vendors v ON v.id = bh.vendor_id
WHERE bh.vendor_id = p_vendor_id
AND bh.bill_date BETWEEN start_date AND end_date
AND bh.bill_status_id IN (3, 4)
AND bh.is_deleted = false;
--AND bh.is_vendor_paid = false;
ELSE
RETURN QUERY
SELECT
bh.id AS id,
bh.bill_number AS bill_number,
bh.bill_voucher AS bill_voucher,
bh.debit_account_id AS debit_account_id,
bh.bill_date AS bill_date,
v.id AS vendor_id,
v.name::text AS vendor_name,
bh.due_date AS due_date,
bh.payment_term AS payment_term,
bh.note AS note,
bh.currency_id AS currency_id,
bh.discount AS discount,
bs.id AS bill_status_id,
bs.name::text AS bill_status,
bh.total_amount AS total_amount,
COALESCE(bh.tds_amount, 0) AS tds_amount,
COALESCE(bh.vendor_paid_amount, 0) AS vendor_paid_amount,
COALESCE(bh.tds_paid_amount, 0) AS tds_paid_amount,
COALESCE(bh.is_vendor_paid, false) AS is_vendor_paid,
COALESCE(bh.is_tds_paid, false) AS is_tds_paid,
COALESCE(bh.is_closed, false) AS is_closed,
COALESCE(bh.total_paid_amount, 0) AS total_paid_amount,
COALESCE(bh.vendor_net_amount, 0) AS vendor_net_amount,
bh.created_by AS created_by,
bh.created_on_utc AS created_on_utc,
bh.modified_by AS modified_by,
bh.modified_on_utc AS modified_on_utc,
bh.bill_type_id AS bill_type_id,
bh.current_approval_level AS current_approval_level
FROM public.bill_headers bh
INNER JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
INNER JOIN public.vendors v ON v.id = bh.vendor_id
WHERE bh.bill_date BETWEEN start_date AND end_date
AND bh.bill_status_id IN (3, 4, 5)
AND bh.is_tds_paid = false
AND COALESCE(bh.tds_amount, 0) > 0
AND bh.is_deleted = false
AND bh.company_id IN (
SELECT c.id
FROM public.companies c
WHERE c.organization_id = v_org_id
);
END IF;
END;
$function$
-- Function: get_all_pending_tds_bill_headers
CREATE OR REPLACE FUNCTION public.get_all_pending_tds_bill_headers(p_financial_year integer)
RETURNS TABLE(id uuid, bill_number text, bill_voucher text, debit_account_id uuid, bill_date timestamp without time zone, vendor_id uuid, vendor_name text, due_date timestamp without time zone, payment_term integer, note text, currency_id integer, discount numeric, bill_status_id integer, bill_status text, total_amount numeric, tds_amount numeric, setteled_amount numeric, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, bill_type_id integer, current_approval_level integer)
LANGUAGE plpgsql
AS $function$
DECLARE
start_date date := make_date(p_financial_year, 4, 1); -- 1 April of given year
end_date date := make_date(p_financial_year + 1, 3, 31); -- 31 March of next year
BEGIN
RETURN QUERY
SELECT
bh.id,
bh.bill_number,
bh.bill_voucher,
bh.debit_account_id,
bh.bill_date,
v.id,
v.name::text,
bh.due_date,
bh.payment_term,
bh.note,
bh.currency_id,
bh.discount,
bs.id,
bs.name::text,
bh.total_amount,
COALESCE(bh.tds_amount, 0),
-- CASE
-- WHEN COALESCE(bh.tds_amount, 0) = 0 THEN 0
-- ELSE ROUND((bh.setteled_amount / bh.total_amount) * bh.tds_amount)
-- END,
bh.setteled_amount,
bh.created_by,
bh.created_on_utc,
bh.modified_by,
bh.modified_on_utc,
bh.bill_type_id,
bh.current_approval_level
FROM public.bill_headers bh
JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
JOIN public.vendors v ON v.id = bh.vendor_id
WHERE
bh.bill_date >= start_date
AND bh.bill_date <= end_date
AND COALESCE(bh.tds_amount, 0) > 0
AND COALESCE(bh.is_tds_paid, false) = false
AND bh.setteled_amount > 0
--AND (COALESCE(bh.tds_amount, 0) - COALESCE(bh.paid_tds_amount, 0)) > 0
AND bh.is_deleted = false;
END;
$function$
-- Function: get_all_tds_paid_bills
CREATE OR REPLACE FUNCTION public.get_all_tds_paid_bills(p_company_id uuid, p_financial_year integer, p_start_date date DEFAULT NULL::date, p_end_date date DEFAULT NULL::date)
RETURNS TABLE(id uuid, bill_header_id uuid, bill_number text, vendor_id uuid, vendor_name text, tds_amount numeric, is_tds_paid boolean, total_paid_amount numeric, created_on_utc timestamp without time zone, created_by uuid, modified_on_utc timestamp without time zone, modified_by uuid, company_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
bh.id AS id,
bh.id AS bill_header_id,
bh.bill_number,
bh.vendor_id,
v.name::text AS vendor_name, -- <-- Join & select vendor name
bh.tds_amount,
bh.is_tds_paid,
bh.setteled_amount AS total_paid_amount,
bh.created_on_utc,
bh.created_by,
bh.modified_on_utc,
bh.modified_by,
bh.company_id
FROM bill_headers bh
INNER JOIN vendors v ON v.id = bh.vendor_id -- <-- Join
WHERE
bh.company_id = p_company_id
AND EXTRACT(YEAR FROM bh.bill_date) = p_financial_year
AND bh.is_tds_paid = true
AND bh.is_deleted = false
AND bh.tds_amount > 0
AND (p_start_date IS NULL OR bh.bill_date >= p_start_date)
AND (p_end_date IS NULL OR bh.bill_date <= p_end_date);
END;
$function$
-- Function: get_all_vendor_advances
CREATE OR REPLACE FUNCTION public.get_all_vendor_advances(p_company_id uuid, p_finance_year_id integer, p_only_unutilized boolean DEFAULT false)
RETURNS TABLE(id uuid, vendor_id uuid, vendor_name character varying, advance_number text, paid_date timestamp without time zone, amount numeric, utilized_amount numeric, mode_of_payment text, reference text, description text, currency_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_financial_year_start date;
v_financial_year_end date;
BEGIN
-- Start of financial year: April 1 of the given year
v_financial_year_start := TO_DATE(p_finance_year_id || '-04-01', 'YYYY-MM-DD');
-- End of financial year: March 31 of the next year
v_financial_year_end := TO_DATE((p_finance_year_id + 1) || '-03-31', 'YYYY-MM-DD');
RETURN QUERY
SELECT
va.id,
va.vendor_id,
v.name, -- Add vendor name
va.advance_number,
va.paid_date,
va.amount,
va.utilized_amount,
va.mode_of_payment,
va.reference,
va.description,
va.currency_id
FROM public.vendor_advances va
LEFT JOIN public.vendors v ON va.vendor_id = v.id -- Join here to get the name
WHERE va.company_id = p_company_id
AND va.is_deleted = false
AND v.is_deleted = false
AND va.paid_date >= v_financial_year_start
AND va.paid_date <= v_financial_year_end
AND (
(p_only_unutilized = true AND va.amount > va.utilized_amount)
OR (p_only_unutilized IS DISTINCT FROM true)
);
END;
$function$
-- Function: get_bill_lines
CREATE OR REPLACE FUNCTION public.get_bill_lines(p_bill_header_id uuid, p_bill_status_id integer)
RETURNS TABLE(id uuid, product_id uuid, price numeric, quantity numeric, fees numeric, discount numeric, taxable_amount numeric, sgst_amount numeric, cgst_amount numeric, igst_amount numeric, total_amount numeric, serial_number integer)
LANGUAGE plpgsql
AS $function$
BEGIN
IF p_bill_status_id >= 3 THEN
-- Query Approved Bill Details
RETURN QUERY
SELECT
bd.id,
bd.product_id,
bd.price,
bd.quantity,
bd.fees,
bd.discount,
bd.taxable_amount,
bd.sgst_amount,
bd.cgst_amount,
bd.igst_amount,
bd.total_amount,
bd.serial_number
FROM bill_details bd
WHERE bd.bill_header_id = p_bill_header_id AND bd.is_deleted = FALSE
ORDER BY bd.serial_number;
ELSE
-- Query Draft Bill Details
RETURN QUERY
SELECT
dbd.id,
dbd.product_id,
dbd.price,
dbd.quantity,
dbd.fees,
dbd.discount,
dbd.taxable_amount,
dbd.sgst_amount,
dbd.cgst_amount,
dbd.igst_amount,
dbd.total_amount,
dbd.serial_number
FROM draft_bill_details dbd
WHERE dbd.bill_header_id = p_bill_header_id AND dbd.is_deleted = FALSE
ORDER BY dbd.serial_number;
END IF;
END;
$function$
-- Function: get_bill_payments_by_company_with_tds_check
CREATE OR REPLACE FUNCTION public.get_bill_payments_by_company_with_tds_check(p_company_id uuid, p_is_tds_paid_bills boolean)
RETURNS TABLE(id uuid, vendor_name text, payment_number text, vendor_id uuid, paid_amount numeric, grand_total_amount numeric, advance_amount numeric, credit_account_id uuid, description text, mode_of_payment text, paid_date timestamp without time zone, reference text, created_by uuid, modified_by uuid, created_by_name text, modified_by_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
--select is_tds_vendor into v_is_tds_vendor from vendors where id = p_vendor_id;
RETURN QUERY
SELECT
bph.id,
v.name :: text AS vendor_name,
bph.payment_number,
bph.vendor_id,
bph.paid_amount,
bph.grand_total_amount,
bph.advance_amount,
bph.credit_account_id,
bph.description,
bph.mode_of_payment,
bph.paid_date,
bph.reference,
bph.created_by,
bph.modified_by,
COALESCE(u1.first_name || ' ' || u1.last_name, '') AS created_by_name,
COALESCE(u2.first_name || ' ' || u2.last_name, '') AS modified_by_name,
bph.created_on_utc,
bph.modified_on_utc
FROM bill_payment_headers bph
LEFT JOIN vendors v ON v.id = bph.vendor_id
LEFT JOIN users u1 ON u1.id = bph.created_by
LEFT JOIN users u2 ON u2.id = bph.modified_by
WHERE bph.company_id = p_company_id
AND (bph.is_deleted IS FALSE OR bph.is_deleted IS NULL)
AND(
(p_is_tds_paid_bills = TRUE AND v.is_tds_vendor = TRUE)
or
(p_is_tds_paid_bills = FALSE)
);
END;
$function$
-- Function: get_new_vendor_advance_settlement_number
CREATE OR REPLACE FUNCTION public.get_new_vendor_advance_settlement_number(p_company_id uuid, p_date date)
RETURNS character varying
LANGUAGE plpgsql
AS $function$
DECLARE
v_finance_year int;
new_id int;
v_prefix text := 'ADV'; -- 👈 default prefix
v_length int := 4; -- 👈 default length
number varchar;
BEGIN
-- derive financial year
IF EXTRACT(MONTH FROM p_date) >= 4 THEN
v_finance_year := EXTRACT(YEAR FROM p_date);
ELSE
v_finance_year := EXTRACT(YEAR FROM p_date) - 1;
END IF;
-- upsert into vendor_advance_settlement_ids
WITH upd AS (
UPDATE public.vendor_advance_settlement_ids
SET last_settlement_id = last_settlement_id + 1
WHERE company_id = p_company_id AND fin_year = v_finance_year
RETURNING last_settlement_id, settlement_prefix, settlement_length
), ins AS (
INSERT INTO public.vendor_advance_settlement_ids(
company_id, fin_year, settlement_prefix, settlement_length, last_settlement_id
)
SELECT p_company_id, v_finance_year, v_prefix, v_length, 1
WHERE NOT EXISTS (SELECT 1 FROM upd)
RETURNING last_settlement_id, settlement_prefix, settlement_length
)
SELECT COALESCE(u.last_settlement_id, i.last_settlement_id),
COALESCE(u.settlement_prefix, i.settlement_prefix),
COALESCE(u.settlement_length, i.settlement_length)
INTO new_id, v_prefix, v_length
FROM upd u
FULL JOIN ins i ON true;
-- build formatted settlement number
number := v_prefix || LPAD(new_id::text, v_length, '0');
RETURN number;
END;
$function$
-- Function: get_tds_bills_from_span
CREATE OR REPLACE FUNCTION public.get_tds_bills_from_span(p_company_id uuid, p_fin_year_id integer, p_user_id uuid, p_bill_type_id integer DEFAULT NULL::integer, p_start_date timestamp without time zone DEFAULT NULL::timestamp without time zone, p_end_date timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS TABLE(id uuid, bill_voucher text, bill_number text, vendor_name character varying, vendor_id uuid, due_date date, bill_date date, payment_term integer, total_amount numeric, total_paid_amount numeric, currency_id integer, bill_status character varying, bill_status_id integer, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone, created_by_name character varying, modified_by_name character varying, bill_type character varying, bill_type_id integer, tds_amount numeric, has_next_status_approval boolean, next_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_start_date date := COALESCE(p_start_date::date, MAKE_DATE(p_fin_year_id, 4, 1));
v_end_date date := COALESCE(p_end_date::date, MAKE_DATE(p_fin_year_id + 1, 3, 31));
DRAFT_STATUS CONSTANT integer := 1;
BEGIN
RETURN QUERY
SELECT
bh.id,
bh.bill_voucher,
bh.bill_number,
v.name AS vendor_name,
bh.vendor_id,
bh.due_date::date,
bh.bill_date::date,
bh.payment_term,
bh.total_amount,
bh.total_paid_amount,
bh.currency_id,
bs.name::varchar AS bill_status,
bh.bill_status_id,
bh.created_by,
bh.created_on_utc,
bh.modified_by,
bh.modified_on_utc,
CONCAT(cu.first_name, ' ', cu.last_name)::varchar AS created_by_name,
CONCAT(mu.first_name, ' ', mu.last_name)::varchar AS modified_by_name,
bt.name::varchar AS bill_type,
bh.bill_type_id,
bh.tds_amount,
CASE
WHEN bh.bill_status_id = DRAFT_STATUS THEN true
WHEN EXISTS (
SELECT 1
FROM bill_approval_user_account a
WHERE a.account_id = bh.debit_account_id
AND a.status_id = bh.bill_status_id
AND a.user_id = p_user_id
AND a.is_deleted = false
) THEN true
WHEN EXISTS (
SELECT 1
FROM bill_approval_user_company c
WHERE c.company_id = p_company_id
AND c.status_id = bh.bill_status_id
AND c.user_id = p_user_id
AND c.is_deleted = false
) THEN true
ELSE false
END AS has_next_status_approval,
COALESCE(
(SELECT bw.next_status
FROM bill_workflow bw
WHERE bw.company_id = p_company_id
AND bw.status = bh.bill_status_id
AND bw.is_deleted = false
AND (bw.is_enabled IS DISTINCT FROM FALSE)
ORDER BY bw.approval_level ASC NULLS LAST
LIMIT 1),
0
) AS next_status_id
FROM public.bill_headers bh
LEFT JOIN public.vendors v ON v.id = bh.vendor_id
LEFT JOIN public.bill_statuses bs ON bs.id = bh.bill_status_id
LEFT JOIN public.bill_types bt ON bt.id = bh.bill_type_id
LEFT JOIN users cu ON cu.id = bh.created_by
LEFT JOIN users mu ON mu.id = bh.modified_by
WHERE bh.company_id = p_company_id
AND bh.is_deleted = false
AND bh.tds_amount IS NOT NULL
AND bh.tds_amount > 0
AND (p_bill_type_id IS NULL OR p_bill_type_id = 0 OR bh.bill_type_id = p_bill_type_id)
AND bh.bill_date BETWEEN v_start_date AND v_end_date;
END;
$function$
-- Procedure: create_bill_payment
CREATE OR REPLACE PROCEDURE public.create_bill_payment(IN p_bill_payment_header_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_paid_date date, IN p_mode_of_payment text, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_reference text, IN p_paid_amount numeric, IN p_grand_total_amount numeric, IN p_advance_amount numeric, IN p_description text, IN p_tds_amount numeric, IN p_created_by uuid, IN p_bill_payment_details jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_status_id INT;
v_bill_header_id UUID;
v_payment_amount NUMERIC;
v_tds_amount NUMERIC;
v_serial_number INT;
v_row JSONB;
v_payment_number character varying;
v_is_tds_vendor bool;
v_is_tds_paid bool;
v_bill_total_amount NUMERIC;
v_bill_tds_amount NUMERIC;
v_vendor_paid_amount NUMERIC;
v_tds_paid_amount NUMERIC;
v_tds_status_id INT;
v_payment_status_id INT;
v_vendor_net_amount NUMERIC;
BEGIN
-- Generate new payment number
v_payment_number := get_new_payment_number(p_company_id, p_paid_date);
-- Check if vendor is TDS vendor
SELECT is_tds_vendor
INTO v_is_tds_vendor
FROM vendors
WHERE id = p_vendor_id;
-- Insert payment header
INSERT INTO public.bill_payment_headers (
id,
company_id,
vendor_id,
paid_date,
mode_of_payment,
credit_account_id,
debit_account_id,
reference,
paid_amount,
description,
tds_amount,
advance_amount,
grand_total_amount,
payment_number,
created_by,
created_on_utc,
is_deleted
)
VALUES (
p_bill_payment_header_id,
p_company_id,
p_vendor_id,
p_paid_date,
p_mode_of_payment,
p_credit_account_id,
p_debit_account_id,
p_reference,
p_paid_amount,
p_description,
p_tds_amount,
p_advance_amount,
p_grand_total_amount,
v_payment_number,
p_created_by,
now(),
false
);
-- Loop through each bill payment detail record
FOR v_row IN SELECT * FROM jsonb_array_elements(p_bill_payment_details)
LOOP
v_bill_header_id := (v_row->>'header_id')::UUID;
v_payment_amount := COALESCE((v_row->>'payment_amount')::NUMERIC, 0);
v_tds_amount := COALESCE((v_row->>'tds_amount')::NUMERIC, 0);
v_serial_number := (v_row->>'serial_number')::INT;
v_is_tds_paid := COALESCE((v_row->>'is_tds_paid')::BOOLEAN, false);
-- Fetch current amounts from bill header
SELECT vendor_paid_amount, total_amount, tds_amount, tds_paid_amount, vendor_net_amount
INTO v_vendor_paid_amount, v_bill_total_amount, v_bill_tds_amount, v_tds_paid_amount, v_vendor_net_amount
FROM public.bill_headers
WHERE id = v_bill_header_id;
-- Insert payment details record
INSERT INTO public.bill_payment_details (
id,
bill_payment_header_id,
bill_header_id,
paid_amount,
tds_amount,
serial_number,
created_by,
created_on_utc
)
VALUES (
(v_row->>'id')::uuid,
p_bill_payment_header_id,
v_bill_header_id,
v_payment_amount,
v_tds_amount,
v_serial_number,
p_created_by,
now()
);
-- Update paid amounts based on payment type
IF v_is_tds_vendor OR v_is_tds_paid THEN
v_tds_paid_amount := COALESCE(v_tds_paid_amount, 0) + v_payment_amount;
ELSE
v_vendor_paid_amount := COALESCE(v_vendor_paid_amount, 0) + v_payment_amount;
END IF;
-- Status calculations using updated paid amounts (after applying current payment)
-- Bill payment status
IF (v_vendor_paid_amount >= (v_bill_total_amount - v_bill_tds_amount))
AND (v_tds_paid_amount >= v_bill_tds_amount) THEN
v_bill_status_id := 5; -- PAID
ELSE
v_bill_status_id := 4; -- PARTIALLY_PAID
END IF;
-- TDS status for TDS vendors
IF v_is_tds_vendor THEN
IF v_tds_paid_amount = 0 AND v_bill_tds_amount > 0 THEN
v_tds_status_id := 2; -- Unpaid
ELSIF v_tds_paid_amount > 0 AND v_tds_paid_amount = v_bill_tds_amount THEN
v_tds_status_id := 3; -- Deducted
ELSE
v_tds_status_id := 1; -- N/A fallback
END IF;
ELSE
v_tds_status_id := 1; -- N/A for non-TDS vendors
END IF;
-- Payment status assignment (now covers both TDS and non-TDS vendors)
IF NOT v_is_tds_vendor THEN
IF v_vendor_net_amount > 0 AND v_vendor_paid_amount = 0 THEN
v_payment_status_id := 1; -- Unpaid
ELSIF v_vendor_net_amount > 0 AND v_vendor_paid_amount > 0 AND v_vendor_paid_amount < v_vendor_net_amount THEN
v_payment_status_id := 2; -- PartiallyPaid
ELSIF v_vendor_net_amount = v_vendor_paid_amount THEN
v_payment_status_id := 3; -- FullyPaid
ELSE
v_payment_status_id := 1; -- Default Unpaid fallback
END IF;
ELSE
-- TDS vendors
IF v_vendor_net_amount = v_vendor_paid_amount THEN
v_payment_status_id := 3; -- FullyPaid
ELSIF v_vendor_paid_amount > 0 THEN
v_payment_status_id := 2; -- PartiallyPaid
ELSE
v_payment_status_id := 1; -- Unpaid
END IF;
END IF;
-- Final safeguard to avoid nulls
v_payment_status_id := COALESCE(v_payment_status_id, 1);
-- Update bill header with new payment and status details
UPDATE public.bill_headers
SET vendor_paid_amount = v_vendor_paid_amount,
tds_paid_amount = v_tds_paid_amount,
bill_status_id = v_bill_status_id,
tds_status_id = CASE
WHEN v_is_tds_vendor THEN v_tds_status_id
ELSE 1 -- N/A for non-TDS vendors
END,
payment_status_id = CASE
WHEN v_is_tds_vendor THEN payment_status_id -- leave unchanged for TDS vendors
ELSE v_payment_status_id -- update for non-TDS vendors
END
WHERE id = v_bill_header_id;
END LOOP;
RAISE NOTICE 'Bill payment processed successfully';
END;
$procedure$
-- Procedure: create_draft_bill_detail
CREATE OR REPLACE PROCEDURE public.create_draft_bill_detail(IN p_bill_header_id uuid, IN p_price numeric, IN p_quantity numeric, IN p_discount numeric, IN p_fees numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_sgst_amount numeric, IN p_taxable_amount numeric, IN p_total_amount numeric, IN p_serial_number integer, IN p_product_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
p_id uuid;
BEGIN
-- Generate a new UUID for the detail entry
p_id := gen_random_uuid();
-- Insert into draft_bill_details table
INSERT INTO public.draft_bill_details(
id,
bill_header_id,
price,
quantity,
cgst_amount,
discount,
fees,
igst_amount,
sgst_amount,
taxable_amount,
total_amount,
serial_number,
product_id,
is_deleted
)
VALUES (
p_id,
p_bill_header_id,
p_price,
p_quantity,
p_cgst_amount,
p_discount,
p_fees,
p_igst_amount,
p_sgst_amount,
p_taxable_amount,
p_total_amount,
p_serial_number,
p_product_id,
false
);
-- Log the success
RAISE NOTICE 'Draft bill detail inserted with ID: %', p_id;
END;
$procedure$
-- Procedure: create_multiple_bill_payments
CREATE OR REPLACE PROCEDURE public.create_multiple_bill_payments(IN p_bill_payments jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
bill_payment RECORD;
payment_statuses TEXT[] := ARRAY[]::TEXT[];
BEGIN
FOR bill_payment IN
SELECT * FROM jsonb_to_recordset(p_bill_payments) AS (
bill_payment_header_id uuid,
company_id uuid,
vendor_id uuid,
paid_date date,
paid_amount numeric,
grand_total_amount numeric,
tds_amount numeric,
advance_amount numeric,
description text,
debit_account_id uuid,
credit_account_id uuid,
mode_of_payment text,
reference text,
created_by uuid,
detail_lines jsonb
)
LOOP
BEGIN
IF NOT EXISTS (
SELECT 1
FROM public.bill_payment_headers
WHERE reference = bill_payment.reference
AND company_id = bill_payment.company_id
) THEN
CALL public.create_bill_payment(
bill_payment.bill_payment_header_id,
bill_payment.company_id,
bill_payment.vendor_id,
bill_payment.paid_date,
bill_payment.mode_of_payment,
bill_payment.credit_account_id,
bill_payment.debit_account_id,
bill_payment.reference,
bill_payment.paid_amount,
bill_payment.grand_total_amount,
bill_payment.advance_amount,
bill_payment.description,
bill_payment.tds_amount,
bill_payment.created_by,
bill_payment.detail_lines
);
payment_statuses := payment_statuses || format('%s:success', bill_payment.bill_payment_header_id);
ELSE
RAISE NOTICE 'Duplicate bill payment found for reference: %, company_id: %. Skipping.', bill_payment.reference, bill_payment.company_id;
payment_statuses := payment_statuses || format('%s:duplicate', bill_payment.bill_payment_header_id);
CONTINUE;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred while processing bill payment ID: %, Error: %', bill_payment.bill_payment_header_id, SQLERRM;
payment_statuses := payment_statuses || format('%s:failed', bill_payment.bill_payment_header_id);
END;
END LOOP;
RAISE NOTICE 'BILL_PAYMENT_STATUS:%', to_json(payment_statuses);
RAISE NOTICE 'All bill payments processed.';
END;
$procedure$
-- Procedure: create_vendor_advance
CREATE OR REPLACE PROCEDURE public.create_vendor_advance(IN p_vendor_advance_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_paid_date date, IN p_mode_of_payment text, IN p_through_account_id uuid, IN p_reference text, IN p_amount numeric, IN p_description text, IN p_currency_id integer, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_advance_number varchar;
BEGIN
-- Generate a new vendor advance number for this company & date
v_advance_number := public.get_new_vendor_advance_number(p_company_id, p_paid_date);
-- Insert into vendor_advances
INSERT INTO vendor_advances
(
id,
company_id,
vendor_id,
advance_number,
paid_date,
amount,
mode_of_payment,
through_account_id,
reference,
description,
currency_id,
utilized_amount,
created_on_utc,
created_by
)
VALUES
(
p_vendor_advance_id,
p_company_id,
p_vendor_id,
v_advance_number,
p_paid_date,
p_amount,
p_mode_of_payment,
p_through_account_id,
NULLIF(p_reference, ''),
NULLIF(p_description, ''),
p_currency_id,
0.0, -- initialize utilized_amount
now(),
p_created_by
);
END;
$procedure$
-- Procedure: delete_bill_data_by_company_id
CREATE OR REPLACE PROCEDURE public.delete_bill_data_by_company_id(IN p_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Deleting from bill_payment_details
DELETE FROM public.bill_payment_details
USING public.bill_payment_headers
WHERE bill_payment_details.bill_payment_header_id = bill_payment_headers.id
AND bill_payment_headers.company_id = p_company_id;
-- Deleting from bill_payment_headers
DELETE FROM public.bill_payment_headers
WHERE company_id = p_company_id;
-- Deleting from bill_details
DELETE FROM public.bill_details
USING public.bill_headers
WHERE bill_details.bill_header_id = bill_headers.id
AND bill_headers.company_id = p_company_id;
-- Deleting from bill_headers
DELETE FROM public.bill_headers
WHERE company_id = p_company_id;
-- Deleting from draft_bill_details
DELETE FROM public.draft_bill_details
USING public.draft_bill_headers
WHERE draft_bill_details.bill_header_id = draft_bill_headers.id
AND draft_bill_headers.company_id = p_company_id;
-- Deleting from draft_bill_headers
DELETE FROM public.draft_bill_headers
WHERE company_id = p_company_id;
END;
$procedure$
-- Procedure: delete_vendor_notes_by_company
CREATE OR REPLACE PROCEDURE public.delete_vendor_notes_by_company(IN p_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Delete from vendor_note_details first to maintain referential integrity
DELETE FROM public.vendor_note_details
WHERE vendor_note_header_id IN (
SELECT id FROM public.vendor_note_headers WHERE company_id = p_company_id
);
-- Delete from vendor_note_headers
DELETE FROM public.vendor_note_headers
WHERE company_id = p_company_id;
RAISE NOTICE 'Data deleted successfully for company_id: %', p_company_id;
END;
$procedure$
-- Procedure: initialize_bill_header_ids
CREATE OR REPLACE PROCEDURE public.initialize_bill_header_ids(IN p_default_company_id uuid, IN p_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_header_exists BOOLEAN;
BEGIN
-- Check if bill header IDs already exist for the new company
SELECT EXISTS (
SELECT 1
FROM bill_header_ids
WHERE company_id = p_company_id
) INTO v_header_exists;
IF NOT v_header_exists THEN
-- Insert new records for the new company
INSERT INTO bill_header_ids (
id,
company_id,
fin_year,
bill_prefix,
bill_length,
last_bill_id
)
SELECT
nextval('bill_header_ids_id_seq'), -- Generate new sequential ID
p_company_id, -- Assign the new company ID
fin_year,
bill_prefix,
bill_length,
0
FROM bill_header_ids
WHERE company_id = p_default_company_id;
RAISE NOTICE 'Bill header IDs initialized successfully for new company ID: %', p_company_id;
ELSE
RAISE NOTICE 'Bill header IDs already exist for company ID: %. Skipping initialization.', p_company_id;
END IF;
END
$procedure$
-- Procedure: initialize_bill_payment_header_ids
CREATE OR REPLACE PROCEDURE public.initialize_bill_payment_header_ids(IN p_default_company_id uuid, IN p_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_header_exists BOOLEAN;
BEGIN
-- Check if bill payment header IDs already exist for the new company
SELECT EXISTS (
SELECT 1
FROM bill_payment_header_ids
WHERE company_id = p_company_id
) INTO v_header_exists;
IF NOT v_header_exists THEN
PERFORM setval(
'bill_payment_header_ids_id_seq', -- Sequence name
(SELECT COALESCE(MAX(id), 0) FROM bill_payment_header_ids), -- Current max ID in the table
true -- Ensure the sequence is ready to return the next value
);
-- Insert new records for the new company
INSERT INTO bill_payment_header_ids (
id,
company_id,
fin_year,
payment_prefix,
payment_length,
last_payment_id
)
SELECT
nextval('bill_payment_header_ids_id_seq'), -- Generate new sequential ID
p_company_id, -- Assign the new company ID
fin_year,
payment_prefix,
payment_length,
0
FROM bill_payment_header_ids
WHERE company_id = p_default_company_id;
RAISE NOTICE 'Bill payment header IDs initialized successfully for new company ID: %', p_company_id;
ELSE
RAISE NOTICE 'Bill payment header IDs already exist for company ID: %. Skipping initialization.', p_company_id;
END IF;
END
$procedure$
-- Procedure: initialize_bill_workflow
CREATE OR REPLACE PROCEDURE public.initialize_bill_workflow(IN p_default_company_id uuid, IN p_company_id uuid, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_workflow_exists BOOLEAN;
BEGIN
-- Check if bill workflow records already exist for the new company
SELECT EXISTS (
SELECT 1
FROM public.bill_workflow
WHERE company_id = p_company_id
) INTO v_workflow_exists;
IF NOT v_workflow_exists THEN
-- Insert new records for the new company
INSERT INTO public.bill_workflow (
id,
company_id,
status,
next_status,
previous_status,
is_initial,
created_on_utc,
created_by,
approval_level
)
SELECT
nextval('bill_workflow_id_seq'), -- Generate new sequential ID
p_company_id, -- Assign the new company ID
status,
next_status,
previous_status,
is_initial,
NOW(),
p_created_by, -- Use the provided created_by UUID
approval_level
FROM public.bill_workflow -- The approval_level from the default company
WHERE company_id = p_default_company_id;
-- Optional: Log the operation
RAISE NOTICE 'Bill workflow records have been copied from company % to company % by user %.', p_default_company_id, p_company_id, p_created_by;
ELSE
RAISE NOTICE 'Bill workflow records already exist for company %. Skipping initialization.', p_company_id;
END IF;
END;
$procedure$
-- Procedure: initialize_draft_bill_header_ids
CREATE OR REPLACE PROCEDURE public.initialize_draft_bill_header_ids(IN p_default_company_id uuid, IN p_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_header_exists BOOLEAN;
BEGIN
-- Check if draft bill header IDs already exist for the new company
SELECT EXISTS (
SELECT 1
FROM draft_bill_header_ids
WHERE company_id = p_company_id
) INTO v_header_exists;
IF NOT v_header_exists THEN
-- Insert new records for the new company
INSERT INTO draft_bill_header_ids (
id,
company_id,
fin_year,
bill_prefix,
bill_length,
last_bill_id
)
SELECT
nextval('draft_bill_header_ids_id_seq'),
p_company_id, -- Assign the new company ID
fin_year,
bill_prefix,
bill_length,
last_bill_id
FROM draft_bill_header_ids
WHERE company_id = p_default_company_id;
RAISE NOTICE 'Draft bill header IDs initialized successfully for new company ID: %', p_company_id;
ELSE
RAISE NOTICE 'Draft bill header IDs already exist for company ID: %. Skipping initialization.', p_company_id;
END IF;
END
$procedure$
-- Procedure: initialize_organization
CREATE OR REPLACE PROCEDURE public.initialize_organization(IN p_id uuid, IN p_name text, IN p_company_ids text, IN p_company_names text, IN p_user_id uuid, IN p_user_first_name text, IN p_user_last_name text, IN p_phone_number text, IN p_email text, IN p_created_by uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_id uuid; -- Variable for iterating through company IDs
v_company_name text; -- Variable for iterating through company names
v_company_ids uuid[]; -- Array to hold parsed company IDs
v_company_names text[]; -- Array to hold parsed company names
i integer; -- Iterator for looping
v_organization_exists boolean;
v_user_exists boolean;
BEGIN
-- Check if organization already exists by ID
SELECT EXISTS (
SELECT 1 FROM public.organizations WHERE id = p_id OR (id = p_id AND name = p_name)
) INTO v_organization_exists;
IF v_organization_exists THEN
RAISE NOTICE 'Organization with ID % already exists. Skipping initialization.', p_id;
ELSE
-- Insert into organizations table
INSERT INTO public.organizations (
id,
name,
created_on_utc,
created_by
) VALUES (
p_id,
p_name,
NOW(),
p_created_by
);
RAISE NOTICE 'Initialized organization: % with ID: %', p_name, p_id;
END IF;
-- Parse company IDs and names
v_company_ids := string_to_array(p_company_ids, ',');
v_company_names := string_to_array(p_company_names, ',');
-- Loop through each company and initialize
FOR i IN 1..array_length(v_company_ids, 1) LOOP
v_company_id := v_company_ids[i];
v_company_name := v_company_names[i];
-- Call initialize_company for each company
CALL public.initialize_company(
v_company_id, -- Company ID passed as parameter
p_id, -- Organization ID
v_company_name, -- Organization Name
true, -- Is Apartment
p_created_by, -- Created by user
p_default_company_id -- Old Company ID passed as parameter
);
END LOOP;
v_company_id := v_company_ids[1];
-- Check if user already exists by ID or Email before creating
SELECT EXISTS (
SELECT 1 FROM public.users WHERE id = p_user_id OR email = p_email
) INTO v_user_exists;
IF NOT v_user_exists THEN
-- Call create_user function to set up the user if they don't exist
PERFORM public.create_user(
p_user_id,
p_email,
p_phone_number,
p_user_first_name,
p_user_last_name,
p_created_by,
v_company_id
);
RAISE NOTICE 'Initialized user with ID: % and email: % in organization: %', p_user_id, p_email, p_name;
ELSE
UPDATE public.users
SET company_id = v_company_id
WHERE id = p_user_id OR email = p_email;
RAISE NOTICE 'User with ID % or email % already exists. Skipping user creation.', p_user_id, p_email;
END IF;
END;
$procedure$
-- Procedure: update_bill_previous_status
CREATE OR REPLACE PROCEDURE public.update_bill_previous_status(IN p_company_id uuid, IN p_bill_status_id integer, IN p_bill_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill RECORD;
v_minimum_status INTEGER := 0; -- Minimum allowed status
BEGIN
-- Process draft bills for previous status update
FOR v_bill IN
SELECT id, bill_status_id
FROM public.draft_bill_headers
WHERE id = ANY(p_bill_ids)
LOOP
-- Ensure the status doesn't go below the minimum allowed status
IF v_bill.bill_status_id > v_minimum_status THEN
-- Update the draft bill to the previous status
UPDATE public.draft_bill_headers
SET bill_status_id = p_bill_status_id,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_bill.id;
RAISE NOTICE 'Draft bill status updated to previous status for Bill ID: %', v_bill.id;
ELSE
RAISE NOTICE 'Cannot decrement status below the minimum allowed status for Draft Bill ID: %', v_bill.id;
END IF;
END LOOP;
-- Process approved bills for previous status update
FOR v_bill IN
SELECT id, bill_status_id
FROM public.bill_headers
WHERE id = ANY(p_bill_ids)
LOOP
-- Ensure the status doesn't go below the minimum allowed status
IF v_bill.bill_status_id > v_minimum_status THEN
-- Update the bill to the previous status
UPDATE public.bill_headers
SET bill_status_id = p_bill_status_id,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_bill.id;
RAISE NOTICE 'Bill status updated to previous status for Bill ID: %', v_bill.id;
ELSE
RAISE NOTICE 'Cannot decrement status below the minimum allowed status for Bill ID: %', v_bill.id;
END IF;
END LOOP;
END
$procedure$
-- Procedure: save_company_prefix_for_bill_and_payment
CREATE OR REPLACE PROCEDURE public.save_company_prefix_for_bill_and_payment(IN p_company_id uuid, IN p_fin_year integer, IN p_bill_prefix text, IN p_bill_length integer, IN p_draft_bill_prefix text, IN p_draft_bill_length integer, IN p_payment_prefix text, IN p_payment_length integer)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Handle bill_header_ids
IF EXISTS (SELECT 1 FROM public.bill_header_ids WHERE company_id = p_company_id AND fin_year = p_fin_year) THEN
UPDATE public.bill_header_ids
SET
bill_prefix = COALESCE(p_bill_prefix, bill_prefix),
bill_length = COALESCE(p_bill_length, bill_length)
WHERE company_id = p_company_id AND fin_year = p_fin_year;
ELSE
INSERT INTO public.bill_header_ids (id, company_id, fin_year, bill_prefix, bill_length, last_bill_id)
VALUES ((SELECT COALESCE(MAX(id), 0) + 1 FROM bill_header_ids), p_company_id, p_fin_year, p_bill_prefix, p_bill_length, 0);
END IF;
-- Handle draft_bill_header_ids
IF EXISTS (SELECT 1 FROM public.draft_bill_header_ids WHERE company_id = p_company_id AND fin_year = p_fin_year) THEN
UPDATE public.draft_bill_header_ids
SET
bill_prefix = COALESCE(p_draft_bill_prefix, bill_prefix),
bill_length = COALESCE(p_draft_bill_length, bill_length)
WHERE company_id = p_company_id AND fin_year = p_fin_year;
ELSE
INSERT INTO public.draft_bill_header_ids (id, company_id, fin_year, bill_prefix, bill_length, last_bill_id)
VALUES ((SELECT COALESCE(MAX(id), 0) + 1 FROM draft_bill_header_ids), p_company_id, p_fin_year, p_draft_bill_prefix, p_draft_bill_length, 0);
END IF;
-- Handle bill_payment_header_ids
IF EXISTS (SELECT 1 FROM public.bill_payment_header_ids WHERE company_id = p_company_id AND fin_year = p_fin_year) THEN
UPDATE public.bill_payment_header_ids
SET
payment_prefix = COALESCE(p_payment_prefix, payment_prefix),
payment_length = COALESCE(p_payment_length, payment_length)
WHERE company_id = p_company_id AND fin_year = p_fin_year;
ELSE
INSERT INTO public.bill_payment_header_ids (id, company_id, fin_year, payment_prefix, payment_length, last_payment_id)
VALUES ((SELECT COALESCE(MAX(id), 0) + 1 FROM bill_payment_header_ids), p_company_id, p_fin_year, p_payment_prefix, p_payment_length, 0);
END IF;
END;
$procedure$
-- Procedure: update_account_level_approval_configuration
CREATE OR REPLACE PROCEDURE public.update_account_level_approval_configuration(IN p_company_id uuid, IN p_account_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
approval_item JSONB;
existing_id INT;
v_status_id INT;
BEGIN
-- 1️⃣ Soft delete old records not present in incoming approvals:
UPDATE bill_approval_user_account
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND account_id = p_account_id
AND NOT EXISTS (
SELECT 1
FROM jsonb_array_elements(p_approvals) AS elem
WHERE (elem->>'userId')::UUID = bill_approval_user_account.user_id
AND (elem->>'approvalLevel')::INT = bill_approval_user_account.approval_level
AND (elem->>'statusId')::INT = bill_approval_user_account.status_id
);
-- 2️⃣ Upsert incoming approvals:
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
LOOP
SELECT id INTO existing_id
FROM bill_approval_user_account
WHERE company_id = p_company_id
AND account_id = p_account_id
AND user_id = (approval_item->>'userId')::UUID
AND approval_level = (approval_item->>'approvalLevel')::INT
AND status_id = (approval_item->>'statusId')::INT
AND is_deleted = FALSE
LIMIT 1;
IF existing_id IS NOT NULL THEN
-- -- UPDATE bill_approval_user_account
-- SET status_id = (approval_item->>'statusId')::INT,
-- modified_on_utc = NOW(),
-- modified_by = p_modified_by
-- WHERE id = existing_id;
RAISE NOTICE 'existing IDs: %', existing_id;
ELSE
INSERT INTO bill_approval_user_account (
company_id,
account_id,
status_id,
user_id,
approval_level,
is_deleted,
created_on_utc,
created_by
)
VALUES (
p_company_id,
p_account_id,
(approval_item->>'statusId')::INT,
(approval_item->>'userId')::UUID,
(approval_item->>'approvalLevel')::INT,
FALSE,
NOW(),
p_modified_by
);
END IF;
END LOOP;
-- 3️⃣ Upsert bill_account_approval_levels:
IF EXISTS (
SELECT 1
FROM bill_account_approval_levels
WHERE company_id = p_company_id
AND account_id = p_account_id
AND status_id = 2 -- temparary updating only for status 2
) THEN
UPDATE bill_account_approval_levels
SET approval_level_required = p_required_approval_levels,
status_id = 2,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND account_id = p_account_id
AND status_id = 2; -- temparary checking for only status 2
ELSE
INSERT INTO bill_account_approval_levels (
company_id,
account_id,
status_id,
approval_level_required,
created_on_utc,
created_by
)
VALUES (
p_company_id,
p_account_id,
2,
p_required_approval_levels,
NOW(),
p_modified_by
);
END IF;
END;
$procedure$
-- Procedure: transfer_draft_to_bill
CREATE OR REPLACE PROCEDURE public.transfer_draft_to_bill(IN p_draft_bill_id uuid, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
draft_bill RECORD;
draft_bill_details RECORD;
BEGIN
-- Fetch the draft bill header
SELECT * INTO draft_bill
FROM public.draft_bill_headers
WHERE id = p_draft_bill_id;
IF draft_bill IS NULL THEN
RAISE EXCEPTION 'Draft bill not found: %', p_draft_bill_id;
END IF;
-- Call to create bill header using the procedure
CALL public.create_bill_header(
draft_bill.id,
draft_bill.company_id,
draft_bill.vendor_id,
draft_bill.credit_account_id,
draft_bill.debit_account_id,
draft_bill.bill_date::date,
draft_bill.due_date::date,
draft_bill.payment_term,
draft_bill.taxable_amount,
draft_bill.cgst_amount,
draft_bill.sgst_amount,
draft_bill.igst_amount,
draft_bill.tds_amount,
draft_bill.total_amount,
draft_bill.discount,
draft_bill.fees,
draft_bill.round_off,
draft_bill.round_off_tds,
draft_bill.currency_id,
draft_bill.note,
draft_bill.bill_status_id,
draft_bill.created_by,
draft_bill.source_warehouse_id,
draft_bill.destination_warehouse_id,
draft_bill.bill_type_id,
draft_bill.bill_number,
draft_bill.po_no,
draft_bill.po_date::date
);
-- Raise a notice to indicate success
RAISE NOTICE 'Bill header inserted with ID: %', draft_bill.id;
-- Fetch and insert bill details
FOR draft_bill_details IN
SELECT * FROM public.draft_bill_details
WHERE bill_header_id = p_draft_bill_id
LOOP
INSERT INTO public.bill_details(
id, bill_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount, total_amount, serial_number)
VALUES(
draft_bill_details.id, draft_bill_details.bill_header_id,
draft_bill_details.product_id, draft_bill_details.price,
draft_bill_details.quantity, draft_bill_details.fees,
draft_bill_details.discount, draft_bill_details.taxable_amount,
draft_bill_details.sgst_amount, draft_bill_details.cgst_amount,
draft_bill_details.igst_amount, draft_bill_details.total_amount,
draft_bill_details.serial_number
);
UPDATE public.draft_bill_details
SET
is_deleted = TRUE,
deleted_on_utc = now()
WHERE bill_header_id = p_draft_bill_id;
END LOOP;
-- Mark the draft bill as deleted
UPDATE public.draft_bill_headers
SET
is_deleted = TRUE,
deleted_on_utc = now()
WHERE id = p_draft_bill_id;
END;
$procedure$
-- Procedure: update_bill_next_status
CREATE OR REPLACE PROCEDURE public.update_bill_next_status(IN p_company_id uuid, IN p_bill_status_id integer, IN p_bill_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_draft_bill RECORD;
v_bill RECORD;
next_status INTEGER; -- To store the next status for bills
APPROVED_STATUS CONSTANT INTEGER := 3; -- Status for Approved
BEGIN
-- Process draft bills
FOR v_draft_bill IN
SELECT id, bill_status_id
FROM public.draft_bill_headers
WHERE id = ANY(p_bill_ids)
LOOP
-- Determine the next status based on the provided status or workflow
IF p_bill_status_id IS NOT NULL THEN
next_status := p_bill_status_id; -- Use the provided status
ELSE
SELECT bw.next_status
INTO next_status
FROM public.bill_workflow bw
WHERE bw.company_id = p_company_id
AND bw.status = v_draft_bill.bill_status_id;
END IF;
-- Update the draft bill status
UPDATE public.draft_bill_headers
SET bill_status_id = next_status,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_draft_bill.id;
-- If the next status is 'APPROVED', transfer draft to final bill
IF next_status = APPROVED_STATUS THEN
-- Transfer draft to final bill
CALL transfer_draft_to_bill(v_draft_bill.id, p_modified_by);
-- Output when the draft bill is approved
RAISE NOTICE 'Draft Bill Approved. Transferring to Final Bill: %', v_draft_bill.id;
END IF;
END LOOP;
-- Process bills
FOR v_bill IN
SELECT id, bill_status_id
FROM public.bill_headers
WHERE id = ANY(p_bill_ids)
LOOP
-- Determine the next status based on the provided status or workflow
IF p_bill_status_id IS NOT NULL THEN
next_status := p_bill_status_id; -- Use the provided status
ELSE
SELECT bw.next_status
INTO next_status
FROM public.bill_workflow bw
WHERE bw.company_id = p_company_id
AND bw.status = v_bill.bill_status_id;
END IF;
-- Update the bill status
UPDATE public.bill_headers
SET bill_status_id = next_status,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_bill.id;
-- Output the updated bill
RAISE NOTICE 'Bill ID: %, Current Status: %, Updated to Next Status: %',
v_bill.id, v_bill.bill_status_id, next_status;
END LOOP;
END
$procedure$
-- Procedure: update_bill_next_status_for_bill_header
CREATE OR REPLACE PROCEDURE public.update_bill_next_status_for_bill_header(IN p_company_id uuid, IN p_bill_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill RECORD;
v_account_id uuid;
v_next_status integer;
v_next_status_name character varying;
v_issue TEXT;
v_next_temp_id int;
BEGIN
RAISE NOTICE 'START update_bill_next_status_for_bill_header for company_id=%, modified_by=%', p_company_id, p_modified_by;
RAISE NOTICE 'Incoming bill_ids: %', p_bill_ids;
FOR v_bill IN
SELECT id, bill_status_id, debit_account_id
FROM public.bill_headers
WHERE id = ANY(p_bill_ids)
LOOP
RAISE NOTICE 'Processing bill ID: %, current status: %', v_bill.id, v_bill.bill_status_id;
v_account_id := v_bill.debit_account_id;
-- Resolve next status from workflow
SELECT next_status INTO v_next_status
FROM public.bill_workflow
WHERE company_id = p_company_id
AND status = v_bill.bill_status_id
AND is_deleted = false
AND is_enabled = true
LIMIT 1;
RAISE NOTICE 'Resolved next_status for bill ID %: %', v_bill.id, v_next_status;
IF v_next_status IS NULL THEN
v_issue := 'Next status not found for company or status';
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
VALUES (v_bill.id, v_issue, now(), p_modified_by);
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, 'Not found', v_issue);
RAISE NOTICE 'Bill ID % skipped: next status not found', v_bill.id;
CONTINUE;
END IF;
-- 🔔 Permission check using FUNCTION instead of EXISTS
PERFORM public.check_bill_approval_permissions(
p_company_id,
v_next_status,
p_modified_by,
v_account_id,
0
);
IF NOT FOUND THEN
v_issue := 'User not authorized to move bill to next status';
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
VALUES (v_bill.id, v_issue, now(), p_modified_by);
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, 'User not authorized', v_issue);
RAISE NOTICE 'Bill ID % skipped: user not authorized', v_bill.id;
CONTINUE;
END IF;
-- Move to next status
UPDATE public.bill_headers
SET bill_status_id = v_next_status,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_bill.id;
RAISE NOTICE 'Bill ID % updated to next_status %', v_bill.id, v_next_status;
-- Insert approval log
INSERT INTO public.bill_approval_logs(
bill_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
VALUES (
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
'Approved and moved to next status',
now(), p_modified_by, 0
);
RAISE NOTICE 'Approval log inserted for bill ID %', v_bill.id;
-- Resolve next status name
SELECT name INTO v_next_status_name
FROM public.bill_statuses
WHERE id = v_next_status;
RAISE NOTICE 'Next status name for bill ID %: %', v_bill.id, v_next_status_name;
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, v_next_status_name, NULL);
RAISE NOTICE 'Result inserted into temp_bill_next_status for bill ID %', v_bill.id;
END LOOP;
RAISE NOTICE 'END update_bill_next_status_for_bill_header';
END
$procedure$
-- Procedure: upsert_bill_status_company_config_json
CREATE OR REPLACE PROCEDURE public.upsert_bill_status_company_config_json(IN p_updates jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_item JSONB;
BEGIN
FOR v_item IN SELECT * FROM jsonb_array_elements(p_updates)
LOOP
PERFORM public.upsert_bill_status_company_config(
(v_item->>'CompanyId')::UUID,
(v_item->>'StatusId')::INT,
(v_item->>'IsEnabled')::BOOLEAN,
(v_item->>'UserId')::UUID
);
END LOOP;
END;
$procedure$
-- Procedure: clean_up_org_purchase
CREATE OR REPLACE PROCEDURE public.clean_up_org_purchase(IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_id uuid;
v_vendor_id uuid;
v_bill_header_id uuid;
v_bill_payment_header_id uuid;
v_vendor_note_header_id uuid;
v_gate_pass_header_id uuid;
v_user_id uuid;
BEGIN
FOR v_company_id IN SELECT id FROM companies WHERE organization_id = p_organization_id LOOP
-- Vendors and their children
FOR v_vendor_id IN SELECT id FROM vendors WHERE company_id = v_company_id LOOP
DELETE FROM vendor_contacts WHERE vendor_id = v_vendor_id;
DELETE FROM vendor_bank_accounts WHERE vendor_id = v_vendor_id;
DELETE FROM vendor_upis WHERE vendor_id = v_vendor_id;
DELETE FROM vendor_default_accounts WHERE vendor_id = v_vendor_id;
FOR v_vendor_note_header_id IN SELECT id FROM vendor_note_headers WHERE vendor_id = v_vendor_id LOOP
DELETE FROM vendor_note_details WHERE vendor_note_header_id = v_vendor_note_header_id;
END LOOP;
DELETE FROM vendor_note_headers WHERE vendor_id = v_vendor_id;
-- If you want to delete warehouses for this vendor:
-- DELETE FROM warehouses WHERE vendor_id = v_vendor_id;
END LOOP;
DELETE FROM vendors WHERE company_id = v_company_id;
DELETE FROM vendor_note_work_flows WHERE company_id = v_company_id;
DELETE FROM vendor_note_statuses WHERE company_id = v_company_id;
-- Bills and their children
FOR v_bill_header_id IN SELECT id FROM bill_headers WHERE company_id = v_company_id LOOP
DELETE FROM bill_details WHERE bill_header_id = v_bill_header_id;
DELETE FROM bill_approval_logs WHERE bill_id = v_bill_header_id;
DELETE FROM bill_approval_issue_logs WHERE bill_id = v_bill_header_id;
END LOOP;
DELETE FROM bill_headers WHERE company_id = v_company_id;
DELETE FROM bill_header_ids WHERE company_id = v_company_id;
DELETE FROM bill_workflow WHERE company_id = v_company_id;
DELETE FROM bill_status_company_configs WHERE company_id = v_company_id;
DELETE FROM bill_account_approval_levels WHERE company_id = v_company_id;
DELETE FROM bill_approval_user_account WHERE company_id = v_company_id;
DELETE FROM bill_approval_user_company WHERE company_id = v_company_id;
-- Bill payments
FOR v_bill_payment_header_id IN SELECT id FROM bill_payment_headers WHERE company_id = v_company_id LOOP
DELETE FROM bill_payment_details WHERE bill_payment_header_id = v_bill_payment_header_id;
END LOOP;
DELETE FROM bill_payment_headers WHERE company_id = v_company_id;
DELETE FROM bill_payment_header_ids WHERE company_id = v_company_id;
-- Draft bills
FOR v_bill_header_id IN SELECT id FROM draft_bill_headers WHERE company_id = v_company_id LOOP
DELETE FROM draft_bill_details WHERE bill_header_id = v_bill_header_id;
END LOOP;
DELETE FROM draft_bill_headers WHERE company_id = v_company_id;
DELETE FROM draft_bill_header_ids WHERE company_id = v_company_id;
-- Recurring bills
DELETE FROM recurring_bill_schedules WHERE company_id = v_company_id;
-- Gate pass and details
FOR v_gate_pass_header_id IN SELECT id FROM gate_pass_headers WHERE company_id = v_company_id LOOP
DELETE FROM gate_pass_details WHERE gate_pass_header_id = v_gate_pass_header_id;
END LOOP;
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
-- Users & their roles
FOR v_user_id IN SELECT id FROM users WHERE company_id = v_company_id LOOP
DELETE FROM user_roles WHERE user_id = v_user_id;
END LOOP;
DELETE FROM users WHERE company_id = v_company_id;
-- Finally, company itself
DELETE FROM companies WHERE id = v_company_id;
END LOOP;
-- Organization record itself
DELETE FROM organizations WHERE id = p_organization_id;
RAISE NOTICE 'Organization % and all related purchase data deleted.', p_organization_id;
END;
$procedure$
-- Procedure: copy_bills
CREATE OR REPLACE PROCEDURE public.copy_bills(IN p_bill_ids uuid[])
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_header RECORD;
v_bill_detail RECORD;
v_new_header_id UUID;
-- Constants for statuses and other values
bill_status_draft CONSTANT INTEGER := 1;
approved_bill_status CONSTANT INTEGER := 3;
BEGIN
-- Loop through each provided bill ID
FOR v_bill_header IN
(
SELECT id, company_id, vendor_id, debit_account_id, credit_account_id, bill_date, due_date,
payment_term, taxable_amount, cgst_amount, sgst_amount, igst_amount, tds_amount, total_amount,
discount, fees, round_off, currency_id, note, created_by, source_warehouse_id,
destination_warehouse_id, po_no, po_date, bill_type_id, bill_number, bill_status_id
FROM draft_bill_headers
WHERE id = ANY(p_bill_ids) AND is_deleted = false
UNION ALL
SELECT id, company_id, vendor_id, debit_account_id, credit_account_id, bill_date, due_date,
payment_term, taxable_amount, cgst_amount, sgst_amount, igst_amount, tds_amount, total_amount,
discount, fees, round_off, currency_id, note, created_by, source_warehouse_id,
destination_warehouse_id, po_no, po_date, bill_type_id, bill_number, bill_status_id
FROM bill_headers
WHERE id = ANY(p_bill_ids) AND is_deleted = false
)
LOOP
RAISE NOTICE 'Processing Bill ID: %', v_bill_header.id;
-- Generate a new UUID for the new bill header
v_new_header_id := gen_random_uuid();
RAISE NOTICE 'Generated new bill header ID: %', v_new_header_id;
-- Insert new draft bill header
BEGIN
CALL create_draft_bill_header(
v_new_header_id,
v_bill_header.company_id,
v_bill_header.vendor_id,
v_bill_header.debit_account_id,
v_bill_header.credit_account_id,
v_bill_header.bill_date::date,
v_bill_header.due_date::date,
v_bill_header.payment_term,
v_bill_header.taxable_amount,
v_bill_header.cgst_amount,
v_bill_header.sgst_amount,
v_bill_header.igst_amount,
v_bill_header.tds_amount,
v_bill_header.total_amount,
v_bill_header.discount,
v_bill_header.fees,
v_bill_header.round_off,
v_bill_header.currency_id,
v_bill_header.note,
bill_status_draft,
v_bill_header.created_by,
v_bill_header.source_warehouse_id,
v_bill_header.destination_warehouse_id,
v_bill_header.bill_type_id,
v_bill_header.bill_number,
v_bill_header.po_no,
v_bill_header.po_date::date -- Explicitly cast to date
);
RAISE NOTICE 'Inserted new bill header for original bill ID: %', v_bill_header.id;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error inserting draft bill header for bill ID %: %', v_bill_header.id, SQLERRM;
END;
-- Copy bill details
BEGIN
IF v_bill_header.bill_status_id < approved_bill_status THEN
FOR v_bill_detail IN
SELECT * FROM draft_bill_details WHERE bill_header_id = v_bill_header.id
LOOP
CALL create_draft_bill_detail(
v_new_header_id,
v_bill_detail.price,
v_bill_detail.quantity,
v_bill_detail.discount,
v_bill_detail.fees,
v_bill_detail.cgst_amount,
v_bill_detail.igst_amount,
v_bill_detail.sgst_amount,
v_bill_detail.taxable_amount,
v_bill_detail.total_amount,
v_bill_detail.serial_number,
v_bill_detail.product_id
);
RAISE NOTICE 'Copied draft bill detail for new bill ID: %', v_new_header_id;
END LOOP;
ELSE
FOR v_bill_detail IN
SELECT * FROM bill_details WHERE bill_header_id = v_bill_header.id
LOOP
CALL create_draft_bill_detail(
v_new_header_id,
v_bill_detail.price,
v_bill_detail.quantity,
v_bill_detail.discount,
v_bill_detail.fees,
v_bill_detail.cgst_amount,
v_bill_detail.igst_amount,
v_bill_detail.sgst_amount,
v_bill_detail.taxable_amount,
v_bill_detail.total_amount,
v_bill_detail.serial_number,
v_bill_detail.product_id
);
RAISE NOTICE 'Copied bill detail for new bill ID: %', v_new_header_id;
END LOOP;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Error copying bill details for bill ID %: %', v_bill_header.id, SQLERRM;
END;
END LOOP;
RAISE NOTICE 'Copy bills procedure completed successfully.';
END;
$procedure$
-- Procedure: hard_delete_org_purchase
CREATE OR REPLACE PROCEDURE public.hard_delete_org_purchase(IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_id uuid;
BEGIN
FOR v_company_id IN SELECT id FROM companies WHERE organization_id = p_organization_id LOOP
DELETE FROM bill_workflow WHERE company_id = v_company_id;
DELETE FROM bill_payment_header_ids WHERE company_id = v_company_id;
DELETE FROM bill_payment_headers WHERE company_id = v_company_id;
DELETE FROM bill_header_ids WHERE company_id = v_company_id;
DELETE FROM bill_headers WHERE company_id = v_company_id;
DELETE FROM draft_bill_header_ids WHERE company_id = v_company_id;
DELETE FROM draft_bill_headers WHERE company_id = v_company_id;
DELETE FROM recurring_bill_schedules WHERE company_id = v_company_id;
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
DELETE FROM vendor_note_work_flows WHERE company_id = v_company_id;
DELETE FROM vendor_note_statuses WHERE company_id = v_company_id;
DELETE FROM vendor_note_headers WHERE company_id = v_company_id;
DELETE FROM vendor_categories WHERE company_id = v_company_id;
DELETE FROM vendors WHERE company_id = v_company_id;
DELETE FROM users WHERE company_id = v_company_id;
DELETE FROM companies WHERE id = v_company_id;
END LOOP;
DELETE FROM organizations WHERE id = p_organization_id;
RAISE NOTICE 'Deleted purchase data for organization_id: %', p_organization_id;
END;
$procedure$
-- Procedure: hard_delete_organization
CREATE OR REPLACE PROCEDURE public.hard_delete_organization(IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
p_company_id UUID; -- Declare a variable for company IDs
BEGIN
-- Loop through the associated company IDs for the organization
FOR p_company_id IN
SELECT c.id FROM public.companies c WHERE c.organization_id = p_organization_id
LOOP
-- Delete from bill_payment_header_ids (cascading through companies)
DELETE FROM bill_payment_header_ids
WHERE bill_payment_header_ids.company_id = p_company_id;
-- Delete from bill_header_ids (cascading through companies)
DELETE FROM bill_header_ids
WHERE bill_header_ids.company_id = p_company_id;
-- Delete from draft_bill_header_ids (cascading through companies)
DELETE FROM draft_bill_header_ids
WHERE draft_bill_header_ids.company_id = p_company_id;
-- Delete from bill_workflow (cascading through companies)
DELETE FROM bill_workflow
WHERE bill_workflow.company_id = p_company_id;
-- Delete users associated with the company
DELETE FROM public.users
WHERE users.company_id = p_company_id;
-- Delete from companies (for the current company_id in the loop)
DELETE FROM public.companies
WHERE public.companies.id = p_company_id;
END LOOP;
-- Finally, delete the organization itself
DELETE FROM public.organizations
WHERE public.organizations.id = p_organization_id;
-- Log the operation
RAISE NOTICE 'Organization with ID % and all related data have been hard deleted.', p_organization_id;
END;
$procedure$
-- Procedure: initialize_company
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_company_id uuid, IN p_org_id uuid, IN p_company_name text, IN p_is_apartment boolean, IN p_created_by uuid, IN p_default_company_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_exists boolean;
BEGIN
-- Check if company already exists
SELECT EXISTS (
SELECT 1 FROM public.companies WHERE id = p_company_id
) INTO v_company_exists;
IF NOT v_company_exists THEN
-- Insert into companies table
INSERT INTO public.companies (
id,
organization_id,
name,
is_apartment,
created_on_utc,
created_by
) VALUES (
p_company_id,
p_org_id,
p_company_name,
p_is_apartment,
NOW(),
p_created_by
);
RAISE NOTICE 'Company with ID % created.', p_company_id;
ELSE
RAISE NOTICE 'Company with ID % already exists. Skipping initialization.', p_company_id;
END IF;
CALL public.initialize_bill_workflow(
p_default_company_id,
p_company_id,
p_created_by
);
CALL public.initialize_draft_bill_header_ids(
p_default_company_id,
p_company_id
);
CALL public.initialize_bill_header_ids(
p_default_company_id,
p_company_id
);
CALL public.initialize_bill_payment_header_ids(
p_default_company_id,
p_company_id
);
END;
$procedure$
-- Procedure: log_bill_approval
CREATE OR REPLACE PROCEDURE public.log_bill_approval(IN p_bill_id uuid, IN p_next_status integer, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
APPROVED_STATUS CONSTANT INTEGER := 3;
FIRST_LEVEL_APPROVAL CONSTANT INTEGER := 8;
SECOND_LEVEL_APPROVAL CONSTANT INTEGER := 9;
BEGIN
IF p_next_status = APPROVED_STATUS OR
p_next_status = FIRST_LEVEL_APPROVAL OR
p_next_status = SECOND_LEVEL_APPROVAL THEN
INSERT INTO public.bill_approval_logs(
bill_id,
status_id,
approved_by,
approved_on,
"comment",
created_on_utc,
created_by
)
VALUES(
p_bill_id,
p_next_status,
p_modified_by,
NOW(),
CASE
WHEN p_next_status = FIRST_LEVEL_APPROVAL THEN 'Level 1 Approval'
WHEN p_next_status = SECOND_LEVEL_APPROVAL THEN 'Level 2 Approval'
WHEN p_next_status = APPROVED_STATUS THEN 'Final Approval'
ELSE 'Status updated'
END,
NOW(),
p_modified_by
);
RAISE NOTICE 'Approval logged for bill %, status %', p_bill_id, p_next_status;
END IF;
END;
$procedure$
-- Procedure: update_bill_account_approval_user
CREATE OR REPLACE PROCEDURE public.update_bill_account_approval_user(IN p_updates jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
update_item jsonb;
record_exists int;
v_company_id uuid;
v_account_id uuid;
v_status_id int;
v_user_id uuid;
v_modified_by uuid;
v_approval_level int;
v_company_level int;
BEGIN
-- Loop over each item in the JSON array
FOR update_item IN SELECT * FROM jsonb_array_elements(p_updates)
LOOP
-- Extract values
v_company_id := (update_item->>'CompanyId')::uuid;
v_account_id := (update_item->>'AccountId')::uuid;
v_status_id := (update_item->>'StatusId')::int;
v_user_id := (update_item->>'UserId')::uuid;
v_modified_by := (update_item->>'ModifiedBy')::uuid;
v_approval_level := COALESCE((update_item->>'ApprovalLevel')::int, 0);
-- Check if record exists
SELECT COUNT(*) INTO record_exists
FROM public.bill_approval_user_account
WHERE id = (update_item->>'Id')::int;
IF record_exists > 0 THEN
-- Update existing record
UPDATE public.bill_approval_users_account
SET
company_id = v_company_id,
account_id = v_account_id,
status_id = v_status_id,
user_id = v_user_id,
approval_level = v_approval_level,
modified_by = v_modified_by,
modified_on_utc = now()
WHERE
id = (update_item->>'Id')::int
AND is_deleted = false;
ELSE
-- Insert new record
INSERT INTO public.bill_approval_user_account (
company_id, account_id, status_id, user_id,
approval_level, created_by, created_on_utc,
modified_by, modified_on_utc, is_deleted
) VALUES (
v_company_id, v_account_id, v_status_id, v_user_id,
v_approval_level, v_modified_by, now(),
v_modified_by, now(), false
);
END IF;
-- Get company-level approval level
SELECT iw.approval_level INTO v_company_level
FROM public.bill_workflow iw
WHERE iw.company_id = v_company_id
AND iw.status = v_status_id
AND iw.is_deleted = false
LIMIT 1;
-- If different, upsert into bill_account_approval_levels
IF v_company_level IS DISTINCT FROM v_approval_level THEN
IF EXISTS (
SELECT 1 FROM public.bill_account_approval_levels
WHERE company_id = v_company_id
AND account_id = v_account_id
AND status_id = v_status_id
AND is_deleted = false
) THEN
UPDATE public.bill_account_approval_levels
SET approval_level_required = v_approval_level,
modified_on_utc = now(),
modified_by = v_modified_by
WHERE company_id = v_company_id
AND account_id = v_account_id
AND status_id = v_status_id
AND is_deleted = false;
ELSE
INSERT INTO public.bill_account_approval_levels (
company_id, account_id, status_id, approval_level_required,
created_on_utc, modified_on_utc, deleted_on_utc,
is_deleted, created_by, modified_by
) VALUES (
v_company_id, v_account_id, v_status_id, v_approval_level,
now(), NULL, NULL,
false, v_modified_by, v_modified_by
);
END IF;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: update_company_level_approval_configuration
CREATE OR REPLACE PROCEDURE public.update_company_level_approval_configuration(IN p_company_id uuid, IN p_status_id integer, IN p_required_approval_levels integer, IN p_approvals jsonb, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
approval_item JSONB;
existing_id INT;
upsert_count INT := 0;
delete_count INT := 0;
BEGIN
RAISE NOTICE 'Starting update_company_level_approval_configuration for company %, status %', p_company_id, p_status_id;
RAISE NOTICE 'Required approval levels: %', p_required_approval_levels;
RAISE NOTICE 'Approvals payload: %', p_approvals;
-- 1️⃣ Soft delete old records not present in incoming approvals:
UPDATE bill_approval_user_company
SET is_deleted = TRUE,
deleted_on_utc = NOW(),
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE company_id = p_company_id
AND is_deleted = FALSE
AND NOT EXISTS (
SELECT 1
FROM jsonb_array_elements(p_approvals) AS elem
WHERE (elem->>'userId')::UUID = bill_approval_user_company.user_id
AND (elem->>'statusId')::INT = bill_approval_user_company.status_id
AND (elem->>'approvalLevel')::INT = bill_approval_user_company.approval_level
);
GET DIAGNOSTICS delete_count = ROW_COUNT;
RAISE NOTICE 'Soft deleted % records not present in payload', delete_count;
-- 2️⃣ Upsert incoming approvals:
FOR approval_item IN SELECT * FROM jsonb_array_elements(p_approvals)
LOOP
SELECT id INTO existing_id
FROM bill_approval_user_company
WHERE company_id = p_company_id
AND user_id = (approval_item->>'userId')::UUID
AND status_id = (approval_item->>'statusId')::INT
AND approval_level = (approval_item->>'approvalLevel')::INT
AND is_deleted = FALSE
LIMIT 1;
IF existing_id IS NOT NULL THEN
UPDATE bill_approval_user_company
SET status_id = (approval_item->>'statusId')::INT,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = existing_id;
RAISE NOTICE 'Updated approval for user %, level %, status %', (approval_item->>'userId'), (approval_item->>'approvalLevel'), (approval_item->>'statusId');
ELSE
INSERT INTO bill_approval_user_company (
company_id,
status_id,
user_id,
approval_level,
is_deleted,
created_on_utc,
created_by
)
VALUES (
p_company_id,
(approval_item->>'statusId')::INT,
(approval_item->>'userId')::UUID,
(approval_item->>'approvalLevel')::INT,
FALSE,
NOW(),
p_modified_by
);
RAISE NOTICE 'Inserted approval for user %, level %, status %', (approval_item->>'userId'), (approval_item->>'approvalLevel'), (approval_item->>'statusId');
END IF;
upsert_count := upsert_count + 1;
END LOOP;
RAISE NOTICE 'Upserted % approval records', upsert_count;
-- 3️⃣ Update workflow approval level for the given status:
UPDATE bill_workflow
SET approval_level = p_required_approval_levels
WHERE company_id = p_company_id
AND status = p_status_id;
RAISE NOTICE 'Updated bill_workflow approval_level to % for company % and status %', p_required_approval_levels, p_company_id, p_status_id;
RAISE NOTICE 'Completed update_company_level_approval_configuration for company %', p_company_id;
END;
$procedure$
-- Procedure: purge_purchase_organization_data
CREATE OR REPLACE PROCEDURE public.purge_purchase_organization_data(IN p_organization_ids uuid[] DEFAULT NULL::uuid[])
LANGUAGE plpgsql
AS $procedure$
DECLARE
-- Retention policy
c_retention_hours integer := 24;
v_cutoff_24h timestamp := now() - make_interval(hours => GREATEST(c_retention_hours, 1));
-- Targets
v_orgs uuid[];
v_companies uuid[] := '{}';
-- Vendors and related
v_vendor_ids uuid[] := '{}';
-- Warehouses and their addresses
v_warehouse_ids uuid[] := '{}';
v_warehouse_address_ids uuid[] := '{}';
-- Bills (approved/final)
v_bill_header_ids uuid[] := '{}';
v_bill_detail_ids uuid[] := '{}';
-- Draft bills
v_draft_bill_header_ids uuid[] := '{}';
v_draft_bill_detail_ids uuid[] := '{}';
-- Payments
v_bill_payment_header_ids uuid[] := '{}';
v_bill_payment_detail_ids uuid[] := '{}';
-- Recurring schedules
v_recurring_schedule_ids uuid[] := '{}';
-- Gate passes
v_gate_pass_header_ids uuid[] := '{}';
v_gate_pass_detail_ids uuid[] := '{}';
-- Vendor notes
v_vendor_note_header_ids uuid[] := '{}';
v_vendor_note_detail_ids uuid[] := '{}';
BEGIN
START TRANSACTION;
--------------------------------------------------------------------
-- 1) Resolve target organizations and companies (uniform block)
--------------------------------------------------------------------
IF p_organization_ids IS NULL OR array_length(p_organization_ids, 1) IS NULL THEN
SELECT array_agg(id)
INTO v_orgs
FROM organizations
WHERE created_on_utc > '2025-05-23'
AND created_on_utc < (NOW() - interval '24 hours');
ELSE
v_orgs := p_organization_ids;
END IF;
IF v_orgs IS NULL OR array_length(v_orgs, 1) IS NULL THEN
RAISE NOTICE 'No organizations found for cleanup.';
COMMIT;
RETURN;
END IF;
-- Companies by organization
SELECT COALESCE(array_agg(id), '{}')
INTO v_companies
FROM companies
WHERE organization_id = ANY(v_orgs);
IF v_companies IS NULL OR array_length(v_companies, 1) IS NULL THEN
RAISE NOTICE 'No companies resolved for purchase purge. Orgs: %', v_orgs;
COMMIT;
RETURN;
END IF;
RAISE NOTICE 'Purchase purge targets - Organizations: %; Companies: %', v_orgs, v_companies;
--------------------------------------------------------------------
-- 2) Collect IDs (COALESCE to '{}' to avoid NULL-array issues)
--------------------------------------------------------------------
-- Vendors
SELECT COALESCE(array_agg(id), '{}')
INTO v_vendor_ids
FROM vendors
WHERE company_id = ANY(v_companies);
-- Warehouses (by vendor) and their addresses
SELECT COALESCE(array_agg(id), '{}')
INTO v_warehouse_ids
FROM warehouses
WHERE vendor_id = ANY(v_vendor_ids);
SELECT COALESCE(array_agg(address_id), '{}')
INTO v_warehouse_address_ids
FROM warehouses
WHERE id = ANY(v_warehouse_ids);
-- Draft bills
SELECT COALESCE(array_agg(id), '{}')
INTO v_draft_bill_header_ids
FROM draft_bill_headers
WHERE company_id = ANY(v_companies)
AND created_on_utc < v_cutoff_24h;
SELECT COALESCE(array_agg(id), '{}')
INTO v_draft_bill_detail_ids
FROM draft_bill_details
WHERE bill_header_id = ANY(v_draft_bill_header_ids);
-- Final bills
SELECT COALESCE(array_agg(id), '{}')
INTO v_bill_header_ids
FROM bill_headers
WHERE company_id = ANY(v_companies)
AND created_on_utc < v_cutoff_24h;
SELECT COALESCE(array_agg(id), '{}')
INTO v_bill_detail_ids
FROM bill_details
WHERE bill_header_id = ANY(v_bill_header_ids);
-- Payments
SELECT COALESCE(array_agg(id), '{}')
INTO v_bill_payment_header_ids
FROM bill_payment_headers
WHERE company_id = ANY(v_companies)
AND created_on_utc < v_cutoff_24h;
SELECT COALESCE(array_agg(id), '{}')
INTO v_bill_payment_detail_ids
FROM bill_payment_details
WHERE bill_payment_header_id = ANY(v_bill_payment_header_ids);
-- Recurring schedules
SELECT COALESCE(array_agg(id), '{}')
INTO v_recurring_schedule_ids
FROM recurring_bill_schedules
WHERE company_id = ANY(v_companies);
-- Gate passes
SELECT COALESCE(array_agg(id), '{}')
INTO v_gate_pass_header_ids
FROM gate_pass_headers
WHERE company_id = ANY(v_companies);
SELECT COALESCE(array_agg(id), '{}')
INTO v_gate_pass_detail_ids
FROM gate_pass_details
WHERE gate_pass_header_id = ANY(v_gate_pass_header_ids);
-- Vendor notes
SELECT COALESCE(array_agg(id), '{}')
INTO v_vendor_note_header_ids
FROM vendor_note_headers
WHERE company_id = ANY(v_companies);
SELECT COALESCE(array_agg(id), '{}')
INTO v_vendor_note_detail_ids
FROM vendor_note_details
WHERE vendor_note_header_id = ANY(v_vendor_note_header_ids);
--------------------------------------------------------------------
-- 3) Purge in strict child → parent order (avoid FK violations)
--------------------------------------------------------------------
-- Vendor advance settlements (before payments, in case of linkage)
DELETE FROM vendor_advance_settlements
WHERE company_id = ANY(v_companies)
OR applied_in_payment_id = ANY(v_bill_payment_header_ids);
-- Bill payments: details → headers → counters
DELETE FROM bill_payment_details
WHERE id = ANY(v_bill_payment_detail_ids);
DELETE FROM bill_payment_headers
WHERE id = ANY(v_bill_payment_header_ids);
DELETE FROM bill_payment_header_ids
WHERE company_id = ANY(v_companies);
-- Vendor advances (refunds, advances, counters)
DELETE FROM vendor_advance_refunds
WHERE company_id = ANY(v_companies);
DELETE FROM vendor_advances
WHERE company_id = ANY(v_companies);
DELETE FROM vendor_advance_ids
WHERE company_id = ANY(v_companies);
DELETE FROM vendor_advance_settlement_ids
WHERE company_id = ANY(v_companies);
-- Bill approval logs/issues and details → headers → counters
DELETE FROM bill_approval_logs
WHERE bill_id = ANY(v_bill_header_ids);
DELETE FROM bill_approval_issue_logs
WHERE bill_id = ANY(v_bill_header_ids);
DELETE FROM bill_details
WHERE id = ANY(v_bill_detail_ids);
-- Any temp next-status rows tied to these bills
DELETE FROM temp_bill_next_status
WHERE bill_id = ANY(v_bill_header_ids) OR bill_id = ANY(v_draft_bill_header_ids);
DELETE FROM bill_headers
WHERE id = ANY(v_bill_header_ids);
DELETE FROM bill_header_ids
WHERE company_id = ANY(v_companies);
-- Draft bills: details → headers → counters
DELETE FROM draft_bill_details
WHERE id = ANY(v_draft_bill_detail_ids);
DELETE FROM draft_bill_headers
WHERE id = ANY(v_draft_bill_header_ids);
DELETE FROM draft_bill_header_ids
WHERE company_id = ANY(v_companies);
-- Recurring schedules: details → schedules
DELETE FROM recurrence_bill_schedule_details
WHERE schedule_id = ANY(v_recurring_schedule_ids);
DELETE FROM recurring_bill_schedules
WHERE id = ANY(v_recurring_schedule_ids);
-- Gate passes: details → headers
DELETE FROM gate_pass_details
WHERE id = ANY(v_gate_pass_detail_ids);
DELETE FROM gate_pass_headers
WHERE id = ANY(v_gate_pass_header_ids);
-- Vendor notes: details → headers → configs
DELETE FROM vendor_note_details
WHERE id = ANY(v_vendor_note_detail_ids);
DELETE FROM vendor_note_headers
WHERE id = ANY(v_vendor_note_header_ids);
DELETE FROM vendor_note_work_flows
WHERE company_id = ANY(v_companies);
DELETE FROM vendor_note_statuses
WHERE company_id = ANY(v_companies);
-- Vendor-company scoped defaults
DELETE FROM vendor_default_accounts
WHERE company_id = ANY(v_companies);
-- Payment orders tied to vendors (child of vendors)
DELETE FROM payment_orders
WHERE vendor_id = ANY(v_vendor_ids);
-- Vendor bridges (contacts, bank accounts, upis) and warehouses
DELETE FROM vendor_contacts
WHERE vendor_id = ANY(v_vendor_ids);
DELETE FROM vendor_bank_accounts
WHERE vendor_id = ANY(v_vendor_ids);
DELETE FROM vendor_upis
WHERE vendor_id = ANY(v_vendor_ids);
-- Warehouses and their addresses (after bills and gate passes are gone)
DELETE FROM warehouses
WHERE id = ANY(v_warehouse_ids);
DELETE FROM addresses
WHERE id = ANY(v_warehouse_address_ids);
-- Finally, vendors
DELETE FROM vendors
WHERE id = ANY(v_vendor_ids);
-- Company-scoped bill workflow and approval configs
DELETE FROM bill_status_company_configs
WHERE company_id = ANY(v_companies);
DELETE FROM bill_account_approval_levels
WHERE company_id = ANY(v_companies);
DELETE FROM bill_approval_user_account
WHERE company_id = ANY(v_companies);
DELETE FROM bill_approval_user_company
WHERE company_id = ANY(v_companies);
DELETE FROM bill_workflow
WHERE company_id = ANY(v_companies);
RAISE NOTICE 'Purchase purge complete for companies: % (orgs: %).', v_companies, v_orgs;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE NOTICE 'purge_purchase_organization_data failed: %', SQLERRM;
-- Optionally rethrow
-- RAISE;
END;
$procedure$
-- Procedure: insert_draft_bill
CREATE OR REPLACE PROCEDURE public.insert_draft_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD; -- Declaring a record variable for the loop
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_detail_id uuid;
BEGIN
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
v_source_warehouse_id := NULL;
v_destination_warehouse_id := NULL;
ELSE
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
-- Call to create_draft_bill_header procedure
CALL public.create_draft_bill_header(
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
p_bill_date::date,
p_due_date::date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_round_off_tds,
p_currency_id,
p_note ,
p_bill_status_id ,
p_created_by ,
v_source_warehouse_id ,
v_destination_warehouse_id ,
p_bill_type_id,
p_bill_number,
p_po_no,
p_po_date::date
);
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
-- Loop through the JSONB array of bill details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
id uuid, product_id uuid, price numeric, quantity numeric,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
-- Log each field to ensure they are being read correctly
--RAISE NOTICE 'Detail - product_id: %, price: %, quantity: %, taxable_amount: %', detail."productId", detail.price, detail.quantity, detail."taxableAmount";
-- Insert into draft_bill_details, using "productId" from JSON
INSERT INTO public.draft_bill_details (
id, bill_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount,
total_amount, serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
);
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT needed
EXCEPTION
-- No explicit ROLLBACK needed; just raise the error
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
RAISE;
END;
$procedure$
-- Procedure: insert_bill_by_excel
CREATE OR REPLACE PROCEDURE public.insert_bill_by_excel(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_bill_number text, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_created_by uuid, IN p_bill_details jsonb, IN p_type integer, IN p_setteled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_bill_voucher text;
v_created_by uuid;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
v_bill_voucher := get_new_bill_voucher(p_company_id, p_bill_date::date);
-- Check if the bill voucher was generated successfully
--IF v_bill_voucher IS NULL THEN
-- RAISE EXCEPTION 'Failed to generate bill voucher for company ID: %', p_company_id;
--ELSE
-- RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
--END IF;
-- Fetch the system user ID from the users table
SELECT id INTO v_created_by FROM public.users
WHERE first_name = 'System'
LIMIT 1;
-- Raise an exception if the system user is not found
IF v_created_by IS NULL THEN
RAISE EXCEPTION 'System user not found in the users table';
END IF;
-- Insert into the bill header
INSERT INTO public.bill_headers(
id,
company_id,
vendor_id,
credit_account_id,
debit_account_id,
bill_voucher,
bill_number,
bill_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
tds_amount,
total_amount,
discount,
fees,
round_off,
round_off_tds,
currency_id,
note,
bill_status_id,
created_by,
source_warehouse_id,
destination_warehouse_id,
bill_type_id,
created_on_utc,
po_no,
po_date
)
VALUES (
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
v_bill_voucher,
p_bill_number,
p_bill_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
0,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_round_off_tds,
p_currency_id,
p_note,
p_bill_status_id,
p_created_by,
p_source_warehouse_id,
p_destination_warehouse_id,
p_type,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_po_no,
p_po_date
);
RAISE NOTICE 'bill header inserted with ID: %', p_id;
EXCEPTION
-- Handle unique violation (duplicate key)
WHEN unique_violation THEN
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
ROLLBACK;
-- Handle foreign key violation
WHEN foreign_key_violation THEN
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
ROLLBACK;
-- Catch all other exceptions
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
ROLLBACK;
END;
$procedure$
-- Procedure: insert_bill
CREATE OR REPLACE PROCEDURE public.insert_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD; -- Declaring a record variable for the loop
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_detail_id uuid;
BEGIN
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
v_source_warehouse_id := NULL;
v_destination_warehouse_id := NULL;
ELSE
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
-- Call to create_bill_header procedure
CALL public.create_bill_header(
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
p_bill_date::date,
p_due_date::date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_round_off_tds,
p_currency_id,
p_note ,
p_bill_status_id ,
p_created_by ,
v_source_warehouse_id ,
v_destination_warehouse_id ,
p_bill_type_id,
p_bill_number
);
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
-- Loop through the JSONB array of bill details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
id uuid, product_id uuid, price numeric, quantity integer,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
INSERT INTO public.bill_details (
id, bill_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount,
total_amount, serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
);
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT needed
EXCEPTION
-- No explicit ROLLBACK needed; just raise the error
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
RAISE;
END;
$procedure$
-- Procedure: create_draft_bill_header
CREATE OR REPLACE PROCEDURE public.create_draft_bill_header(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_bill_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_currency_id integer, IN p_note character varying, IN p_bill_status_id integer, IN p_created_by uuid, IN p_source_warehouse_id uuid DEFAULT NULL::uuid, IN p_destination_warehouse_id uuid DEFAULT NULL::uuid, IN p_bill_type_id integer DEFAULT 1, IN p_bill_number character varying DEFAULT NULL::character varying, IN p_po_no text DEFAULT NULL::text, IN p_po_date date DEFAULT NULL::date)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_voucher character varying;
source_warehouse_id uuid;
destination_warehouse_id uuid;
BEGIN
-- Generate bill voucher using the draft bill function
v_bill_voucher := get_new_draft_bill_voucher(p_company_id,p_bill_date);
-- Log the generated bill voucher
RAISE NOTICE 'Bill Voucher: %', v_bill_voucher;
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
source_warehouse_id := NULL;
destination_warehouse_id := NULL;
ELSE
-- Use passed values or default to null if not provided
source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
-- Insert into draft_bill_headers table
INSERT INTO
public.draft_bill_headers(
id,
company_id,
vendor_id,
credit_account_id,
debit_account_id,
bill_voucher,
bill_number,
bill_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
tds_amount,
total_amount,
discount,
fees,
round_off,
round_off_tds,
currency_id,
note,
bill_status_id,
created_by,
source_warehouse_id,
destination_warehouse_id,
bill_type_id,
created_on_utc,
po_no,
po_date
)
VALUES (
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
v_bill_voucher,
p_bill_number,
p_bill_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_round_off_tds,
p_currency_id,
p_note,
p_bill_status_id,
p_created_by,
p_source_warehouse_id,
p_destination_warehouse_id,
p_bill_type_id,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_po_no,
p_po_date
);
-- Log success message
RAISE NOTICE 'Draft Bill header inserted successfully with Bill Voucher: % ', v_bill_voucher ;
END;
$procedure$
-- Procedure: create_bill_header
CREATE OR REPLACE PROCEDURE public.create_bill_header(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_bill_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_currency_id integer, IN p_note character varying, IN p_bill_status_id integer, IN p_created_by uuid, IN p_source_warehouse_id uuid DEFAULT NULL::uuid, IN p_destination_warehouse_id uuid DEFAULT NULL::uuid, IN p_bill_type_id integer DEFAULT 1, IN p_bill_number character varying DEFAULT NULL::character varying, IN p_po_no text DEFAULT NULL::text, IN p_po_date date DEFAULT NULL::date)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_voucher character varying;
source_warehouse_id uuid;
destination_warehouse_id uuid;
BEGIN
-- Attempt to generate bill voucher
v_bill_voucher := get_new_bill_voucher(p_company_id, p_bill_date);
-- Check if the bill voucher was generated successfully
IF v_bill_voucher IS NULL THEN
RAISE EXCEPTION 'Failed to generate bill voucher for company ID: %', p_company_id;
ELSE
RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
END IF;
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
source_warehouse_id := NULL;
destination_warehouse_id := NULL;
ELSE
-- Use passed values or default to null if not provided
source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
-- Insert into bill_headers table
INSERT INTO public.bill_headers(
id,
company_id,
vendor_id,
credit_account_id,
debit_account_id,
bill_voucher,
bill_number,
bill_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
tds_amount,
total_amount,
discount,
fees,
round_off,
round_off_tds,
currency_id,
note,
bill_status_id,
created_by,
source_warehouse_id,
destination_warehouse_id,
bill_type_id,
created_on_utc,
po_no,
po_date
)
VALUES (
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
v_bill_voucher,
p_bill_number,
p_bill_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_round_off_tds,
p_currency_id,
p_note,
p_bill_status_id,
p_created_by,
p_source_warehouse_id,
p_destination_warehouse_id,
p_bill_type_id,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_po_no,
p_po_date
);
RAISE NOTICE 'Bill header inserted successfully with Bill Voucher: %', v_bill_voucher;
END;
$procedure$
-- Procedure: run_scheduled_bill
CREATE OR REPLACE PROCEDURE public.run_scheduled_bill(IN p_bill_header_id uuid, IN p_draft_bill_header_id uuid, IN p_schedule_date timestamp without time zone DEFAULT CURRENT_TIMESTAMP)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_system_user_id uuid;
v_execution_log_id uuid := gen_random_uuid();
v_related_schedule_id uuid;
v_new_draft_bill_id uuid;
v_source_is_live boolean := false;
v_source_bill_id uuid; -- final chosen source id (live or draft)
src_header RECORD;
src_detail RECORD;
BEGIN
----------------------------------------------------------------
-- 1. Find 'System' user (same as sales side)
----------------------------------------------------------------
SELECT u.id
INTO v_system_user_id
FROM public.users AS u
WHERE u.is_deleted = false
AND lower(u.first_name) = 'system'
ORDER BY u.created_on_utc NULLS LAST, u.id
LIMIT 1;
IF v_system_user_id IS NULL THEN
RAISE NOTICE 'No user with first_name="System" found; proceeding with NULL triggered_by.';
END IF;
----------------------------------------------------------------
-- 2. Decide source: live bill or draft bill (same idea as invoices)
----------------------------------------------------------------
IF p_bill_header_id IS NOT NULL THEN
v_source_bill_id := p_bill_header_id;
v_source_is_live := true;
ELSIF p_draft_bill_header_id IS NOT NULL THEN
v_source_bill_id := p_draft_bill_header_id;
v_source_is_live := false;
ELSE
RAISE NOTICE 'Both bill_header_id and draft_bill_header_id are NULL; aborting.';
RETURN;
END IF;
----------------------------------------------------------------
-- 3. Get related schedule_id from recurring_bill_schedules
-- must match on live OR draft id, just like sales version
----------------------------------------------------------------
SELECT s.id
INTO v_related_schedule_id
FROM public.recurring_bill_schedules AS s
WHERE s.is_deleted = false
AND (
(v_source_is_live AND s.bill_header_id = v_source_bill_id)
OR (NOT v_source_is_live AND s.draft_bill_header_id = v_source_bill_id)
)
ORDER BY s.starts_from DESC
LIMIT 1;
----------------------------------------------------------------
-- 4. Fetch source header from correct table
----------------------------------------------------------------
IF v_source_is_live THEN
SELECT *
INTO src_header
FROM public.bill_headers
WHERE id = v_source_bill_id
AND is_deleted = false;
ELSE
SELECT *
INTO src_header
FROM public.draft_bill_headers
WHERE id = v_source_bill_id
AND is_deleted = false;
END IF;
-- if nothing found, log failure and stop
IF NOT FOUND THEN
INSERT INTO public.schedule_execution_logs (
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
) VALUES (
v_execution_log_id,
v_related_schedule_id,
(p_schedule_date::date),
NOW(),
'Failure',
'Source bill header not found in live or draft tables.',
v_system_user_id,
false
);
RAISE NOTICE 'Source bill header % not found; aborting for date %.',
v_source_bill_id, (p_schedule_date::date);
RETURN;
END IF;
----------------------------------------------------------------
-- 5. Create new draft bill header (clone header-level data)
----------------------------------------------------------------
v_new_draft_bill_id := gen_random_uuid();
CALL public.create_draft_bill_header(
/* p_id */ v_new_draft_bill_id,
/* p_company_id */ src_header.company_id,
/* p_vendor_id */ src_header.vendor_id,
/* p_credit_account_id */ src_header.credit_account_id,
/* p_debit_account_id */ src_header.debit_account_id,
/* p_bill_date */ (p_schedule_date::date),
/* p_due_date */ ((p_schedule_date::date) + COALESCE(src_header.payment_term, 10))::date,
/* p_payment_term */ src_header.payment_term,
/* p_taxable_amount */ src_header.taxable_amount,
/* p_cgst_amount */ src_header.cgst_amount,
/* p_sgst_amount */ src_header.sgst_amount,
/* p_igst_amount */ src_header.igst_amount,
/* p_tds_amount */ COALESCE(src_header.tds_amount, 0),
/* p_total_amount */ src_header.total_amount,
/* p_discount */ src_header.discount,
/* p_fees */ src_header.fees,
/* p_round_off */ src_header.round_off,
/* p_round_off_tds */ COALESCE(src_header.round_off_tds, 0),
/* p_currency_id */ src_header.currency_id,
/* p_note (varchar) */ COALESCE(src_header.note, '')::varchar,
/* p_bill_status_id */ 1,
/* p_created_by */ v_system_user_id,
/* p_source_wh_id */ src_header.source_warehouse_id,
/* p_destination_wh_id */ src_header.destination_warehouse_id,
/* p_bill_type_id */ src_header.bill_type_id,
/* p_bill_number */ COALESCE(src_header.bill_number, '')::varchar,
/* p_po_no */ src_header.po_no::text,
/* p_po_date */ (src_header.po_date)::date
);
----------------------------------------------------------------
-- 6. Clone all details from correct source table
----------------------------------------------------------------
IF v_source_is_live THEN
FOR src_detail IN
SELECT *
FROM public.bill_details
WHERE bill_header_id = v_source_bill_id
LOOP
CALL public.create_draft_bill_detail(
v_new_draft_bill_id,
src_detail.price,
src_detail.quantity,
src_detail.discount,
src_detail.fees,
src_detail.cgst_amount,
src_detail.igst_amount,
src_detail.sgst_amount,
src_detail.taxable_amount,
src_detail.total_amount,
src_detail.serial_number,
src_detail.product_id
);
END LOOP;
ELSE
FOR src_detail IN
SELECT *
FROM public.draft_bill_details
WHERE bill_header_id = v_source_bill_id
LOOP
CALL public.create_draft_bill_detail(
v_new_draft_bill_id,
src_detail.price,
src_detail.quantity,
src_detail.discount,
src_detail.fees,
src_detail.cgst_amount,
src_detail.igst_amount,
src_detail.sgst_amount,
src_detail.taxable_amount,
src_detail.total_amount,
src_detail.serial_number,
src_detail.product_id
);
END LOOP;
END IF;
----------------------------------------------------------------
-- 7. Log success
----------------------------------------------------------------
INSERT INTO public.schedule_execution_logs (
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
) VALUES (
v_execution_log_id,
v_related_schedule_id,
(p_schedule_date::date),
NOW(),
'Success',
'',
v_system_user_id,
false
);
RAISE NOTICE 'Draft bill % created from % (live=%), schedule_id %, on %.',
v_new_draft_bill_id, v_source_bill_id, v_source_is_live, v_related_schedule_id, (p_schedule_date::date);
EXCEPTION WHEN OTHERS THEN
INSERT INTO public.schedule_execution_logs (
id, schedule_id, execution_date, execution_time, status, error_message, triggered_by, is_deleted
) VALUES (
COALESCE(v_execution_log_id, gen_random_uuid()),
v_related_schedule_id,
(p_schedule_date::date),
NOW(),
'Failure',
SQLERRM,
v_system_user_id,
false
);
RAISE NOTICE 'Error while cloning bill % on %: %',
v_source_bill_id, (p_schedule_date::date), SQLERRM;
END;
$procedure$
-- Procedure: update_draft_bill_next_status
CREATE OR REPLACE PROCEDURE public.update_draft_bill_next_status(IN p_company_id uuid, IN p_bill_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill RECORD;
v_account_id uuid;
v_current_approval_level integer;
v_approval_level_required integer;
v_issue TEXT;
APPROVED_STATUS CONSTANT INTEGER := 3;
v_debug_next_status TEXT;
v_next_temp_id int;
BEGIN
RAISE NOTICE 'START update_draft_bill_next_status for company %, user %', p_company_id, p_modified_by;
RAISE NOTICE 'Input bill IDs: %', p_bill_ids;
FOR v_bill IN
SELECT id, bill_status_id, debit_account_id, current_approval_level
FROM public.draft_bill_headers
WHERE id = ANY(p_bill_ids) AND bill_status_id = 2
LOOP
RAISE NOTICE 'Processing Bill ID: %', v_bill.id;
v_account_id := v_bill.debit_account_id;
v_current_approval_level := v_bill.current_approval_level;
SELECT approval_level_required
INTO v_approval_level_required
FROM public.bill_approval_level_view
WHERE company_id = p_company_id
AND status_id = v_bill.bill_status_id
AND (account_id = v_account_id OR account_id IS NULL)
ORDER BY account_id ASC
LIMIT 1;
RAISE NOTICE 'Bill % current level: %, required: %', v_bill.id, v_current_approval_level, v_approval_level_required;
IF v_approval_level_required IS NULL THEN
v_issue := 'Approval level not found';
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
VALUES (v_bill.id, v_issue, now(), p_modified_by);
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, 'Not found', v_issue);
RAISE NOTICE 'Bill % skipped: approval level not found', v_bill.id;
CONTINUE;
END IF;
-- 🔔 Replace this with call to `check_bill_approval_permissions`
PERFORM public.check_bill_approval_permissions(
p_company_id,
v_bill.bill_status_id,
p_modified_by,
v_account_id,
v_current_approval_level + 1
);
IF NOT FOUND THEN
v_issue := 'User not authorized to approve at the next level';
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
VALUES (v_bill.id, v_issue, now(), p_modified_by);
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, 'User not authorized', v_issue);
RAISE NOTICE 'Bill % skipped: user not authorized', v_bill.id;
CONTINUE;
END IF;
IF v_current_approval_level + 1 < v_approval_level_required THEN
v_debug_next_status := CONCAT('Level ', v_current_approval_level + 1);
ELSE
v_debug_next_status := 'Approved';
END IF;
RAISE NOTICE 'Bill % next computed status: %', v_bill.id, v_debug_next_status;
IF v_approval_level_required > (v_current_approval_level + 1) THEN
UPDATE public.draft_bill_headers
SET current_approval_level = v_current_approval_level + 1,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_bill.id;
INSERT INTO public.bill_approval_logs(
bill_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
VALUES (
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
CONCAT('Approved at level ', v_current_approval_level + 1),
now(), p_modified_by, v_current_approval_level + 1
);
RAISE NOTICE 'Bill % moved to next approval level: %', v_bill.id, v_current_approval_level + 1;
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
ELSE
UPDATE public.draft_bill_headers
SET bill_status_id = APPROVED_STATUS,
modified_by = p_modified_by,
modified_on_utc = now(),
current_approval_level = v_current_approval_level + 1
WHERE id = v_bill.id;
INSERT INTO public.bill_approval_logs(
bill_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
VALUES (
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
'Final Approval',
now(), p_modified_by, v_current_approval_level + 1
);
CALL transfer_draft_to_bill(v_bill.id, p_modified_by);
RAISE NOTICE 'Bill % approved and transferred', v_bill.id;
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
END IF;
RAISE NOTICE '--- Finished processing Bill: % ---', v_bill.id;
END LOOP;
RAISE NOTICE 'END update_draft_bill_next_status';
END
$procedure$
-- Procedure: save_bill_and_details
CREATE OR REPLACE PROCEDURE public.save_bill_and_details(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_round_off_tds numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_created_by uuid, IN p_currency_id integer, IN p_po_date timestamp without time zone, IN p_po_no text, IN p_destination_warehouse_id uuid, IN p_source_warehouse_id uuid, IN p_bill_type_id integer, IN p_bill_number text, IN p_tds_amount numeric, IN p_details jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_voucher text;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
-- Generate the new bill voucher number
v_bill_voucher := public.get_new_bill_voucher(p_company_id, p_bill_date::date);
RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
-- Validate p_details JSONB
IF p_details IS NULL OR jsonb_array_length(p_details) = 0 THEN
RAISE EXCEPTION 'Details JSONB parameter (p_details) cannot be empty or NULL';
END IF;
-- Insert into bill_headers table
INSERT INTO public.bill_headers (
id, company_id, vendor_id, discount, bill_voucher, bill_date, payment_term,
bill_status_id, due_date, total_amount, taxable_amount, fees, sgst_amount,
cgst_amount, igst_amount, note, round_off, round_off_tds, debit_account_id, credit_account_id,
created_on_utc, created_by, currency_id, po_date, po_no, destination_warehouse_id,
source_warehouse_id, bill_type_id, bill_number, tds_amount
) VALUES (
p_id, p_company_id, p_vendor_id, p_discount, v_bill_voucher, p_bill_date,
p_payment_term, p_bill_status_id, p_due_date, p_total_amount, p_taxable_amount, p_fees,
p_sgst_amount, p_cgst_amount, p_igst_amount, p_note, p_round_off, p_round_off_tds, p_debit_account_id,
p_credit_account_id, NOW(), p_created_by, p_currency_id, p_po_date, p_po_no,
v_destination_warehouse_id, v_source_warehouse_id, p_bill_type_id, p_bill_number, p_tds_amount
);
-- Insert into bill_details table
INSERT INTO public.bill_details (
id, bill_header_id, price, quantity, cgst_amount, discount, fees, igst_amount,
sgst_amount, taxable_amount, total_amount, serial_number, product_id
)
SELECT
gen_random_uuid(), p_id, (detail->>'price')::numeric, (detail->>'quantity')::numeric,
(detail->>'cgst_amount')::numeric, (detail->>'discount')::numeric, (detail->>'fees')::numeric,
(detail->>'igst_amount')::numeric, (detail->>'sgst_amount')::numeric, (detail->>'taxable_amount')::numeric,
(detail->>'total_amount')::numeric, (detail->>'serial_number')::integer, (detail->>'product_id')::uuid
FROM jsonb_array_elements(p_details) AS detail;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'An error occurred: %', SQLERRM;
END;
$procedure$
-- Procedure: update_draft_bill_next_status_1
CREATE OR REPLACE PROCEDURE public.update_draft_bill_next_status_1(IN p_company_id uuid, IN p_bill_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill RECORD;
v_account_id uuid;
v_current_approval_level integer;
v_approval_level_required integer;
v_issue TEXT;
APPROVED_STATUS CONSTANT INTEGER := 3;
v_debug_next_status TEXT;
v_next_temp_id int;
BEGIN
RAISE NOTICE 'START update_draft_bill_next_status_1 for company %, user %', p_company_id, p_modified_by;
RAISE NOTICE 'Input bill IDs: %', p_bill_ids;
FOR v_bill IN
SELECT id, bill_status_id, debit_account_id, current_approval_level
FROM public.draft_bill_headers
WHERE id = ANY(p_bill_ids) AND bill_status_id = 2
LOOP
RAISE NOTICE 'Processing Bill ID: %', v_bill.id;
v_account_id := v_bill.debit_account_id;
v_current_approval_level := v_bill.current_approval_level;
SELECT approval_level_required
INTO v_approval_level_required
FROM public.bill_approval_level_view
WHERE company_id = p_company_id
AND status_id = v_bill.bill_status_id
AND (account_id = v_account_id OR account_id IS NULL)
ORDER BY account_id ASC
LIMIT 1;
RAISE NOTICE 'Bill % current level: %, required: %', v_bill.id, v_current_approval_level, v_approval_level_required;
IF v_approval_level_required IS NULL THEN
v_issue := 'Approval level not found';
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
VALUES (v_bill.id, v_issue, now(), p_modified_by);
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, 'Not found', v_issue);
RAISE NOTICE 'Bill % skipped: approval level not found', v_bill.id;
CONTINUE;
END IF;
-- 🔔 Replace this with call to `check_bill_approval_permissions`
PERFORM public.check_bill_approval_permissions(
p_company_id,
v_bill.bill_status_id,
p_modified_by,
v_account_id,
v_current_approval_level + 1
);
IF NOT FOUND THEN
v_issue := 'User not authorized to approve at the next level';
INSERT INTO public.bill_approval_issue_logs(bill_id, issue, created_on_utc, created_by)
VALUES (v_bill.id, v_issue, now(), p_modified_by);
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, 'User not authorized', v_issue);
RAISE NOTICE 'Bill % skipped: user not authorized', v_bill.id;
CONTINUE;
END IF;
IF v_current_approval_level + 1 < v_approval_level_required THEN
v_debug_next_status := CONCAT('Level ', v_current_approval_level + 1);
ELSE
v_debug_next_status := 'Approved';
END IF;
RAISE NOTICE 'Bill % next computed status: %', v_bill.id, v_debug_next_status;
IF v_approval_level_required > (v_current_approval_level + 1) THEN
UPDATE public.draft_bill_headers
SET current_approval_level = v_current_approval_level + 1,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_bill.id;
INSERT INTO public.bill_approval_logs(
bill_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
VALUES (
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
CONCAT('Approved at level ', v_current_approval_level + 1),
now(), p_modified_by, v_current_approval_level + 1
);
RAISE NOTICE 'Bill % moved to next approval level: %', v_bill.id, v_current_approval_level + 1;
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
ELSE
UPDATE public.draft_bill_headers
SET bill_status_id = APPROVED_STATUS,
modified_by = p_modified_by,
modified_on_utc = now(),
current_approval_level = v_current_approval_level + 1
WHERE id = v_bill.id;
INSERT INTO public.bill_approval_logs(
bill_id, status_id, approved_by, approved_on, "comment",
created_on_utc, created_by, approval_level
)
VALUES (
v_bill.id, v_bill.bill_status_id, p_modified_by, now(),
'Final Approval',
now(), p_modified_by, v_current_approval_level + 1
);
CALL transfer_draft_to_bill(v_bill.id, p_modified_by);
RAISE NOTICE 'Bill % approved and transferred', v_bill.id;
SELECT COALESCE(MAX(id), 0) + 1 INTO v_next_temp_id FROM temp_bill_next_status;
INSERT INTO temp_bill_next_status(id, bill_id, status, error)
VALUES (v_next_temp_id, v_bill.id, v_debug_next_status, NULL);
END IF;
RAISE NOTICE '--- Finished processing Bill: % ---', v_bill.id;
END LOOP;
RAISE NOTICE 'END update_draft_bill_next_status';
END
$procedure$
-- Procedure: insert_multiple_bills_by_excel1
CREATE OR REPLACE PROCEDURE public.insert_multiple_bills_by_excel1(IN p_bills jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
bill RECORD;
v_created_by uuid;
BEGIN
-- Validate the input JSONB structure
BEGIN
PERFORM *
FROM jsonb_to_recordset(p_bills) AS (
bill_id uuid,
company_id uuid,
vendor_id uuid,
debit_account_id uuid,
credit_account_id uuid,
discount numeric,
bill_number text,
currency_id integer,
bill_date timestamp without time zone,
payment_term integer,
bill_status_id integer,
due_date timestamp without time zone,
total_amount numeric,
note text,
taxable_amount numeric,
fees numeric,
sgst_amount numeric,
cgst_amount numeric,
igst_amount numeric,
tds_amount numeric,
round_off numeric,
po_no text,
po_date timestamp without time zone,
source_warehouse_id uuid,
destination_warehouse_id uuid,
bill_type_id integer,
lines jsonb,
created_by uuid
);
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'Invalid JSON input provided: %', SQLERRM;
RETURN;
END;
-- Get 'System' user as created_by
SELECT id INTO v_created_by
FROM public.users
WHERE first_name = 'System'
LIMIT 1;
-- Loop through each bill and process
FOR bill IN
SELECT * FROM jsonb_to_recordset(p_bills) AS (
bill_id uuid,
company_id uuid,
vendor_id uuid,
debit_account_id uuid,
credit_account_id uuid,
discount numeric,
bill_number text,
currency_id integer,
bill_date timestamp without time zone,
payment_term integer,
bill_status_id integer,
due_date timestamp without time zone,
total_amount numeric,
note text,
taxable_amount numeric,
fees numeric,
sgst_amount numeric,
cgst_amount numeric,
igst_amount numeric,
tds_amount numeric,
round_off numeric,
po_no text,
po_date timestamp without time zone,
source_warehouse_id uuid,
destination_warehouse_id uuid,
bill_type_id integer,
lines jsonb,
created_by uuid
)
LOOP
BEGIN
-- Check for duplicate
IF EXISTS (
SELECT 1
FROM public.bill_headers
WHERE bill_number = bill.bill_number
AND company_id = bill.company_id
) THEN
-- If duplicate found, log and skip
RAISE NOTICE 'Skipping duplicate bill_number: %, company_id: %', bill.bill_number, bill.company_id;
CONTINUE;
END IF;
-- Insert the bill if no duplicate
CALL public.save_bill_and_details(
bill.bill_id,
bill.company_id,
bill.vendor_id,
bill.discount,
bill.bill_date,
bill.payment_term,
bill.bill_status_id,
bill.due_date,
bill.total_amount,
bill.taxable_amount,
bill.fees,
bill.sgst_amount,
bill.cgst_amount,
bill.igst_amount,
bill.note,
bill.round_off,
bill.debit_account_id,
bill.credit_account_id,
v_created_by,
bill.currency_id,
bill.po_date,
bill.po_no,
bill.destination_warehouse_id,
bill.source_warehouse_id,
bill.bill_type_id,
bill.bill_number,
bill.tds_amount,
bill.lines
);
EXCEPTION
WHEN OTHERS THEN
-- Log error for this bill, but continue
RAISE NOTICE 'Error processing bill ID %: %', bill.bill_id, SQLERRM;
CONTINUE;
END;
END LOOP;
RAISE NOTICE 'All bills processed successfully (excluding duplicates/errors)';
END;
$procedure$
-- Procedure: insert_draft_bill
CREATE OR REPLACE PROCEDURE public.insert_draft_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD; -- Declaring a record variable for the loop
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_detail_id uuid;
BEGIN
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
v_source_warehouse_id := NULL;
v_destination_warehouse_id := NULL;
ELSE
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
-- Call to create_draft_bill_header procedure
CALL public.create_draft_bill_header(
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
p_bill_date::date,
p_due_date::date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note ,
p_bill_status_id ,
p_created_by ,
v_source_warehouse_id ,
v_destination_warehouse_id ,
p_bill_type_id,
p_bill_number,
p_po_no,
p_po_date::date
);
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
-- Loop through the JSONB array of bill details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
id uuid, product_id uuid, price numeric, quantity numeric,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
-- Log each field to ensure they are being read correctly
--RAISE NOTICE 'Detail - product_id: %, price: %, quantity: %, taxable_amount: %', detail."productId", detail.price, detail.quantity, detail."taxableAmount";
-- Insert into draft_bill_details, using "productId" from JSON
INSERT INTO public.draft_bill_details (
id, bill_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount,
total_amount, serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
);
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT needed
EXCEPTION
-- No explicit ROLLBACK needed; just raise the error
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
RAISE;
END;
$procedure$
-- Procedure: insert_bill_by_excel
CREATE OR REPLACE PROCEDURE public.insert_bill_by_excel(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_bill_number text, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_created_by uuid, IN p_bill_details jsonb, IN p_type integer, IN p_setteled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_bill_voucher text;
v_created_by uuid;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
v_bill_voucher := get_new_bill_voucher(p_company_id, p_bill_date::date);
-- Check if the bill voucher was generated successfully
--IF v_bill_voucher IS NULL THEN
-- RAISE EXCEPTION 'Failed to generate bill voucher for company ID: %', p_company_id;
--ELSE
-- RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
--END IF;
-- Fetch the system user ID from the users table
SELECT id INTO v_created_by FROM public.users
WHERE first_name = 'System'
LIMIT 1;
-- Raise an exception if the system user is not found
IF v_created_by IS NULL THEN
RAISE EXCEPTION 'System user not found in the users table';
END IF;
-- Insert into the bill header
INSERT INTO public.bill_headers(
id,
company_id,
vendor_id,
credit_account_id,
debit_account_id,
bill_voucher,
bill_number,
bill_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
tds_amount,
total_amount,
discount,
fees,
round_off,
currency_id,
note,
bill_status_id,
created_by,
source_warehouse_id,
destination_warehouse_id,
bill_type_id,
created_on_utc,
po_no,
po_date
)
VALUES (
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
v_bill_voucher,
p_bill_number,
p_bill_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
0,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_bill_status_id,
p_created_by,
p_source_warehouse_id,
p_destination_warehouse_id,
p_type,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_po_no,
p_po_date
);
RAISE NOTICE 'bill header inserted with ID: %', p_id;
EXCEPTION
-- Handle unique violation (duplicate key)
WHEN unique_violation THEN
RAISE NOTICE 'Duplicate entry: %, rolling back', SQLERRM;
ROLLBACK;
-- Handle foreign key violation
WHEN foreign_key_violation THEN
RAISE NOTICE 'Foreign key violation: %, rolling back', SQLERRM;
ROLLBACK;
-- Catch all other exceptions
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
ROLLBACK;
END;
$procedure$
-- Procedure: insert_bill
CREATE OR REPLACE PROCEDURE public.insert_bill(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_currency_id integer, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_po_no text, IN p_po_date timestamp without time zone, IN p_source_warehouse_id uuid, IN p_destination_warehouse_id uuid, IN p_bill_type_id integer, IN p_created_by uuid, IN p_bill_details jsonb, IN p_bill_number text, IN p_setteled_amount numeric DEFAULT 0)
LANGUAGE plpgsql
AS $procedure$
DECLARE
detail RECORD; -- Declaring a record variable for the loop
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
v_detail_id uuid;
BEGIN
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
v_source_warehouse_id := NULL;
v_destination_warehouse_id := NULL;
ELSE
v_source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
RAISE NOTICE 'v_source_warehouse_id: %', v_source_warehouse_id;
RAISE NOTICE 'v_destination_warehouse_id: %', v_destination_warehouse_id;
-- Call to create_bill_header procedure
CALL public.create_bill_header(
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
p_bill_date::date,
p_due_date::date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note ,
p_bill_status_id ,
p_created_by ,
v_source_warehouse_id ,
v_destination_warehouse_id ,
p_bill_type_id,
p_bill_number
);
RAISE NOTICE 'Bill header inserted with ID: %', p_id;
-- Loop through the JSONB array of bill details
FOR detail IN
SELECT * FROM jsonb_to_recordset(p_bill_details) AS (
id uuid, product_id uuid, price numeric, quantity integer,
fees numeric, discount numeric, taxable_amount numeric,
sgst_amount numeric, cgst_amount numeric, igst_amount numeric,
total_amount numeric, serial_number integer
)
LOOP
INSERT INTO public.bill_details (
id, bill_header_id, product_id, price, quantity, fees, discount,
taxable_amount, sgst_amount, cgst_amount, igst_amount,
total_amount, serial_number, is_deleted
) VALUES (
detail.id, p_id, detail.product_id, detail.price, detail.quantity,
detail.fees, detail.discount, detail.taxable_amount, detail.sgst_amount,
detail.cgst_amount, detail.igst_amount, detail.total_Amount, detail.serial_number, false
);
RAISE NOTICE 'Bill detail inserted with ID: %', detail.id;
END LOOP;
-- No explicit COMMIT needed
EXCEPTION
-- No explicit ROLLBACK needed; just raise the error
WHEN OTHERS THEN
RAISE NOTICE 'An error occurred: %, transaction rolled back', SQLERRM;
RAISE;
END;
$procedure$
-- Procedure: save_bill_and_details
CREATE OR REPLACE PROCEDURE public.save_bill_and_details(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_discount numeric, IN p_bill_date timestamp without time zone, IN p_payment_term integer, IN p_bill_status_id integer, IN p_due_date timestamp without time zone, IN p_total_amount numeric, IN p_taxable_amount numeric, IN p_fees numeric, IN p_sgst_amount numeric, IN p_cgst_amount numeric, IN p_igst_amount numeric, IN p_note text, IN p_round_off numeric, IN p_debit_account_id uuid, IN p_credit_account_id uuid, IN p_created_by uuid, IN p_currency_id integer, IN p_po_date timestamp without time zone, IN p_po_no text, IN p_destination_warehouse_id uuid, IN p_source_warehouse_id uuid, IN p_bill_type_id integer, IN p_bill_number text, IN p_tds_amount numeric, IN p_details jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_voucher text;
v_source_warehouse_id uuid;
v_destination_warehouse_id uuid;
BEGIN
-- Set default values for warehouses
v_source_warehouse_id := COALESCE(p_source_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
v_destination_warehouse_id := COALESCE(p_destination_warehouse_id, '00000000-0000-0000-0000-000000000000'::uuid);
-- Generate the new bill voucher number
v_bill_voucher := public.get_new_bill_voucher(p_company_id, p_bill_date::date);
RAISE NOTICE 'Generated Bill Voucher: %', v_bill_voucher;
-- Validate p_details JSONB
IF p_details IS NULL OR jsonb_array_length(p_details) = 0 THEN
RAISE EXCEPTION 'Details JSONB parameter (p_details) cannot be empty or NULL';
END IF;
-- Insert into bill_headers table
INSERT INTO public.bill_headers (
id, company_id, vendor_id, discount, bill_voucher, bill_date, payment_term,
bill_status_id, due_date, total_amount, taxable_amount, fees, sgst_amount,
cgst_amount, igst_amount, note, round_off, debit_account_id, credit_account_id,
created_on_utc, created_by, currency_id, po_date, po_no, destination_warehouse_id,
source_warehouse_id, bill_type_id, bill_number, tds_amount
) VALUES (
p_id, p_company_id, p_vendor_id, p_discount, v_bill_voucher, p_bill_date,
p_payment_term, p_bill_status_id, p_due_date, p_total_amount, p_taxable_amount, p_fees,
p_sgst_amount, p_cgst_amount, p_igst_amount, p_note, p_round_off, p_debit_account_id,
p_credit_account_id, NOW(), p_created_by, p_currency_id, p_po_date, p_po_no,
v_destination_warehouse_id, v_source_warehouse_id, p_bill_type_id, p_bill_number, p_tds_amount
);
-- Insert into bill_details table
INSERT INTO public.bill_details (
id, bill_header_id, price, quantity, cgst_amount, discount, fees, igst_amount,
sgst_amount, taxable_amount, total_amount, serial_number, product_id
)
SELECT
gen_random_uuid(), p_id, (detail->>'price')::numeric, (detail->>'quantity')::numeric,
(detail->>'cgst_amount')::numeric, (detail->>'discount')::numeric, (detail->>'fees')::numeric,
(detail->>'igst_amount')::numeric, (detail->>'sgst_amount')::numeric, (detail->>'taxable_amount')::numeric,
(detail->>'total_amount')::numeric, (detail->>'serial_number')::integer, (detail->>'product_id')::uuid
FROM jsonb_array_elements(p_details) AS detail;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION 'An error occurred: %', SQLERRM;
END;
$procedure$
-- Procedure: create_draft_bill_header
CREATE OR REPLACE PROCEDURE public.create_draft_bill_header(IN p_id uuid, IN p_company_id uuid, IN p_vendor_id uuid, IN p_credit_account_id uuid, IN p_debit_account_id uuid, IN p_bill_date date, IN p_due_date date, IN p_payment_term integer, IN p_taxable_amount numeric, IN p_cgst_amount numeric, IN p_sgst_amount numeric, IN p_igst_amount numeric, IN p_tds_amount numeric, IN p_total_amount numeric, IN p_discount numeric, IN p_fees numeric, IN p_round_off numeric, IN p_currency_id integer, IN p_note character varying, IN p_bill_status_id integer, IN p_created_by uuid, IN p_source_warehouse_id uuid DEFAULT NULL::uuid, IN p_destination_warehouse_id uuid DEFAULT NULL::uuid, IN p_bill_type_id integer DEFAULT 1, IN p_bill_number character varying DEFAULT NULL::character varying, IN p_po_no text DEFAULT NULL::text, IN p_po_date date DEFAULT NULL::date)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_bill_voucher character varying;
source_warehouse_id uuid;
destination_warehouse_id uuid;
BEGIN
-- Generate bill voucher using the draft bill function
v_bill_voucher := get_new_draft_bill_voucher(p_company_id,p_bill_date);
-- Log the generated bill voucher
RAISE NOTICE 'Bill Voucher: %', v_bill_voucher;
-- Set default values for warehouses based on bill type
IF p_bill_type_id = 2 OR p_bill_type_id = 3 THEN
source_warehouse_id := NULL;
destination_warehouse_id := NULL;
ELSE
-- Use passed values or default to null if not provided
source_warehouse_id := COALESCE(p_source_warehouse_id, NULL);
destination_warehouse_id := COALESCE(p_destination_warehouse_id, NULL);
END IF;
-- Insert into draft_bill_headers table
INSERT INTO
public.draft_bill_headers(
id,
company_id,
vendor_id,
credit_account_id,
debit_account_id,
bill_voucher,
bill_number,
bill_date,
due_date,
payment_term,
taxable_amount,
cgst_amount,
sgst_amount,
igst_amount,
tds_amount,
total_amount,
discount,
fees,
round_off,
currency_id,
note,
bill_status_id,
created_by,
source_warehouse_id,
destination_warehouse_id,
bill_type_id,
created_on_utc,
po_no,
po_date
)
VALUES (
p_id,
p_company_id,
p_vendor_id,
p_credit_account_id,
p_debit_account_id,
v_bill_voucher,
p_bill_number,
p_bill_date,
p_due_date,
p_payment_term,
p_taxable_amount,
p_cgst_amount,
p_sgst_amount,
p_igst_amount,
p_tds_amount,
p_total_amount,
p_discount,
p_fees,
p_round_off,
p_currency_id,
p_note,
p_bill_status_id,
p_created_by,
p_source_warehouse_id,
p_destination_warehouse_id,
p_bill_type_id,
(CURRENT_TIMESTAMP AT TIME ZONE 'Asia/Kolkata')::timestamp,
p_po_no,
p_po_date
);
-- Log success message
RAISE NOTICE 'Draft Bill header inserted successfully with Bill Voucher: % ', v_bill_voucher ;
END;
$procedure$
-- Procedure: create_schedule_bills
CREATE OR REPLACE PROCEDURE public.create_schedule_bills(IN p_company_id uuid, IN p_frequency_cycle text, IN p_starts_from date, IN p_end_date date, IN p_created_by uuid, IN p_bill_header_id uuid DEFAULT NULL::uuid, IN p_draft_bill_header_id uuid DEFAULT NULL::uuid, IN p_month_of_year integer DEFAULT NULL::integer, IN p_day_of_month integer DEFAULT NULL::integer, IN p_day_of_week integer DEFAULT NULL::integer, IN p_time_of_day interval DEFAULT '00:00:00'::interval)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_recurring_schedule_id uuid := gen_random_uuid(); -- New ID for recurring_bill_schedules
v_schedule_status text;
-- Normalized copies (treat all-zero GUID as NULL)
v_bill_header_id uuid := NULLIF(p_bill_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
v_draft_bill_header_id uuid := NULLIF(p_draft_bill_header_id, '00000000-0000-0000-0000-000000000000'::uuid);
v_day_of_week integer := COALESCE(p_day_of_week, 0);
v_day_of_month integer := COALESCE(p_day_of_month, 0);
v_month_of_year integer := COALESCE(p_month_of_year, 0);
v_time_of_day interval := COALESCE(p_time_of_day, '00:00:00'::interval);
BEGIN
----------------------------------------------------------------
-- Validate: exactly ONE of bill_header_id / draft_bill_header_id
----------------------------------------------------------------
IF (v_bill_header_id IS NULL AND v_draft_bill_header_id IS NULL) THEN
RAISE EXCEPTION 'Either bill_header_id or draft_bill_header_id must be provided.';
ELSIF (v_bill_header_id IS NOT NULL AND v_draft_bill_header_id IS NOT NULL) THEN
RAISE EXCEPTION 'Provide only one of bill_header_id or draft_bill_header_id, not both.';
END IF;
----------------------------------------------------------------
-- Determine the initial schedule status
----------------------------------------------------------------
IF p_starts_from > CURRENT_DATE THEN
v_schedule_status := 'inactive';
ELSE
v_schedule_status := 'active';
END IF;
----------------------------------------------------------------
-- Insert into the recurring_bill_schedules table
----------------------------------------------------------------
INSERT INTO public.recurring_bill_schedules (
id,
bill_header_id,
draft_bill_header_id,
company_id,
frequency_cycle,
schedule_status,
starts_from,
end_date,
created_on_utc,
created_by,
is_deleted
)
VALUES (
v_recurring_schedule_id,
v_bill_header_id,
v_draft_bill_header_id,
p_company_id,
p_frequency_cycle,
v_schedule_status,
p_starts_from,
p_end_date,
NOW(),
p_created_by,
false
);
----------------------------------------------------------------
-- Insert into recurrence_bill_schedule_details
----------------------------------------------------------------
IF p_frequency_cycle = 'Daily' THEN
INSERT INTO public.recurrence_bill_schedule_details (
schedule_id,
frequency_cycle,
time_of_day,
is_deleted
)
VALUES (
v_recurring_schedule_id,
p_frequency_cycle,
v_time_of_day,
false
);
ELSIF p_frequency_cycle = 'Weekly' THEN
INSERT INTO public.recurrence_bill_schedule_details (
schedule_id,
frequency_cycle,
day_of_week,
time_of_day,
is_deleted
)
VALUES (
v_recurring_schedule_id,
p_frequency_cycle,
v_day_of_week,
v_time_of_day,
false
);
ELSIF p_frequency_cycle = 'Monthly' THEN
INSERT INTO public.recurrence_bill_schedule_details (
schedule_id,
frequency_cycle,
day_of_month,
time_of_day,
is_deleted
)
VALUES (
v_recurring_schedule_id,
p_frequency_cycle,
v_day_of_month,
v_time_of_day,
false
);
ELSIF p_frequency_cycle = 'Yearly' THEN
INSERT INTO public.recurrence_bill_schedule_details (
schedule_id,
frequency_cycle,
month_of_year,
day_of_month,
time_of_day,
is_deleted
)
VALUES (
v_recurring_schedule_id,
p_frequency_cycle,
v_month_of_year,
v_day_of_month,
v_time_of_day,
false
);
ELSE
RAISE NOTICE 'Invalid frequency cycle: %', p_frequency_cycle;
END IF;
RAISE NOTICE 'Recurring bill schedule created successfully with ID: %, Status: %',
v_recurring_schedule_id, v_schedule_status;
END;
$procedure$
-- View: bill_details_with_payments
SELECT bh.bill_number,
bh.bill_date,
bd.product_id,
bd.quantity,
bd.price,
bd.total_amount AS bill_amount,
COALESCE(bp.paid_amount, (0)::numeric) AS paid_amount,
(bd.total_amount - COALESCE(bp.paid_amount, (0)::numeric)) AS remaining_amount,
bd.cgst_amount,
bd.sgst_amount,
bd.igst_amount,
bd.taxable_amount,
bd.discount,
bd.fees
FROM (((bill_details bd
JOIN bill_headers bh ON ((bd.bill_header_id = bh.id)))
LEFT JOIN bill_payment_details bpd ON ((bd.bill_header_id = bpd.bill_header_id)))
LEFT JOIN bill_payment_headers bp ON ((bpd.bill_payment_header_id = bp.id)))
WHERE ((bh.is_deleted = false) AND (bd.is_deleted = false));
-- View: bill_approval_level_view
SELECT company_id,
account_id,
status_id,
COALESCE(( SELECT bill_account_approval_levels.approval_level_required
FROM bill_account_approval_levels
WHERE ((bill_account_approval_levels.company_id = i.company_id) AND (bill_account_approval_levels.account_id = i.account_id) AND (bill_account_approval_levels.status_id = i.status_id) AND (bill_account_approval_levels.is_deleted = false))
LIMIT 1), ( SELECT bill_workflow.approval_level
FROM bill_workflow
WHERE ((bill_workflow.company_id = i.company_id) AND (bill_workflow.status = i.status_id) AND (bill_workflow.is_deleted = false) AND (bill_workflow.is_enabled = true))
LIMIT 1)) AS approval_level_required
FROM ( SELECT bill_account_approval_levels.company_id,
bill_account_approval_levels.account_id,
bill_account_approval_levels.status_id
FROM bill_account_approval_levels
UNION
SELECT bill_workflow.company_id,
NULL::uuid AS account_id,
bill_workflow.status AS status_id
FROM bill_workflow) i;
-- View: bill_with_details
SELECT bh.bill_number,
bh.bill_date,
bd.product_id,
bd.quantity,
bd.price,
bd.total_amount AS bill_amount,
COALESCE(bp.paid_amount, (0)::numeric) AS paid_amount,
(bd.total_amount - COALESCE(bp.paid_amount, (0)::numeric)) AS remaining_amount,
bd.cgst_amount,
bd.sgst_amount,
bd.igst_amount,
bd.taxable_amount,
bd.discount,
bd.fees
FROM (((bill_details bd
JOIN bill_headers bh ON ((bd.bill_header_id = bh.id)))
LEFT JOIN bill_payment_details bpd ON ((bd.bill_header_id = bpd.bill_header_id)))
LEFT JOIN bill_payment_headers bp ON ((bpd.bill_payment_header_id = bp.id)))
WHERE ((bh.is_deleted = false) AND (bd.is_deleted = false));
-- View: vw_bill_approval_permissions
SELECT company_id,
status_id,
user_id,
approval_level,
account_id,
account_approval_level,
account_status_id
FROM ( SELECT baua.company_id,
baua.status_id,
baua.user_id,
baua.approval_level,
baua.account_id,
baua.approval_level AS account_approval_level,
baua.status_id AS account_status_id
FROM bill_approval_user_account baua
WHERE (baua.is_deleted = false)
UNION ALL
SELECT bauc.company_id,
bauc.status_id,
bauc.user_id,
bauc.approval_level,
NULL::uuid AS account_id,
NULL::integer AS account_approval_level,
bauc.status_id AS account_status_id
FROM bill_approval_user_company bauc
WHERE ((bauc.is_deleted = false) AND (NOT (EXISTS ( SELECT 1
FROM bill_approval_user_account baua
WHERE ((baua.company_id = bauc.company_id) AND (baua.status_id = bauc.status_id) AND (baua.user_id = bauc.user_id) AND (baua.is_deleted = false))))))) sub;