| Type | Name | Status | PK | FK | Columns | Index | Script | Diff Script |
|---|---|---|---|---|---|---|---|---|
| Table | residents | Missing in Target |
|
|||||
| Table | countries | Missing in Target |
|
|||||
| Table | event_occurrence_status | Missing in Target |
|
|||||
| Table | guest_approvals | Missing in Target |
|
|||||
| Table | community_directory_verification_statuses | Missing in Target |
|
|||||
| Table | visitor_phones | Missing in Target |
|
|||||
| Table | service_provider_ticket_categories | Missing in Target |
|
|||||
| Table | visitors | Missing in Target |
|
|||||
| Table | poll_votes | Missing in Target |
|
|||||
| Table | __EFMigrationsHistory | Missing in Target |
|
|||||
| Table | occupant_types | Missing in Target |
|
|||||
| Table | organizations | Missing in Target |
|
|||||
| Table | community_calenders | Missing in Target |
|
|||||
| Table | buildings | Missing in Target |
|
|||||
| Table | committee_members | Missing in Target |
|
|||||
| Table | UserToRoleMapping | Missing in Target |
|
|||||
| Table | ticket_number_sequences | Missing in Target |
|
|||||
| Table | pre_approved_entries | Missing in Target |
|
|||||
| Table | meeting_agenda_items | Missing in Target |
|
|||||
| Table | ticket_logs | Missing in Target |
|
|||||
| Table | noc_approvals | Missing in Target |
|
|||||
| Table | vehicles | Missing in Target |
|
|||||
| Table | unit_statuses | Missing in Target |
|
|||||
| Table | states | Missing in Target |
|
|||||
| Table | service_provider_logs | Missing in Target |
|
|||||
| Table | noc_certificates | Missing in Target |
|
|||||
| Table | sub_categories | Missing in Target |
|
|||||
| Table | delivery_companies | Missing in Target |
|
|||||
| Table | noc_due_snapshots | Missing in Target |
|
|||||
| Table | noc_requests | Missing in Target |
|
|||||
| Table | tickets | Missing in Target |
|
|||||
| Table | unit_vehicle_limits | Missing in Target |
|
|||||
| Table | noc_types | Missing in Target |
|
|||||
| Table | visitor_statuses | Missing in Target |
|
|||||
| Table | service_provider_logs_2022_2023 | Missing in Target |
|
|||||
| Table | banks | Missing in Target |
|
|||||
| Table | visitor_vehicles | Missing in Target |
|
|||||
| Table | occupancy_types | Missing in Target |
|
|||||
| Table | noc_approval_roles | Missing in Target |
|
|||||
| Table | water_tanker_deliveries | Missing in Target |
|
|||||
| Table | units | Missing in Target |
|
|||||
| Table | visitor_approvals | Missing in Target |
|
|||||
| Table | visitor_types | Missing in Target |
|
|||||
| Table | visitor_sp_company_visits | Missing in Target |
|
|||||
| Table | service_provider_verifications | Missing in Target |
|
|||||
| Table | resident_tokens | Missing in Target |
|
|||||
| Table | pre_approved_schedule_rules | Missing in Target |
|
|||||
| Table | visitor_details | Missing in Target |
|
|||||
| Table | visitor_apartments | Missing in Target |
|
|||||
| Table | visitor_emails | Missing in Target |
|
|||||
| Table | floors | Missing in Target |
|
|||||
| Table | gate_types | Missing in Target |
|
|||||
| Table | emergency_contacts | Missing in Target |
|
|||||
| Table | resident_types | Missing in Target |
|
|||||
| Table | visitor_identities | Missing in Target |
|
|||||
| Table | visitor_logs | Missing in Target |
|
|||||
| Table | member_additional_details | Missing in Target |
|
|||||
| Table | push_notifications | Missing in Target |
|
|||||
| Table | multi_unit_visits | Missing in Target |
|
|||||
| Table | meeting_action_items | Missing in Target |
|
|||||
| Table | meeting_notes | Missing in Target |
|
|||||
| Table | event_status | Missing in Target |
|
|||||
| Table | event_types | Missing in Target |
|
|||||
| Table | identity_types | Missing in Target |
|
|||||
| Table | ticket_comment_documents | Missing in Target |
|
|||||
| Table | gates | Missing in Target |
|
|||||
| Table | ticket_comments | Missing in Target |
|
|||||
| Table | users | Missing in Target |
|
|||||
| Table | event_occurrences | Missing in Target |
|
|||||
| Table | has_default | Missing in Target |
|
|||||
| Table | service_provider_apartments | Missing in Target |
|
|||||
| Table | community_directory_location_types | Missing in Target |
|
|||||
| Table | community_directory_source_types | Missing in Target |
|
|||||
| Table | community_directory_listing_types | Missing in Target |
|
|||||
| Table | community_directory_categories | Missing in Target |
|
|||||
| Table | community_directory_status_types | Missing in Target |
|
|||||
| Table | pins | Missing in Target |
|
|||||
| Table | building_types | Missing in Target |
|
|||||
| Table | facility_bookings | Missing in Target |
|
|||||
| Table | resident_units | Missing in Target |
|
|||||
| Table | resident_requests | Missing in Target |
|
|||||
| Table | ticket_priorities | Missing in Target |
|
|||||
| Table | cities | Missing in Target |
|
|||||
| Table | companies | Missing in Target |
|
|||||
| Table | delivery_personnel | Missing in Target |
|
|||||
| Table | events | Missing in Target |
|
|||||
| Table | verification_types | Missing in Target |
|
|||||
| Table | apartments | Missing in Target |
|
|||||
| Table | apartment_requests | Missing in Target |
|
|||||
| Table | ticket_statuses | Missing in Target |
|
|||||
| Table | polls | Missing in Target |
|
|||||
| Table | vehicle_types | Missing in Target |
|
|||||
| Table | UserToPaidModulesMapping | Missing in Target |
|
|||||
| Table | ticket_documents | Missing in Target |
|
|||||
| Table | calendars | Missing in Target |
|
|||||
| Table | apartment_types | Missing in Target |
|
|||||
| Table | addresses | Missing in Target |
|
|||||
| Table | service_provider_companies | Missing in Target |
|
|||||
| Table | service_provider_company_categories | Missing in Target |
|
|||||
| Table | service_provider_company_subtypes | Missing in Target |
|
|||||
| Table | service_providers | Missing in Target |
|
|||||
| Table | unit_service_providers | Missing in Target |
|
|||||
| Table | ticket_categories | Missing in Target |
|
|||||
| Table | possible_visitors | Missing in Target |
|
|||||
| Table | user_roles | Missing in Target |
|
|||||
| Table | community_documents | Missing in Target |
|
|||||
| Table | document_types | Missing in Target |
|
|||||
| Table | user_fcm_tokens | Missing in Target |
|
|||||
| Table | security_guard_apartments | Missing in Target |
|
|||||
| Table | service_provider_users | Missing in Target |
|
|||||
| Table | community_directory_listings | Missing in Target |
|
|||||
| Table | community_directory_contacts | Missing in Target |
|
|||||
| Table | community_directory_recommendations | Missing in Target |
|
|||||
| Table | decisions | Missing in Target |
|
|||||
| Table | poll_types | Missing in Target |
|
|||||
| Table | decision_outcomes | Missing in Target |
|
|||||
| Table | poll_statuses | Missing in Target |
|
|||||
| Table | poll_options | Missing in Target |
|
|||||
| Table | visitor_delivery_company | Missing in Target |
|
|||||
| Table | ticket_workflow | Missing in Target |
|
|||||
| Table | categories | Missing in Target |
|
|||||
| Table | complaint | Missing in Target |
|
|||||
| Table | delivery_company_categories | Missing in Target |
|
|||||
| Table | ticket_fors | Missing in Target |
|
|||||
| Table | notice_categories | Missing in Target |
|
|||||
| Table | notice_priorities | Missing in Target |
|
|||||
| Table | notices | Missing in Target |
|
|||||
| Table | schema_versions | Missing in Target |
|
|||||
| Table | service_provider_sub_types | Missing in Target |
|
|||||
| Table | service_provider_types | Missing in Target |
|
|||||
| Table | service_provider_addresses | Missing in Target |
|
|||||
| Table | meeting_occurrences | Missing in Target |
|
|||||
| Table | meeting_statuses | Missing in Target |
|
|||||
| Table | occurrence_participants | Missing in Target |
|
|||||
| Table | roles | Missing in Target |
|
|||||
| Table | portfolios | Missing in Target |
|
|||||
| Table | rsvp_statuses | Missing in Target |
|
|||||
| Table | attendance_statuses | Missing in Target |
|
|||||
| Table | user_device_tokens | Missing in Target |
|
|||||
| Table | visit_types | Missing in Target |
|
|||||
| Table | unit_types | Missing in Target |
|
|||||
| Table | service_provider_logs_2024_2025 | Missing in Target |
|
|||||
| Table | meeting_modes | Missing in Target |
|
|||||
| Table | gate_passes | Missing in Target |
|
|||||
| Table | participant_roles | Missing in Target |
|
|||||
| Table | raise_tickets | Missing in Target |
|
|||||
| Table | resident_request_statuses | Missing in Target |
|
|||||
| Table | service_provider_logs_2023_2024 | Missing in Target |
|
|||||
| Table | community_directory_locations | Missing in Target |
|
|||||
| Table | option_types | Missing in Target |
|
|||||
| Table | poll_decisions | Missing in Target |
|
|||||
| Table | poll_eligibility_types | Missing in Target |
|
|||||
| Table | ticket_service_provider_otps | Missing in Target |
|
|||||
| Table | service_provider_logs_2025_2026 | Missing in Target |
|
|||||
| 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 | generate_random_pin | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.generate_random_pin()
2
RETURNS character varying
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
pin_code VARCHAR(6);
7
BEGIN
8
-- Generate a random 6-digit number
9
pin_code := LPAD(CAST(FLOOR(RANDOM() * 1000000) AS VARCHAR), 6, '0');
10
RETURN pin_code;
11
END;
12
$function$
|
|||||
| Function | get_active_committee_members_by_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_active_committee_members_by_apartment(p_apartment_id uuid)
2
RETURNS TABLE(id integer, apartment_id uuid, role_id integer, user_id uuid, member_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
cm.id,
9
cm.apartment_id,
10
cm.role_id,
11
cm.user_id,
12
u.first_name || ' ' || u.last_name AS member_name
13
FROM
14
committee_members cm
15
inner join users u on u.id = cm.user_id
16
WHERE
17
cm.is_deleted = FALSE
18
AND cm.apartment_id = p_apartment_id
19
AND cm.effective_start_date <= NOW()
20
AND (cm.effective_end_date IS NULL OR cm.effective_end_date >= NOW());
21
END;
22
$function$
|
|||||
| Function | get_current_visitors_last24h | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_current_visitors_last24h(p_apartment_id uuid)
2
RETURNS TABLE(visitor_id integer, first_name text, last_name text, email text, visiting_from text, contact_number text, vehicle_number text, entry_time timestamp without time zone, visitor_type_id integer, identity_type_id integer, identity_number text)
3
LANGUAGE plpgsql
4
STABLE
5
AS $function$
6
BEGIN
7
RETURN QUERY
8
SELECT
9
v.id AS visitor_id,
10
v.first_name,
11
v.last_name,
12
v.email,
13
v.visiting_from,
14
v.contact_number,
15
v.vehicle_number,
16
vl.entry_time,
17
v.visitor_type_id,
18
v.identity_type_id,
19
v.identity_number
20
FROM
21
public.visitors v
22
INNER JOIN public.visitor_logs vl ON vl.visitor_id = v.id
23
WHERE
24
v.apartment_id = p_apartment_id
25
AND vl.exit_time IS NULL
26
AND vl.entry_time >= (NOW() AT TIME ZONE 'UTC') - INTERVAL '24 hours'
27
AND vl.is_deleted = false
28
AND v.is_deleted = false;
29
END;
30
$function$
|
|||||
| Function | get_resident_fcm_tokens_by_visitor_log_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_resident_fcm_tokens_by_visitor_log_id(p_visitorlogid integer)
2
RETURNS TABLE(id integer, user_ids uuid[], visitor_id integer, first_name text, last_name text, fcm_tokens text[], device_ids text[])
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
ROW_NUMBER() OVER ()::INT AS id, -- synthetic serial-like column
7
ARRAY_AGG(DISTINCT r.user_id) AS user_ids,
8
v.id AS visitor_id,
9
v.first_name AS first_name,
10
v.last_name AS last_name,
11
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.fcm_token), NULL) AS fcm_tokens,
12
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.device_id), NULL) AS device_ids
13
FROM multi_unit_visits muv
14
INNER JOIN resident_units ru ON muv.unit_id = ru.unit_id
15
INNER JOIN residents r ON ru.resident_id = r.id
16
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
17
INNER JOIN visitors v ON v.id = vl.visitor_id
18
LEFT JOIN user_fcm_tokens uft ON uft.user_id = r.user_id
19
WHERE muv.visitor_log_id = p_visitorlogid
20
AND muv.is_deleted = FALSE
21
AND ru.is_deleted = FALSE
22
AND r.is_deleted = FALSE
23
GROUP BY v.id, v.first_name, v.last_name;
24
$function$
|
|||||
| Function | get_user_ids_by_visitor_log_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_user_ids_by_visitor_log_id(p_visitorlogid integer)
2
RETURNS TABLE(id integer, user_ids uuid[], visitor_id integer, first_name text, last_name text)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
ROW_NUMBER() OVER ()::INT AS id, -- synthetic serial-like column
7
ARRAY_AGG(DISTINCT r.user_id) AS user_ids,
8
v.id AS visitor_id,
9
v.first_name As first_name,
10
v.last_name As last_name
11
FROM multi_unit_visits muv
12
INNER JOIN resident_units ru ON muv.unit_id = ru.unit_id
13
INNER JOIN residents r ON ru.resident_id = r.id
14
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
15
INNER JOIN visitors v ON v.id = vl.visitor_id
16
WHERE muv.visitor_log_id = p_VisitorLogId
17
AND muv.is_deleted = FALSE
18
AND ru.is_deleted = FALSE
19
AND r.is_deleted = FALSE
20
GROUP BY v.id, v.first_name, v.last_name;
21
$function$
|
|||||
| Function | get_visitor_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitor_logs(p_apartment_id uuid, p_visitor_id integer, p_visitor_type_id integer)
2
RETURNS TABLE(visitor_log_id integer, visitor_id integer, visitor_first_name text, unit_id integer, unit_name text, visitor_type_id integer, visitor_type_name text, visiting_from text, current_status_id integer, entry_time timestamp without time zone, exit_time timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
vl.id AS visitor_log_id,
9
vl.visitor_id,
10
v.first_name AS visitor_first_name,
11
u.id AS unit_id,
12
u.name AS unit_name,
13
vl.visitor_type_id,
14
(SELECT vt.name::TEXT -- Explicitly cast visitor type name to TEXT
15
FROM visitor_types vt
16
WHERE vt.id = vl.visitor_type_id
17
LIMIT 1) AS visitor_type_name,
18
vl.visiting_from,
19
vl.current_status_id,
20
vl.entry_time,
21
vl.exit_time
22
FROM visitor_logs vl
23
INNER JOIN visitors v ON vl.visitor_id = v.id
24
INNER JOIN visitor_unit_logs vut ON vl.id = vut.visitor_log_id
25
INNER JOIN units u ON vut.unit_id = u.id
26
WHERE v.apartment_id = p_apartment_id
27
AND vl.visitor_id = p_visitor_id
28
AND vl.visitor_type_id = p_visitor_type_id
29
ORDER BY vl.id;
30
END;
31
$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 | unit_resident_fcm_tokens | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.unit_resident_fcm_tokens(p_unit_id integer)
2
RETURNS TABLE(resident_id integer, user_id uuid, device_id text, fcm_token text)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
ru.resident_id,
7
r.user_id,
8
uft.device_id,
9
uft.fcm_token
10
FROM resident_units ru
11
INNER JOIN residents r ON ru.resident_id = r.id
12
LEFT JOIN user_fcm_tokens uft ON uft.user_id = r.user_id
13
WHERE ru.unit_id = p_unit_id
14
AND ru.is_deleted = FALSE
15
AND r.is_deleted = FALSE
16
AND (uft.fcm_token IS NOT NULL OR uft.device_id IS NOT NULL);
17
$function$
|
|||||
| Function | update_visitor_log | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_visitor_log(p_visitor_log_id integer)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
UPDATE public.visitor_logs
7
SET
8
exit_time = NOW(),
9
visitor_status_id = 4,
10
modified_on_utc = NOW()
11
WHERE id = p_visitor_log_id;
12
END;
13
$function$
|
|||||
| Function | update_visitor_approval | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_visitor_approval(p_visitor_approval_id integer, p_visit_type_id integer, p_start_date date, p_end_date date, p_entry_time time without time zone, p_exit_time time without time zone, p_vehicle_number text, p_company_name text, p_created_by uuid)
2
RETURNS TABLE(visitor_approve_id integer, visitor_id integer, visit_type_id integer, start_date date, end_date date, entry_time time without time zone, exit_time time without time zone, vehicle_number text, company_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
-- Update the record
7
UPDATE public.visitor_approvals va
8
SET
9
visit_type_id = p_visit_type_id,
10
start_date = COALESCE(p_start_date, va.start_date),
11
end_date = COALESCE(p_end_date, va.end_date),
12
entry_time = COALESCE(p_entry_time, va.entry_time),
13
exit_time = COALESCE(p_exit_time, va.exit_time),
14
vehicle_number = COALESCE(p_vehicle_number, va.vehicle_number),
15
company_name = COALESCE(p_company_name, va.company_name),
16
modified_on_utc = NOW(),
17
modified_by = p_created_by
18
WHERE va.id = p_visitor_approval_id;
19
20
-- Check if update happened
21
IF NOT FOUND THEN
22
RAISE EXCEPTION 'Visitor approval not found for id: %', p_visitor_approval_id;
23
END IF;
24
25
-- Return the updated row
26
RETURN QUERY
27
SELECT
28
va.id AS visitor_approve_id,
29
va.visitor_id,
30
va.visit_type_id,
31
va.start_date,
32
va.end_date,
33
va.entry_time,
34
va.exit_time,
35
va.vehicle_number,
36
va.company_name,
37
va.created_on_utc,
38
va.modified_on_utc,
39
va.created_by,
40
va.modified_by
41
FROM public.visitor_approvals va
42
WHERE va.id = p_visitor_approval_id;
43
END;
44
$function$
|
|||||
| Function | upsert_user_fcm_token | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.upsert_user_fcm_token(p_user_id uuid, p_device_id text, p_fcm_token text, p_platform text)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_id int4;
7
BEGIN
8
INSERT INTO public.user_fcm_tokens (user_id, device_id, fcm_token, platform, upsert_on_utc)
9
VALUES (p_user_id, p_device_id, p_fcm_token, p_platform, now())
10
ON CONFLICT (user_id, device_id)
11
DO UPDATE SET
12
fcm_token = EXCLUDED.fcm_token,
13
platform = EXCLUDED.platform,
14
upsert_on_utc = now()
15
RETURNING id INTO v_id;
16
17
RETURN v_id;
18
END;
19
$function$
|
|||||
| Function | get_all_committee_members_by_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_committee_members_by_apartment(p_apartment_id uuid)
2
RETURNS TABLE(id integer, user_id uuid, member_name text, effective_start_date timestamp without time zone, effective_end_date timestamp without time zone, role_id integer, role_name character varying, portfolio_id integer, portfolio_name text)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
cm.id,
7
cm.user_id,
8
CONCAT(res.first_name, ' ', res.last_name) AS member_name,
9
cm.effective_start_date,
10
cm.effective_end_date,
11
cm.role_id,
12
r.name AS role_name,
13
cm.portfolio_id,
14
p.name AS portfolio_name
15
FROM
16
committee_members cm
17
LEFT JOIN roles r ON r.id = cm.role_id
18
LEFT JOIN portfolios p ON p.id = cm.portfolio_id
19
LEFT JOIN (
20
SELECT DISTINCT ON (user_id) user_id, first_name, last_name
21
FROM residents
22
WHERE is_deleted = false
23
ORDER BY user_id, id
24
) res ON res.user_id = cm.user_id
25
WHERE
26
cm.apartment_id = p_apartment_id
27
AND cm.is_deleted = false;
28
$function$
|
|||||
| Function | get_all_roles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_roles()
2
RETURNS TABLE(id integer, name character varying, description character varying)
3
LANGUAGE sql
4
AS $function$
5
SELECT id, name, description
6
FROM public.roles
7
WHERE true
8
ORDER BY id;
9
$function$
|
|||||
| Function | get_water_tanker_summary | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_water_tanker_summary(p_company_id uuid, p_start_date date, p_end_date date)
2
RETURNS TABLE(start_date date, end_date date, total_tankers integer, total_liters numeric)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
p_start_date,
9
p_end_date,
10
COUNT(*)::INT AS total_tankers, -- β
cast to INT
11
COALESCE(SUM(actual_received_liters)::NUMERIC(14,2), 0) AS total_liters
12
FROM public.water_tanker_deliveries
13
WHERE company_id = p_company_id
14
AND delivery_date BETWEEN p_start_date AND p_end_date
15
AND NOT is_deleted;
16
END;
17
$function$
|
|||||
| Function | get_unit_info_by_user_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_info_by_user_apartment(p_user_id uuid, p_apartment_id uuid)
2
RETURNS TABLE(id integer, unit_id integer, customer_id uuid, unit_name text, user_name text, apartment_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ru.id, -- added resident_units primary key
9
ru.unit_id,
10
u.customer_id,
11
u.name,
12
usr.first_name || ' ' || usr.last_name AS user_name,
13
r.apartment_id
14
FROM resident_units ru
15
INNER JOIN residents r ON ru.resident_id = r.id
16
INNER JOIN units u ON ru.unit_id = u.id
17
INNER JOIN users usr ON r.user_id = usr.id
18
WHERE r.user_id = p_user_id
19
AND r.apartment_id = p_apartment_id;
20
END;
21
$function$
|
|||||
| Function | get_portfolios_by_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_portfolios_by_apartment(p_apartment_id uuid)
2
RETURNS TABLE(id integer, apartment_id uuid, name text, description text)
3
LANGUAGE sql
4
AS $function$
5
SELECT id, apartment_id, name, description
6
FROM public.portfolios
7
WHERE apartment_id = p_apartment_id
8
ORDER BY id;
9
$function$
|
|||||
| Function | get_committee_members_by_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_committee_members_by_apartment(p_apartment_id uuid)
2
RETURNS TABLE(id integer, apartment_id uuid, role_id integer, user_id uuid, member_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
cm.id,
9
cm.apartment_id,
10
cm.role_id,
11
cm.user_id,
12
u.first_name || ' ' || u.last_name AS member_name
13
FROM
14
committee_members cm
15
inner join users u on u.id = cm.user_id
16
WHERE
17
cm.is_deleted = FALSE
18
AND cm.apartment_id = p_apartment_id
19
AND cm.effective_start_date <= NOW()
20
AND (cm.effective_end_date IS NULL OR cm.effective_end_date >= NOW());
21
22
END;
23
$function$
|
|||||
| Function | get_units_with_primary_owner_name | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_units_with_primary_owner_name(p_apartment_id uuid)
2
RETURNS TABLE(id integer, customer_id uuid, unit_id integer, unit_name text, owner_first_name text, owner_last_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ROW_NUMBER() OVER (ORDER BY u.name)::int AS id, -- 1,2,3...
9
u.customer_id AS customer_id,
10
u.id AS unit_id,
11
u.name AS unit_name,
12
r.first_name AS owner_first_name,
13
r.last_name AS owner_last_name
14
FROM public.units u
15
JOIN public.resident_units ru
16
ON ru.unit_id = u.id
17
AND (ru.is_deleted = false OR ru.is_deleted IS NULL)
18
AND ru.is_primary_owner = true
19
JOIN public.residents r
20
ON r.id = ru.resident_id
21
AND (r.is_deleted = false OR r.is_deleted IS NULL)
22
WHERE (u.is_deleted = false OR u.is_deleted IS NULL)
23
AND u.apartment_id = p_apartment_id
24
ORDER BY u.name;
25
END;
26
$function$
|
|||||
| Function | get_visitors_of_unit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitors_of_unit(p_unit_id integer, p_datetime timestamp without time zone)
2
RETURNS TABLE(id integer, visitor_log_id integer, name text, visiting_from text, checked_in_at timestamp without time zone, checked_out_at timestamp without time zone, status text, status_id integer, visitor_type text, visitor_type_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visit_date DATE := p_datetime::date;
7
BEGIN
8
RETURN QUERY
9
SELECT
10
ROW_NUMBER() OVER ()::integer AS id, -- Cast ROW_NUMBER() result to integer
11
vl.id AS visitor_log_id,
12
TRIM(v.first_name || ' ' || COALESCE(v.last_name, '')) AS name,
13
vl.visiting_from,
14
vl.entry_time AS checked_in_at,
15
vl.exit_time AS checked_out_at,
16
vs.name::text AS status,
17
vs.id AS status_id,
18
vt.name::text AS visitor_type,
19
vt.id AS visitor_type_id
20
FROM
21
multi_unit_visits muv
22
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
23
INNER JOIN visitors v ON v.id = vl.visitor_id
24
INNER JOIN visitor_statuses vs ON vs.id = muv.visitor_statuses
25
INNER JOIN visitor_types vt ON vt.id = vl.visitor_type_id
26
WHERE
27
muv.unit_id = p_unit_id
28
AND DATE(vl.entry_time) = v_visit_date
29
AND muv.is_deleted = FALSE
30
AND vl.is_deleted = FALSE
31
AND v.is_deleted = FALSE;
32
END;
33
$function$
|
|||||
| Function | get_resident_details_by_user | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_resident_details_by_user(p_company_id uuid, p_user_id uuid)
2
RETURNS TABLE(id integer, resident_id integer, first_name text, last_name text, email text, resident_primary_owner boolean, unit_id integer, unit_name text, customer_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
r.id AS id,
9
r.id AS resident_id,
10
r.first_name,
11
r.last_name,
12
r.email,
13
r.is_primary_owner AS resident_primary_owner,
14
ru.unit_id,
15
u.name AS unit_name,
16
u.customer_id
17
FROM residents r
18
LEFT JOIN resident_units ru
19
ON ru.resident_id = r.id
20
AND ru.is_deleted = false
21
LEFT JOIN units u
22
ON u.id = ru.unit_id
23
AND u.apartment_id = p_company_id
24
WHERE r.user_id = p_user_id
25
AND r.is_deleted = false;
26
END;
27
$function$
|
|||||
| Function | get_vehicles_by_unit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_vehicles_by_unit(p_unit_id integer)
2
RETURNS TABLE(id integer, vehicle_number text, vehicle_type_id integer, unit_id integer, vehicle_rf_id text, vehicle_rf_id_secretcode text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean, created_by uuid, modified_by uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
v.id, v.vehicle_number, v.vehicle_type_id, v.unit_id,
9
v.vehicle_rf_id, v.vehicle_rf_id_secretcode,
10
v.created_on_utc, v.modified_on_utc, v.deleted_on_utc,
11
v.is_deleted, v.created_by, v.modified_by
12
FROM public.vehicles v
13
WHERE v.is_deleted = false
14
AND v.unit_id = p_unit_id;
15
END;
16
$function$
|
|||||
| Function | set_sequence_for_table | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.set_sequence_for_table(p_table_name text, p_sequence_name text)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
max_id bigint;
7
is_identity boolean;
8
sql_alter text;
9
sql_setval text;
10
BEGIN
11
-- Get max id from the table
12
EXECUTE format('SELECT COALESCE(MAX(id), 0) FROM %I', p_table_name) INTO max_id;
13
14
-- Check if the id column is identity
15
SELECT EXISTS (
16
SELECT 1 FROM information_schema.columns c
17
WHERE c.table_name = p_table_name
18
AND c.column_name = 'id'
19
AND c.is_identity = 'YES'
20
) INTO is_identity;
21
22
IF NOT is_identity THEN
23
-- ALTER TABLE to set default nextval only if not identity
24
sql_alter := format('ALTER TABLE %I ALTER COLUMN id SET DEFAULT nextval(''%I'')', p_table_name, p_sequence_name);
25
EXECUTE sql_alter;
26
END IF;
27
28
-- Debug: raise notice of max id
29
RAISE NOTICE 'Setting sequence % to max id % for table %', p_sequence_name, max_id, p_table_name;
30
31
-- Set the sequence value to max_id with is_called = true
32
sql_setval := format('SELECT setval(''%I'', %s, true)', p_sequence_name, max_id);
33
EXECUTE sql_setval;
34
END;
35
$function$
|
|||||
| Function | get_user_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_user_details(p_user_id uuid, p_apartment_id uuid)
2
RETURNS TABLE(user_id uuid, user_email text, resident_id integer, first_name text, last_name text, contact_number text, is_primary_owner boolean, unit_id integer, unit_name text, building_id integer, building_name text, floor_id integer, floor_name text, area numeric, bhk_type numeric, customer_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
u.id AS user_id,
9
u.email::text AS user_email, -- Fix: varchar β text
10
r.id AS resident_id,
11
r.first_name,
12
r.last_name,
13
r.contact_number,
14
r.is_primary_owner,
15
ru.unit_id,
16
un.name AS unit_name,
17
18
b.id AS building_id,
19
b.name AS building_name,
20
21
f.id AS floor_id,
22
f.name AS floor_name,
23
24
un.area,
25
un.bhk_type,
26
un.customer_id
27
FROM residents r
28
JOIN users u
29
ON u.id = r.user_id
30
31
LEFT JOIN resident_units ru
32
ON ru.resident_id = r.id
33
AND ru.is_deleted = false
34
35
LEFT JOIN units un
36
ON un.id = ru.unit_id
37
AND un.is_deleted = false
38
39
LEFT JOIN floors f
40
ON f.id = un.floor_id
41
AND f.is_deleted = false
42
43
LEFT JOIN buildings b
44
ON b.id = f.building_id
45
AND b.is_deleted = false
46
47
WHERE r.is_deleted = false
48
AND r.user_id = p_user_id
49
AND r.apartment_id = p_apartment_id;
50
END;
51
$function$
|
|||||
| Function | get_units_by_user | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_units_by_user(p_company_id uuid, p_user_id uuid)
2
RETURNS TABLE(id integer, resident_unit_id integer, unit_id integer, unit_name text, customer_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ru.id AS id, -- β
NEW COLUMN
9
ru.id AS resident_unit_id, -- existing
10
u.id AS unit_id,
11
u.name AS unit_name,
12
u.customer_id
13
FROM residents r
14
INNER JOIN resident_units ru
15
ON ru.resident_id = r.id
16
AND ru.is_deleted = FALSE
17
INNER JOIN units u
18
ON u.id = ru.unit_id
19
INNER JOIN apartments a
20
ON a.id = u.apartment_id
21
WHERE a.id = p_company_id -- β
company filter
22
AND r.user_id = p_user_id -- β
user filter
23
AND r.is_deleted = FALSE;
24
END;
25
$function$
|
|||||
| Function | get_inhouse_service_providers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_inhouse_service_providers(p_apartment_id uuid, p_types integer[])
2
RETURNS TABLE(id integer, service_provider_id integer, first_name text, last_name text, service_provider_type_id integer, service_provider_type_name character varying)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
CAST(ROW_NUMBER() OVER () AS int) AS id, -- index
9
sp.id AS service_provider_id, -- actual service provider ID
10
sp.first_name,
11
sp.last_name,
12
sp.service_provider_type_id,
13
spt.name AS service_provider_type_name
14
FROM public.service_providers sp
15
INNER JOIN public.service_provider_types spt
16
ON sp.service_provider_type_id = spt.id
17
WHERE sp.apartment_id = p_apartment_id
18
AND sp.service_provider_type_id = ANY(p_types)
19
AND sp.is_deleted = false
20
AND COALESCE(spt.is_deleted, false) = false;
21
END;
22
$function$
|
|||||
| Function | get_visitor_by_contact_or_email | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitor_by_contact_or_email(p_apartment_id uuid DEFAULT NULL::uuid, p_contact_number text DEFAULT NULL::text, p_email text DEFAULT NULL::text)
2
RETURNS TABLE(id integer, first_name text, last_name text, email text, contact_number text, visitor_type_id integer, vehicle_number text, identity_type_id integer, identity_number text, apartment_id uuid)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
v.id,
7
v.first_name,
8
v.last_name,
9
v.email,
10
v.contact_number,
11
v.visitor_type_id,
12
v.vehicle_number,
13
v.identity_type_id,
14
v.identity_number,
15
v.apartment_id
16
FROM public.visitors v
17
WHERE v.is_deleted = false
18
AND (p_apartment_id IS NULL OR v.apartment_id = p_apartment_id)
19
AND (
20
(p_contact_number IS NOT NULL AND trim(v.contact_number) = trim(p_contact_number))
21
OR
22
(p_email IS NOT NULL AND lower(trim(v.email)) = lower(trim(p_email)))
23
);
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
END LOOP;
17
18
-- Grant on sequences
19
FOR obj IN
20
SELECT sequence_name
21
FROM information_schema.sequences
22
WHERE sequence_schema = p_schema
23
LOOP
24
EXECUTE format('GRANT USAGE, SELECT ON SEQUENCE %I.%I TO %I;', p_schema, obj.sequence_name, p_user);
25
END LOOP;
26
27
-- Grant on functions
28
FOR obj IN
29
SELECT routine_name, specific_name
30
FROM information_schema.routines
31
WHERE routine_schema = p_schema
32
AND routine_type = 'FUNCTION'
33
LOOP
34
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.%I() TO %I;', p_schema, obj.routine_name, p_user);
35
-- Note: This only grants for functions without parameters. For overloaded functions, you may need to manually grant.
36
END LOOP;
37
38
-- Grant on procedures (Postgres 11+)
39
FOR obj IN
40
SELECT routine_name, specific_name
41
FROM information_schema.routines
42
WHERE routine_schema = p_schema
43
AND routine_type = 'PROCEDURE'
44
LOOP
45
EXECUTE format('GRANT EXECUTE ON PROCEDURE %I.%I() TO %I;', p_schema, obj.routine_name, p_user);
46
-- Note: As above, only for procedures without params.
47
END LOOP;
48
49
END;
50
$function$
|
|||||
| Function | get_apartment_visitors_in_timespan | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_apartment_visitors_in_timespan(p_apartment_id uuid, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
2
RETURNS TABLE(id integer, visitor_id integer, visitor_type_id integer, visitor_type text, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_person_type text, visitor_person_sub_type text, visitor_status_id integer, visitor_status text, building_id integer, building_name text, floor_id integer, floor_name text, unit_id integer, unit_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
CAST(ROW_NUMBER() OVER (ORDER BY vl.entry_time, v.id) AS int) AS serial_id,
9
v.id as visitor_id,
10
vt.id AS visitor_type_id,
11
'visitor'::text AS visitor_type,
12
v.first_name::text,
13
v.last_name::text,
14
vl.entry_time,
15
vl.exit_time,
16
vt.name::text AS visitor_person_type,
17
NULL::text AS visitor_person_sub_type,
18
CASE
19
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 3
20
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 4
21
ELSE vl.visitor_status_id
22
END AS visitor_status_id,
23
(
24
CASE
25
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 'Checked In'
26
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 'Checked Out'
27
ELSE COALESCE(vs.name::text, 'Unknown')
28
END
29
)::text AS visitor_status,
30
b.id AS building_id,
31
b.name::text AS building_name,
32
f.id AS floor_id,
33
f.name::text AS floor_name,
34
u.id AS unit_id,
35
u.name::text AS unit_name
36
FROM public.visitor_logs vl
37
JOIN public.visitors v ON vl.visitor_id = v.id
38
JOIN public.visitor_types vt ON v.visitor_type_id = vt.id
39
LEFT JOIN public.visitor_statuses vs ON vl.visitor_status_id = vs.id
40
-- 🔗 Join chain for hierarchy
41
LEFT JOIN public.multi_unit_visits muv ON muv.visitor_log_id = vl.id AND COALESCE(muv.is_deleted, FALSE) = FALSE
42
LEFT JOIN public.units u ON u.id = muv.unit_id AND COALESCE(u.is_deleted, FALSE) = FALSE
43
LEFT JOIN public.floors f ON f.id = u.floor_id AND COALESCE(f.is_deleted, FALSE) = FALSE
44
LEFT JOIN public.buildings b ON b.id = f.building_id AND COALESCE(b.is_deleted, FALSE) = FALSE
45
--LEFT JOIN public.apartments a ON a.id = COALESCE(v.apartment_id, b.apartment_id) AND COALESCE(a.is_deleted, FALSE) = FALSE
46
WHERE
47
vl.entry_time BETWEEN p_start_date AND p_end_date
48
AND COALESCE(vl.is_deleted, FALSE) = FALSE
49
AND COALESCE(v.is_deleted, FALSE) = FALSE
50
ORDER BY vl.entry_time, v.id;
51
END;
52
$function$
|
|||||
| Function | get_unit_related_details_by_user | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_related_details_by_user(p_user_id uuid)
2
RETURNS jsonb
3
LANGUAGE sql
4
AS $function$
5
6
SELECT COALESCE(
7
8
/* 1οΈβ£ Unit-based JSON (resident users) */
9
(
10
SELECT jsonb_build_object(
11
'id', u.id,
12
'name', u.name,
13
'unit_type_id', u.unit_type_id,
14
'phone_extension', u.phone_extention,
15
'area', u.area,
16
'bhk_type', u.bhk_type,
17
'occupant_type_id', u.occupant_type_id,
18
'occupancy_type_id', u.occupancy_type_id,
19
20
'residents', COALESCE(
21
(
22
SELECT jsonb_agg(
23
DISTINCT jsonb_build_object(
24
'id', r2.id,
25
'first_name', r2.first_name,
26
'last_name', r2.last_name,
27
'email', r2.email,
28
'contact_number', r2.contact_number,
29
'resident_type', rt.name
30
)
31
)
32
FROM resident_units ru2
33
JOIN residents r2
34
ON r2.id = ru2.resident_id
35
AND r2.is_deleted = false
36
JOIN resident_types rt
37
ON rt.id = r2.resident_type_id
38
AND rt.is_deleted = false
39
WHERE ru2.unit_id = u.id
40
AND ru2.is_deleted = false
41
),
42
'[]'::jsonb
43
),
44
45
'vehicles', COALESCE(
46
(
47
SELECT jsonb_agg(
48
DISTINCT jsonb_build_object(
49
'id', v.id,
50
'vehicle_number', v.vehicle_number,
51
'vehicle_type', vt.name
52
)
53
)
54
FROM vehicles v
55
JOIN vehicle_types vt
56
ON vt.id = v.vehicle_type_id
57
AND vt.is_deleted = false
58
WHERE v.unit_id = u.id
59
AND v.is_deleted = false
60
),
61
'[]'::jsonb
62
)
63
)
64
FROM residents r
65
JOIN resident_units ru
66
ON ru.resident_id = r.id
67
AND ru.is_deleted = false
68
JOIN units u
69
ON u.id = ru.unit_id
70
AND u.is_deleted = false
71
WHERE r.user_id = p_user_id
72
AND r.is_deleted = false
73
ORDER BY r.is_primary_owner DESC NULLS LAST
74
LIMIT 1
75
),
76
77
/* 2οΈβ£ Fallback: user-only JSON (non-resident users) */
78
(
79
SELECT jsonb_build_object(
80
'user_id', u.id,
81
'first_name', u.first_name,
82
'last_name', u.last_name,
83
'email', u.email,
84
'contact_number', u.contact_number
85
)
86
FROM users u
87
WHERE u.id = p_user_id
88
AND u.is_deleted = false
89
)
90
91
);
92
93
$function$
|
|||||
| Function | get_pre_approved_entries_by_unit_date_range | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_pre_approved_entries_by_unit_date_range(p_apartment_id uuid, p_unit_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
2
RETURNS TABLE(id integer, apartment_id uuid, unit_id integer, unit_name text, visitor_id bigint, visitor_name text, possible_visitor_id bigint, possible_visitor_name text, visitor_type_id integer, visitor_type_name text, notes text, is_once boolean, valid_from timestamp without time zone, valid_until timestamp without time zone, pin text, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
pae.id,
7
pae.apartment_id,
8
pae.unit_id,
9
u.name AS unit_name,
10
11
pae.visitor_id,
12
CASE
13
WHEN v.id IS NOT NULL
14
THEN concat_ws(' ', v.first_name, v.last_name)
15
ELSE NULL
16
END AS visitor_name,
17
18
pae.possible_visitor_id,
19
CASE
20
WHEN pv.id IS NOT NULL
21
THEN concat_ws(' ', pv.first_name, pv.last_name)
22
ELSE NULL
23
END AS possible_visitor_name,
24
25
v.visitor_type_id,
26
vt.name AS visitor_type_name,
27
28
pae.notes,
29
pae.is_once,
30
pae.valid_from,
31
pae.valid_until,
32
pae.pin,
33
34
pae.created_by,
35
concat_ws(' ',
36
COALESCE(cu.first_name, null),
37
COALESCE(cu.last_name, null)
38
) AS created_by_name,
39
pae.created_on_utc,
40
41
pae.modified_by,
42
concat_ws(' ',
43
COALESCE(mu.first_name, null),
44
COALESCE(mu.last_name, null)
45
) AS modified_by_name,
46
pae.modified_on_utc
47
FROM pre_approved_entries pae
48
LEFT JOIN units u
49
ON u.id = pae.unit_id
50
AND u.is_deleted = false
51
52
LEFT JOIN visitors v
53
ON v.id = pae.visitor_id
54
AND v.is_deleted = false
55
56
LEFT JOIN possible_visitors pv
57
ON pv.id = pae.possible_visitor_id
58
AND pv.is_deleted = false
59
60
LEFT JOIN visitor_types vt
61
ON vt.id = v.visitor_type_id
62
AND vt.is_deleted = false
63
64
LEFT JOIN users cu
65
ON cu.id = pae.created_by
66
AND cu.is_deleted = false
67
68
LEFT JOIN users mu
69
ON mu.id = pae.modified_by
70
AND mu.is_deleted = false
71
72
WHERE
73
pae.apartment_id = p_apartment_id
74
AND pae.unit_id = p_unit_id
75
AND pae.is_deleted = false
76
77
-- OVERLAPPING DATE LOGIC
78
AND pae.valid_from <= p_end_date
79
AND COALESCE(pae.valid_until, pae.valid_from) >= p_start_date
80
81
ORDER BY
82
pae.valid_from,
83
pae.id;
84
$function$
|
|||||
| Function | get_fcm_tokens_by_roles | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_fcm_tokens_by_roles(p_apartment_id uuid, p_role_id integer)
2
RETURNS TABLE(user_id uuid, device_id text, fcm_token text)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
SELECT DISTINCT ON (uft.user_id, uft.device_id)
7
u.id AS user_id,
8
uft.device_id,
9
uft.fcm_token
10
FROM users u
11
INNER JOIN fdw_common.user_roles ur
12
ON ur.user_id = u.id
13
INNER JOIN user_fcm_tokens uft
14
ON uft.user_id = u.id
15
WHERE ur.role_id = p_role_id
16
AND u.company_id = p_apartment_id
17
AND u.is_deleted = FALSE
18
AND uft.fcm_token IS NOT NULL
19
AND uft.device_id IS NOT NULL;
20
$function$
|
|||||
| 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, p_entry_time timestamp without time zone)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
v_service_provider_id int;
8
v_log_id int;
9
BEGIN
10
-- Step 1: Validate service provider by apartmentId and pin
11
SELECT id INTO v_service_provider_id
12
FROM public.service_providers
13
WHERE apartment_id = p_apartment_id
14
AND pin = p_pin
15
AND is_deleted = false
16
LIMIT 1;
17
18
IF v_service_provider_id IS NULL THEN
19
RAISE EXCEPTION 'Service provider not found for apartment % with pin %', p_apartment_id, p_pin
20
USING ERRCODE = 'no_data_found';
21
END IF;
22
23
-- Step 2: Insert service provider log (CheckedIn)
24
INSERT INTO public.service_provider_logs (
25
service_provider_id,
26
current_status_id,
27
entry_time,
28
created_on_utc,
29
created_by
30
)
31
VALUES (
32
v_service_provider_id,
33
1, -- CheckedIn
34
p_entry_time,
35
p_entry_time,
36
p_created_by
37
)
38
RETURNING id INTO v_log_id;
39
40
RETURN v_log_id;
41
END;
42
$function$
|
|||||
| Function | get_community_home_memory_snapshot | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_community_home_memory_snapshot(p_apartment_id uuid)
2
RETURNS TABLE(announcement jsonb, meeting jsonb, decision jsonb, poll jsonb)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
WITH params AS (
7
SELECT
8
now() AS now_ts,
9
now() - interval '3 months' AS last_3_months
10
)
11
SELECT
12
/* ------------------------------------------------------------------
13
1) Recent Announcement (last 3 months)
14
------------------------------------------------------------------ */
15
(
16
SELECT to_jsonb(a)
17
FROM (
18
SELECT
19
n.id,
20
n.title,
21
n.created_on_utc AS "created_on_utc"
22
FROM notices n
23
CROSS JOIN params p
24
WHERE n.apartment_id = p_apartment_id
25
AND n.is_deleted = false
26
AND n.created_on_utc >= p.last_3_months
27
ORDER BY n.created_on_utc DESC
28
LIMIT 1
29
) a
30
) AS announcement,
31
32
/* ------------------------------------------------------------------
33
2) Upcoming Meeting (nearest future occurrence)
34
------------------------------------------------------------------ */
35
(
36
SELECT to_jsonb(m)
37
FROM (
38
SELECT
39
e.id,
40
e.title,
41
make_timestamp(
42
EXTRACT(YEAR FROM eo.occurrence_date)::int,
43
EXTRACT(MONTH FROM eo.occurrence_date)::int,
44
EXTRACT(DAY FROM eo.occurrence_date)::int,
45
EXTRACT(HOUR FROM eo.start_time)::int,
46
EXTRACT(MINUTE FROM eo.start_time)::int,
47
0
48
) AS "meeting_date"
49
FROM events e
50
JOIN event_occurrences eo
51
ON eo.event_id = e.id
52
CROSS JOIN params p
53
WHERE e.company_id = p_apartment_id
54
AND e.event_type_id IN (2, 3, 4) -- Meeting, Committee Meeting, AGM
55
AND e.is_deleted = false
56
AND eo.is_deleted = false
57
AND eo.occurrence_date >= CURRENT_DATE
58
ORDER BY eo.occurrence_date ASC
59
LIMIT 1
60
) m
61
) AS meeting,
62
63
/* ------------------------------------------------------------------
64
3) Latest Decision (most recent)
65
------------------------------------------------------------------ */
66
(
67
SELECT to_jsonb(d)
68
FROM (
69
SELECT
70
d.id,
71
d.title,
72
d.created_on_utc AS "created_on_utc"
73
FROM decisions d
74
WHERE d.apartment_id = p_apartment_id
75
AND d.is_deleted = false
76
ORDER BY d.created_on_utc DESC
77
LIMIT 1
78
) d
79
) AS decision,
80
81
/* ------------------------------------------------------------------
82
4) Active Poll (currently running)
83
------------------------------------------------------------------ */
84
(
85
SELECT to_jsonb(p)
86
FROM (
87
SELECT
88
p.id,
89
p.title,
90
p.start_date AS "starts_on_utc",
91
p.end_date AS "ends_on_utc"
92
FROM polls p
93
WHERE p.apartment_id = p_apartment_id
94
AND p.is_deleted = false
95
AND p.end_date >= now()
96
ORDER BY p.end_date ASC
97
LIMIT 1
98
) p
99
) AS poll;
100
$function$
|
|||||
| Function | save_visitor_and_approval | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.save_visitor_and_approval(p_apartment_id uuid, p_first_name text, p_contact_number text, p_visitor_type_id integer, p_visit_type_id integer, p_start_date date, p_end_date date, p_entry_time time without time zone, p_exit_time time without time zone, p_vehicle_number text, p_company_name text, p_created_by uuid)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id INTEGER;
7
BEGIN
8
-- Check if the visitor already exists based on first_name and contact_number
9
SELECT id INTO v_visitor_id
10
FROM public.visitors
11
WHERE first_name = p_first_name
12
AND contact_number = p_contact_number
13
AND is_deleted = false;
14
15
-- If visitor exists, use the existing visitor_id, else insert a new visitor
16
IF NOT FOUND THEN
17
-- Insert into visitors table (auto-generated id will be used)
18
INSERT INTO public.visitors (
19
first_name,
20
contact_number,
21
visitor_type_id,
22
created_on_utc,
23
created_by,
24
apartment_id
25
)
26
VALUES (
27
p_first_name,
28
p_contact_number,
29
p_visitor_type_id,
30
NOW(),
31
p_created_by,
32
p_apartment_id
33
)
34
RETURNING id INTO v_visitor_id; -- Capture the auto-generated id
35
END IF;
36
37
-- Insert into visitor_approvals table using the visitor_id
38
INSERT INTO public.visitor_approvals (
39
visitor_id,
40
visit_type_id,
41
start_date,
42
end_date,
43
entry_time,
44
exit_time,
45
vehicle_number,
46
company_name,
47
created_on_utc,
48
created_by
49
)
50
VALUES (
51
v_visitor_id,
52
p_visit_type_id,
53
COALESCE(p_start_date, NULL), -- Handle NULL for start_date
54
COALESCE(p_end_date, NULL), -- Handle NULL for end_date
55
COALESCE(p_entry_time, NULL), -- Handle NULL for entry_time
56
COALESCE(p_exit_time, NULL), -- Handle NULL for exit_time
57
COALESCE(p_vehicle_number, NULL), -- Handle NULL for vehicle_number
58
COALESCE(p_company_name, NULL), -- Handle NULL for company_name
59
NOW(),
60
p_created_by
61
);
62
END;
63
$function$
|
|||||
| Function | take_visitor_action | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.take_visitor_action(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_action_status integer)
2
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, approver_first_name text, approver_last_name text, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
local_total_visits INT;
8
local_approved_count INT;
9
local_rejected_count INT;
10
local_new_visitor_status INT;
11
12
v_pending CONSTANT INT := 1;
13
v_partial_approved CONSTANT INT := 2;
14
v_approved CONSTANT INT := 3;
15
v_rejected CONSTANT INT := 7;
16
BEGIN
17
/*
18
* 1. Update unit-level status (approve / reject)
19
*/
20
UPDATE public.multi_unit_visits AS muv
21
SET visitor_statuses = p_action_status,
22
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
23
modified_by = p_resident_user
24
WHERE muv.visitor_log_id = p_visitor_log_id
25
AND muv.unit_id = p_unit_id
26
AND muv.is_deleted = FALSE;
27
28
IF NOT FOUND THEN
29
RETURN;
30
END IF;
31
32
/*
33
* 2. Recalculate unit status counts
34
*/
35
SELECT
36
COUNT(*),
37
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_approved),
38
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_rejected)
39
INTO
40
local_total_visits,
41
local_approved_count,
42
local_rejected_count
43
FROM public.multi_unit_visits AS muv
44
WHERE muv.visitor_log_id = p_visitor_log_id
45
AND muv.is_deleted = FALSE;
46
47
/*
48
* 3. Decide parent visitor_logs status
49
*/
50
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
51
local_new_visitor_status := v_approved; -- 3 Approved
52
ELSIF local_approved_count > 0 THEN
53
local_new_visitor_status := v_partial_approved; -- 2 Partial Approved
54
ELSIF local_rejected_count > 0 THEN
55
local_new_visitor_status := v_rejected; -- 7 Rejected
56
ELSE
57
local_new_visitor_status := v_pending; -- 1 Pending
58
END IF;
59
60
/*
61
* 4. Update visitor_logs parent status
62
*/
63
UPDATE public.visitor_logs AS vl
64
SET
65
visitor_status_id = local_new_visitor_status,
66
entry_time = CASE
67
WHEN local_new_visitor_status = v_approved
68
AND vl.entry_time IS NULL
69
--THEN (NOW() AT TIME ZONE '+05:30')::time
70
THEN (NOW() AT TIME ZONE 'Asia/Kolkata')
71
ELSE vl.entry_time
72
END,
73
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
74
modified_by = p_resident_user
75
WHERE vl.id = p_visitor_log_id;
76
77
/*
78
* 5. Return response with approver details
79
*/
80
RETURN QUERY
81
SELECT
82
p_visitor_log_id,
83
p_unit_id,
84
r.id,
85
r.first_name,
86
r.last_name,
87
local_new_visitor_status
88
FROM public.residents r
89
WHERE r.user_id = p_resident_user
90
AND r.is_deleted = FALSE;
91
END;
92
$function$
|
|||||
| Function | create_pre_approved_entry_generic | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_pre_approved_entry_generic(p_apartment_id uuid, p_unit_id integer, p_visitor_type_id integer, p_visitor_id bigint DEFAULT NULL::bigint, p_delivery_company_id integer DEFAULT NULL::integer, p_possible_first_name text DEFAULT NULL::text, p_possible_last_name text DEFAULT NULL::text, p_possible_phone text DEFAULT NULL::text, p_possible_email text DEFAULT NULL::text, p_possible_vehicle text DEFAULT NULL::text, p_possible_notes text DEFAULT NULL::text, p_valid_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_valid_to timestamp without time zone DEFAULT NULL::timestamp without time zone, p_is_once boolean DEFAULT true, p_notes text DEFAULT NULL::text, p_created_by uuid DEFAULT NULL::uuid, p_schedule_rule jsonb DEFAULT NULL::jsonb)
2
RETURNS TABLE(pre_approved_entry_id integer, first_name text, last_name text, pin text, valid_from timestamp without time zone, valid_to timestamp without time zone, possible_visitor_id bigint, visitor_id bigint, schedule_days_of_week integer[], schedule_start_time text, schedule_end_time text, schedule_max_entries_per_day integer, schedule_validity_in_months integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_delivery_type_id constant int := 2; -- Delivery from visitor_types table
7
8
v_now timestamp := (NOW() AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Kolkata';
9
v_valid_from timestamp := COALESCE(p_valid_from, NOW());
10
v_valid_to timestamp := p_valid_to;
11
12
v_visitor_id bigint := NULL;
13
v_possible_visitor_id bigint := NULL;
14
15
v_pin text := NULL;
16
v_attempt int := 0;
17
18
v_entry_id int := NULL;
19
20
v_days int[] := NULL;
21
v_start_time time := NULL;
22
v_end_time time := NULL;
23
v_max_entries int := NULL;
24
v_validity_months int := NULL;
25
26
v_first_name text := NULL;
27
v_last_name text := NULL;
28
BEGIN
29
-- =======================
30
-- VALIDATIONS
31
-- =======================
32
IF p_apartment_id IS NULL THEN
33
RAISE EXCEPTION 'ApartmentId is required';
34
END IF;
35
36
IF p_unit_id IS NULL OR p_unit_id <= 0 THEN
37
RAISE EXCEPTION 'UnitId must be > 0';
38
END IF;
39
40
IF p_created_by IS NULL THEN
41
RAISE EXCEPTION 'CreatedBy is required';
42
END IF;
43
44
-- normalize visitor id (EF code treats 0 as null)
45
IF p_visitor_id = 0 THEN
46
p_visitor_id := NULL;
47
END IF;
48
49
v_visitor_id := p_visitor_id;
50
51
-- One-at-a-time per apartment for PIN generation & inserts
52
PERFORM pg_advisory_xact_lock(hashtext(p_apartment_id::text));
53
54
-- =======================
55
-- STEP 1: Resolve VisitorId via phone
56
-- =======================
57
IF v_visitor_id IS NULL AND COALESCE(trim(p_possible_phone), '') <> '' THEN
58
SELECT vp.visitor_id::bigint
59
INTO v_visitor_id
60
FROM public.visitor_phones vp
61
WHERE vp.phone_number = p_possible_phone
62
AND vp.is_active = true
63
ORDER BY vp.id DESC
64
LIMIT 1;
65
END IF;
66
67
-- =======================
68
-- STEP 2: Resolve PossibleVisitor via phone
69
-- =======================
70
IF v_visitor_id IS NULL AND COALESCE(trim(p_possible_phone), '') <> '' THEN
71
SELECT pv.id
72
INTO v_possible_visitor_id
73
FROM public.possible_visitors pv
74
WHERE pv.phone_number = p_possible_phone
75
AND pv.is_deleted = false
76
ORDER BY pv.id DESC
77
LIMIT 1;
78
END IF;
79
80
-- =======================
81
-- STEP 3: Create PossibleVisitor if needed
82
-- =======================
83
IF v_visitor_id IS NULL AND v_possible_visitor_id IS NULL THEN
84
INSERT INTO public.possible_visitors (
85
first_name,
86
last_name,
87
phone_number,
88
email,
89
vehicle_number,
90
notes,
91
created_on_utc,
92
created_by,
93
is_deleted
94
)
95
VALUES (
96
COALESCE(p_possible_first_name, ''),
97
p_possible_last_name,
98
p_possible_phone,
99
p_possible_email,
100
p_possible_vehicle,
101
p_possible_notes,
102
v_now,
103
p_created_by,
104
false
105
)
106
RETURNING id INTO v_possible_visitor_id;
107
END IF;
108
109
-- =======================
110
-- STEP 4: Generate Unique PIN
111
-- Must be unique across:
112
-- 1) pre_approved_entries
113
-- 2) service_provider_apartments
114
-- for the same apartment and overlapping time window
115
-- =======================
116
WHILE v_attempt < 10 LOOP
117
v_attempt := v_attempt + 1;
118
v_pin := (floor(random() * 900000) + 100000)::int::text;
119
120
IF NOT EXISTS (
121
-- Check visitor pre-approved entries
122
SELECT 1
123
FROM public.pre_approved_entries pae
124
WHERE pae.apartment_id = p_apartment_id
125
AND pae.is_deleted = false
126
AND pae.pin = v_pin
127
AND pae.valid_from <= COALESCE(v_valid_to, 'infinity'::timestamp)
128
AND COALESCE(pae.valid_until, 'infinity'::timestamp) >= v_valid_from
129
130
UNION ALL
131
132
-- Check service provider apartment pins
133
SELECT 1
134
FROM public.service_provider_apartments spa
135
WHERE spa.apartment_id = p_apartment_id
136
AND spa.is_deleted = false
137
AND spa.is_active = true
138
AND spa.pin = v_pin
139
AND spa.valid_from <= COALESCE(v_valid_to, 'infinity'::timestamp)
140
AND COALESCE(spa.valid_to, 'infinity'::timestamp) >= v_valid_from
141
) THEN
142
EXIT;
143
END IF;
144
END LOOP;
145
146
IF v_pin IS NULL OR v_attempt >= 10 THEN
147
RAISE EXCEPTION
148
'Unable to generate a unique PIN for apartment % after % attempts',
149
p_apartment_id, v_attempt;
150
END IF;
151
152
-- =======================
153
-- STEP 5: Insert pre_approved_entries
154
-- =======================
155
INSERT INTO public.pre_approved_entries (
156
apartment_id,
157
unit_id,
158
notes,
159
is_once,
160
valid_from,
161
valid_until,
162
created_by,
163
created_on_utc,
164
is_deleted,
165
possible_visitor_id,
166
visitor_id,
167
pin
168
)
169
VALUES (
170
p_apartment_id,
171
p_unit_id,
172
p_notes,
173
COALESCE(p_is_once, true),
174
v_valid_from,
175
v_valid_to,
176
p_created_by,
177
v_now,
178
false,
179
v_possible_visitor_id,
180
v_visitor_id,
181
v_pin
182
)
183
RETURNING id INTO v_entry_id;
184
185
-- =======================
186
-- STEP 6: Delivery company mapping (Delivery = 2)
187
-- Now supports BOTH visitor_id and possible_visitor_id
188
-- =======================
189
IF p_visitor_type_id = v_delivery_type_id
190
AND p_delivery_company_id IS NOT NULL
191
AND (v_visitor_id IS NOT NULL OR v_possible_visitor_id IS NOT NULL) THEN
192
193
INSERT INTO public.visitor_delivery_company (
194
visitor_id,
195
possible_visitor_id,
196
delivery_company_id,
197
is_active,
198
valid_from,
199
valid_to
200
)
201
VALUES (
202
v_visitor_id,
203
v_possible_visitor_id,
204
p_delivery_company_id,
205
true,
206
NOW(),
207
NULL
208
);
209
END IF;
210
211
-- =======================
212
-- STEP 7: Schedule rule insert (optional)
213
-- =======================
214
IF p_schedule_rule IS NOT NULL THEN
215
-- Parse JSON safely
216
-- daysOfWeek (int array)
217
IF (p_schedule_rule ? 'daysOfWeek') THEN
218
SELECT COALESCE(array_agg(value::int), NULL)
219
INTO v_days
220
FROM jsonb_array_elements_text(p_schedule_rule->'daysOfWeek') AS t(value);
221
END IF;
222
223
-- startTime / endTime as time
224
IF (p_schedule_rule ? 'startTime') THEN
225
v_start_time := NULLIF(p_schedule_rule->>'startTime', '')::time;
226
END IF;
227
228
IF (p_schedule_rule ? 'endTime') THEN
229
v_end_time := NULLIF(p_schedule_rule->>'endTime', '')::time;
230
END IF;
231
232
-- maxEntriesPerDay default 1
233
v_max_entries :=
234
COALESCE(NULLIF(p_schedule_rule->>'maxEntriesPerDay', '')::int, 1);
235
236
-- validityInMonths nullable
237
IF (p_schedule_rule ? 'validityInMonths') THEN
238
v_validity_months := NULLIF(p_schedule_rule->>'validityInMonths', '')::int;
239
END IF;
240
241
INSERT INTO public.pre_approved_schedule_rules (
242
pre_approved_entry_id,
243
days_of_week,
244
start_time,
245
end_time,
246
max_entries_per_day,
247
validity_in_months
248
)
249
VALUES (
250
v_entry_id,
251
v_days,
252
v_start_time,
253
v_end_time,
254
v_max_entries,
255
v_validity_months
256
);
257
END IF;
258
259
-- =======================
260
-- STEP 8: Resolve names
261
-- =======================
262
IF v_visitor_id IS NOT NULL THEN
263
SELECT v.first_name, v.last_name
264
INTO v_first_name, v_last_name
265
FROM public.visitors v
266
WHERE v.id = v_visitor_id
267
AND v.is_deleted = false
268
LIMIT 1;
269
ELSIF v_possible_visitor_id IS NOT NULL THEN
270
SELECT pv.first_name, pv.last_name
271
INTO v_first_name, v_last_name
272
FROM public.possible_visitors pv
273
WHERE pv.id = v_possible_visitor_id
274
AND pv.is_deleted = false
275
LIMIT 1;
276
END IF;
277
278
-- =======================
279
-- RETURN
280
-- =======================
281
pre_approved_entry_id := v_entry_id;
282
first_name := v_first_name;
283
last_name := v_last_name;
284
pin := v_pin;
285
valid_from := v_valid_from;
286
valid_to := v_valid_to;
287
possible_visitor_id := v_possible_visitor_id;
288
visitor_id := v_visitor_id;
289
290
schedule_days_of_week := v_days;
291
schedule_start_time := CASE WHEN v_start_time IS NULL THEN NULL ELSE to_char(v_start_time, 'HH24:MI') END;
292
schedule_end_time := CASE WHEN v_end_time IS NULL THEN NULL ELSE to_char(v_end_time, 'HH24:MI') END;
293
schedule_max_entries_per_day := v_max_entries;
294
schedule_validity_in_months := v_validity_months;
295
296
RETURN NEXT;
297
END;
298
$function$
|
|||||
| Function | visitor_bulk_check_out | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.visitor_bulk_check_out(p_visitor_log_ids bigint[])
2
RETURNS SETOF visitor_logs
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
UPDATE public.visitor_logs
8
SET
9
exit_time = (NOW() AT TIME ZONE 'Asia/Kolkata'),
10
visitor_status_id = 5, -- Checked Out
11
created_on_utc = created_on_utc -- no-op, keeps row shape stable
12
WHERE id = ANY(p_visitor_log_ids)
13
RETURNING *;
14
END;
15
$function$
|
|||||
| Function | verify_user_password | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.verify_user_password(p_user_name text, p_password text)
2
RETURNS uuid
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_user_id UUID;
7
BEGIN
8
SELECT id
9
INTO v_user_id
10
FROM users
11
WHERE user_name = p_user_name
12
AND is_deleted = false
13
AND password_hash = crypt(p_password, password_hash);
14
15
RETURN v_user_id;
16
END;
17
$function$
|
|||||
| Function | get_community_memory_snapshot | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_community_memory_snapshot(p_apartment_id uuid)
2
RETURNS TABLE(announcement jsonb, meeting jsonb, decision jsonb, poll jsonb)
3
LANGUAGE plpgsql
4
STABLE
5
AS $function$
6
BEGIN
7
RETURN QUERY
8
SELECT
9
(
10
SELECT to_jsonb(a)
11
FROM notices a
12
WHERE a.apartment_id = p_apartment_id
13
AND a.created_on_utc >= now() - interval '3 months'
14
AND a.is_deleted = false
15
ORDER BY a.created_on_utc DESC
16
LIMIT 1
17
) AS announcement,
18
19
(
20
SELECT to_jsonb(m)
21
FROM meetings m
22
WHERE m.apartment_id = p_apartment_id
23
AND m.meeting_date >= now()
24
AND m.is_cancelled = false
25
ORDER BY m.meeting_date ASC
26
LIMIT 1
27
) AS meeting,
28
29
(
30
SELECT to_jsonb(d)
31
FROM decisions d
32
WHERE d.apartment_id = p_apartment_id
33
AND d.decision_date >= now() - interval '3 months'
34
AND d.is_deleted = false
35
ORDER BY d.decision_date DESC
36
LIMIT 1
37
) AS decision,
38
39
(
40
SELECT to_jsonb(p)
41
FROM polls p
42
WHERE p.apartment_id = p_apartment_id
43
AND p.start_date <= now()
44
AND p.end_date >= now()
45
AND p.is_deleted = false
46
ORDER BY p.end_date ASC
47
LIMIT 1
48
) AS poll;
49
END;
50
$function$
|
|||||
| Function | get_service_provider_app_context | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_service_provider_app_context(p_user_id uuid, p_apartment_id uuid)
2
RETURNS TABLE(service_provider_id integer, role text, category jsonb, verified boolean, onboarded_on timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
sp.id AS service_provider_id,
9
'Service Provider'::text AS role,
10
jsonb_build_object(
11
'id', sp.service_provider_sub_type_id,
12
'name', sps.name
13
) AS category,
14
15
sp.police_verification_status AS verified,
16
sp.created_on_utc AS onboarded_on
17
FROM service_provider_users spu
18
JOIN service_providers sp
19
ON sp.id = spu.service_provider_id
20
AND sp.is_deleted = false
21
JOIN service_provider_sub_types sps
22
ON sps.id = sp.service_provider_sub_type_id
23
JOIN service_provider_apartments spa
24
ON spa.service_provider_id = sp.id
25
AND spa.apartment_id = p_apartment_id
26
AND spa.is_deleted = false
27
AND spa.is_active = true
28
WHERE spu.user_id = p_user_id;
29
END;
30
$function$
|
|||||
| Function | get_facility_manager_app_context | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_facility_manager_app_context(p_user_id uuid, p_apartment_id uuid)
2
RETURNS TABLE(user_id uuid, role text, apartment_id uuid, apartment_name text, assigned_since timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ur.user_id,
9
r.name::text AS role,
10
a.id AS apartment_id,
11
a.name::text AS apartment_name,
12
13
-- Assigned since = role start date (fallback to created_on)
14
CASE
15
WHEN ur.start_date > '0001-01-01' THEN ur.start_date
16
ELSE ur.created_on_utc
17
END AS assigned_since
18
19
FROM fdw_common.user_roles ur
20
JOIN roles r
21
ON r.id = ur.role_id
22
AND r.id = 11 -- Facility Manager
23
JOIN organizations o
24
ON o.id = ur.organization_id
25
JOIN apartments a
26
ON a.organization_id = o.id
27
AND a.id = p_apartment_id
28
WHERE ur.user_id = p_user_id
29
AND ur.is_deleted = false
30
AND (ur.end_date = '0001-01-01' OR ur.end_date > now());
31
END;
32
$function$
|
|||||
| Function | get_security_guard_app_context | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_security_guard_app_context(p_user_id uuid, p_apartment_id uuid)
2
RETURNS TABLE(user_id uuid, role text, apartment_id uuid, apartment_name text, assigned_gate_ids integer[], active_since timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ur.user_id,
9
r.name::text AS role,
10
a.id AS apartment_id,
11
a.name AS apartment_name,
12
-- FUTURE: security_guard_gates
13
-- CURRENT: return empty array safely
14
ARRAY[]::int[] AS assigned_gate_ids,
15
sga.valid_from
16
FROM fdw_common.user_roles ur
17
JOIN roles r
18
ON r.id = ur.role_id
19
AND r.name = 'Security Guard'
20
JOIN public.security_guard_apartments sga
21
ON sga.user_id = ur.user_id
22
AND sga.apartment_id = p_apartment_id
23
AND sga.is_active = true
24
AND sga.is_deleted = false
25
JOIN apartments a
26
ON a.id = p_apartment_id
27
WHERE ur.user_id = p_user_id
28
AND ur.is_deleted = false;
29
END;
30
$function$
|
|||||
| Function | postgres_fdw_handler | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_handler()
2
RETURNS fdw_handler
3
LANGUAGE c
4
STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_handler$function$
|
|||||
| Function | postgres_fdw_validator | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_validator(text[], oid)
2
RETURNS void
3
LANGUAGE c
4
STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_validator$function$
|
|||||
| Function | postgres_fdw_disconnect | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect(text)
2
RETURNS boolean
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect$function$
|
|||||
| Function | postgres_fdw_disconnect_all | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect_all()
2
RETURNS boolean
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect_all$function$
|
|||||
| Function | postgres_fdw_get_connections | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.postgres_fdw_get_connections(check_conn boolean DEFAULT false, OUT server_name text, OUT user_name text, OUT valid boolean, OUT used_in_xact boolean, OUT closed boolean, OUT remote_backend_pid integer)
2
RETURNS SETOF record
3
LANGUAGE c
4
PARALLEL RESTRICTED STRICT
5
AS '$libdir/postgres_fdw', $function$postgres_fdw_get_connections_1_2$function$
|
|||||
| Function | get_service_provider_categories | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_service_provider_categories()
2
RETURNS TABLE(id integer, name text)
3
LANGUAGE sql
4
AS $function$
5
SELECT spc.id, spc.name
6
FROM public.service_provider_company_categories spc;
7
$function$
|
|||||
| Function | get_security_guard_fcm_tokens | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_security_guard_fcm_tokens(p_apartment_id uuid)
2
RETURNS TABLE(user_id uuid, device_id text, fcm_token text)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
7
SELECT DISTINCT ON (uft.user_id, uft.device_id)
8
u.id AS user_id,
9
uft.device_id,
10
uft.fcm_token
11
FROM users u
12
INNER JOIN fdw_common.user_roles ur
13
ON ur.user_id = u.id
14
INNER JOIN user_fcm_tokens uft
15
ON uft.user_id = u.id
16
WHERE ur.role_id = 7
17
AND u.company_id = p_apartment_id
18
AND u.is_deleted = FALSE
19
AND uft.fcm_token IS NOT NULL
20
AND uft.device_id IS NOT NULL;
21
$function$
|
|||||
| Function | get_community_feed | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_community_feed(p_apartment_id uuid, p_unit_id integer, p_resident_id integer)
2
RETURNS TABLE(type text, priority text, occurred_on timestamp without time zone, payload jsonb)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
7
/* ------------------------------------------------------------------
8
📢 Announcements
9
------------------------------------------------------------------ */
10
(
11
SELECT
12
'Announcement',
13
'High',
14
n.created_on_utc,
15
jsonb_build_object(
16
'id', n.id,
17
'title', n.title,
18
'summary', n.short_description,
19
'createdBy', u.first_name || ' ' || u.last_name
20
)
21
FROM notices n
22
JOIN users u ON u.id = n.created_by
23
WHERE n.apartment_id = p_apartment_id
24
AND n.is_deleted = false
25
AND u.is_deleted = false
26
ORDER BY n.created_on_utc DESC
27
LIMIT 1
28
)
29
30
/* ------------------------------------------------------------------
31
🗳 Decisions
32
------------------------------------------------------------------ */
33
UNION ALL
34
(
35
SELECT
36
'Decision',
37
'Medium',
38
d.created_on_utc,
39
jsonb_build_object(
40
'id', d.id,
41
'title', d.title,
42
'description', d.description,
43
'decisionOutcome', deo.name,
44
'createdBy', u.first_name || ' ' || u.last_name
45
)
46
FROM decisions d
47
JOIN decision_outcomes deo ON deo.id = d.decision_outcome_id
48
JOIN users u ON u.id = d.created_by
49
WHERE d.apartment_id = p_apartment_id
50
AND d.is_deleted = false
51
AND u.is_deleted = false
52
ORDER BY d.created_on_utc DESC
53
LIMIT 2
54
)
55
56
/* ------------------------------------------------------------------
57
📊 Polls (CORRECT & DERIVED)
58
------------------------------------------------------------------ */
59
UNION ALL
60
(
61
WITH vote_stats AS (
62
SELECT
63
pv.poll_id,
64
COUNT(*) AS total_votes
65
FROM poll_votes pv
66
WHERE pv.is_deleted = false
67
GROUP BY pv.poll_id
68
),
69
option_stats AS (
70
SELECT
71
pv.poll_option_id,
72
COUNT(*) AS votes
73
FROM poll_votes pv
74
WHERE pv.is_deleted = false
75
GROUP BY pv.poll_option_id
76
),
77
possible_voters AS (
78
SELECT
79
p.id AS poll_id,
80
CASE
81
WHEN p.poll_eligibility_type_id = 1 THEN (
82
SELECT COUNT(*)
83
FROM units u
84
WHERE u.apartment_id = p_apartment_id
85
AND u.unit_type_id = 1
86
AND u.is_deleted = false
87
)
88
WHEN p.poll_eligibility_type_id = 2 THEN (
89
SELECT COUNT(*)
90
FROM residents r
91
WHERE r.apartment_id = p_apartment_id
92
AND r.is_deleted = false
93
)
94
ELSE 0
95
END AS total_possible
96
FROM polls p
97
WHERE p.apartment_id = p_apartment_id
98
)
99
100
SELECT
101
'Poll',
102
'Medium',
103
p.start_date,
104
105
jsonb_build_object(
106
'id', p.id,
107
'title', p.title,
108
109
'status', CASE
110
WHEN p.start_date > now() THEN 'Not Started'
111
WHEN p.end_date < now() THEN 'Closed'
112
ELSE 'Ongoing'
113
END,
114
115
'endsAt', p.end_date,
116
117
'hasVoted',
118
EXISTS (
119
SELECT 1
120
FROM poll_votes pv
121
WHERE pv.poll_id = p.id
122
AND pv.is_deleted = false
123
AND (
124
(p.poll_eligibility_type_id = 1 AND pv.unit_id = p_unit_id)
125
OR (p.poll_eligibility_type_id = 2 AND pv.resident_id = p_resident_id)
126
)
127
),
128
129
'selectedOptionId',
130
(
131
SELECT pv.poll_option_id
132
FROM poll_votes pv
133
WHERE pv.poll_id = p.id
134
AND pv.is_deleted = false
135
AND (
136
(p.poll_eligibility_type_id = 1 AND pv.unit_id = p_unit_id)
137
OR (p.poll_eligibility_type_id = 2 AND pv.resident_id = p_resident_id)
138
)
139
LIMIT 1
140
),
141
142
'votesCast', COALESCE(vs.total_votes, 0),
143
'possibleVoters', pv.total_possible,
144
145
'participationPercent',
146
CASE
147
WHEN pv.total_possible = 0 THEN 0
148
ELSE ROUND((COALESCE(vs.total_votes, 0) * 100.0) / pv.total_possible, 1)
149
END,
150
151
'options',
152
COALESCE(
153
(
154
SELECT jsonb_agg(
155
jsonb_build_object(
156
'id', po.id,
157
'text', po.option_text,
158
'voteCount', COALESCE(os.votes, 0),
159
'percentage',
160
CASE
161
WHEN COALESCE(vs.total_votes, 0) = 0 THEN 0
162
ELSE ROUND(
163
(COALESCE(os.votes, 0) * 100.0)
164
/ COALESCE(vs.total_votes, 0),
165
1
166
)
167
END
168
)
169
ORDER BY po.id
170
)
171
FROM poll_options po
172
LEFT JOIN option_stats os ON os.poll_option_id = po.id
173
WHERE po.poll_id = p.id
174
),
175
'[]'::jsonb
176
)
177
)
178
179
FROM polls p
180
LEFT JOIN vote_stats vs ON vs.poll_id = p.id
181
LEFT JOIN possible_voters pv ON pv.poll_id = p.id
182
WHERE p.apartment_id = p_apartment_id
183
AND p.is_deleted = false
184
ORDER BY p.created_on_utc DESC
185
LIMIT 1
186
)
187
188
/* ------------------------------------------------------------------
189
📅 Meetings
190
------------------------------------------------------------------ */
191
UNION ALL
192
(
193
SELECT
194
'Meeting',
195
'High',
196
e.start_time,
197
jsonb_build_object(
198
'id', e.id,
199
'title', e.title,
200
'description', e.description,
201
'startsAt', e.start_time,
202
'endsAt', e.end_time,
203
'isLive', (e.start_time <= now() AND now() <= e.end_time)
204
)
205
FROM events e
206
WHERE e.company_id = p_apartment_id
207
AND e.is_deleted = false
208
ORDER BY e.created_on_utc DESC
209
LIMIT 1
210
);
211
212
$function$
|
|||||
| Function | get_meeting_data | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_meeting_data(p_event_uuid uuid, p_occ_date date)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_agenda jsonb;
7
v_participants jsonb;
8
v_actions jsonb;
9
v_notes jsonb;
10
BEGIN
11
-- Get agenda items, or empty array if no data
12
SELECT COALESCE(
13
jsonb_agg(
14
jsonb_build_object(
15
'id', id,
16
'item_text', item_text,
17
'order_no', order_no)
18
), '[]'::jsonb
19
)
20
INTO v_agenda
21
FROM meeting_agenda_items
22
WHERE occurrence_id = (
23
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
24
) AND is_deleted = false;
25
26
-- Get participants, or empty array if no data
27
28
SELECT COALESCE(
29
jsonb_agg(
30
jsonb_build_object(
31
'id', mp.id,
32
'user_id', user_id,
33
'user_name', CONCAT(u.first_name, ' ', u.last_name) -- Concatenate first and last name
34
)
35
), '[]'::jsonb
36
)
37
INTO v_participants
38
FROM meeting_participants mp
39
JOIN users u ON mp.user_id = u.id -- Join with the users table to get first_name and last_name
40
WHERE occurrence_id = (
41
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
42
) AND mp.is_deleted = false;
43
44
-- Get actions, or empty array if no data
45
46
SELECT COALESCE(
47
jsonb_agg(
48
jsonb_build_object(
49
'id', mat.id,
50
'action_description', action_description,
51
'assigned_to_user_id', assigned_to_user_id,
52
'assigned_to_user_name', CONCAT(u.first_name, ' ', u.last_name),
53
'due_date', due_date,
54
'status', status
55
)
56
), '[]'::jsonb
57
)
58
INTO v_actions
59
FROM meeting_action_items mat
60
JOIN users u ON mat.assigned_to_user_id = u.id -- Join with the users table to get first_name and last_name
61
WHERE occurrence_id = (
62
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
63
) AND mat.is_deleted = false;
64
65
-- Get notes
66
SELECT jsonb_agg(
67
jsonb_build_object(
68
'note_text', note_text
69
)
70
)
71
INTO v_notes
72
FROM meeting_notes
73
WHERE occurrence_id = (
74
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
75
) AND is_deleted = false;
76
77
-- Return all data in a single JSON object
78
RETURN jsonb_build_object(
79
'agenda', v_agenda,
80
'participants', v_participants,
81
'actions', v_actions,
82
'notes', v_notes
83
);
84
END;
85
$function$
|
|||||
| Function | create_poll_vote | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_poll_vote(p_poll_id integer, p_unit_id integer, p_resident_id integer, p_poll_option_id integer, p_vote_comment text)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
-- 1οΈβ£ Validate required identifiers
7
IF p_unit_id IS NULL OR p_resident_id IS NULL THEN
8
RAISE EXCEPTION 'Both unitId and residentId are required';
9
END IF;
10
11
-- 2οΈβ£ Validate poll active
12
IF NOT EXISTS (
13
SELECT 1
14
FROM polls
15
WHERE id = p_poll_id
16
AND is_deleted = false
17
AND start_date <= now()
18
AND end_date >= now()
19
) THEN
20
RAISE EXCEPTION 'Poll is not active';
21
END IF;
22
23
-- 3οΈβ£ Validate option
24
IF NOT EXISTS (
25
SELECT 1
26
FROM poll_options
27
WHERE id = p_poll_option_id
28
AND poll_id = p_poll_id
29
) THEN
30
RAISE EXCEPTION 'Invalid poll option';
31
END IF;
32
33
-- 4οΈβ£ Insert vote (unit-based)
34
INSERT INTO poll_votes (
35
poll_id,
36
unit_id,
37
resident_id,
38
poll_option_id,
39
vote_comment,
40
vote_date,
41
created_on_utc,
42
is_deleted
43
)
44
VALUES (
45
p_poll_id,
46
p_unit_id,
47
p_resident_id,
48
p_poll_option_id,
49
p_vote_comment,
50
now(),
51
now(),
52
false
53
)
54
ON CONFLICT ON CONSTRAINT uq_poll_votes_unit_per_poll
55
DO NOTHING;
56
END;
57
$function$
|
|||||
| Function | create_poll_with_options | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_poll_with_options(p_apartment_id uuid, p_title character varying, p_description text, p_poll_type_id integer, p_poll_eligibility_type_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone, p_created_by uuid, p_options jsonb)
2
RETURNS TABLE(poll_id integer, options_count integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_poll_id int;
7
v_option jsonb;
8
BEGIN
9
-- 1οΈβ£ Validate date range
10
IF p_end_date <= p_start_date THEN
11
RAISE EXCEPTION 'Poll end date must be greater than start date';
12
END IF;
13
14
-- 2οΈβ£ Validate poll type
15
IF NOT EXISTS (
16
SELECT 1 FROM poll_types WHERE id = p_poll_type_id
17
) THEN
18
RAISE EXCEPTION 'Invalid poll_type_id: %', p_poll_type_id;
19
END IF;
20
21
-- 3οΈβ£ Validate eligibility type
22
IF NOT EXISTS (
23
SELECT 1 FROM poll_eligibility_types WHERE id = p_poll_eligibility_type_id
24
) THEN
25
RAISE EXCEPTION 'Invalid poll_eligibility_type_id: %', p_poll_eligibility_type_id;
26
END IF;
27
28
-- 4οΈβ£ Create poll
29
INSERT INTO polls (
30
apartment_id,
31
title,
32
description,
33
poll_type_id,
34
poll_eligibility_type_id,
35
status_id,
36
start_date,
37
end_date,
38
created_on_utc,
39
created_by,
40
is_deleted
41
)
42
VALUES (
43
p_apartment_id,
44
p_title,
45
p_description,
46
p_poll_type_id,
47
p_poll_eligibility_type_id,
48
1, -- Draft / Active (adjust if needed)
49
p_start_date,
50
p_end_date,
51
now(),
52
p_created_by,
53
false
54
)
55
RETURNING id INTO v_poll_id;
56
57
-- 5οΈβ£ Insert options
58
FOR v_option IN
59
SELECT * FROM jsonb_array_elements(p_options)
60
LOOP
61
INSERT INTO poll_options (
62
poll_id,
63
option_text,
64
option_type_id
65
)
66
VALUES (
67
v_poll_id,
68
v_option ->> 'text',
69
1
70
);
71
END LOOP;
72
73
-- 6οΈβ£ Return
74
RETURN QUERY
75
SELECT
76
v_poll_id,
77
jsonb_array_length(p_options);
78
79
END;
80
$function$
|
|||||
| Function | update_visitor_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_visitor_status(p_visitor_log_id integer, p_unit_id integer, p_visitor_status_id integer, p_modified_by uuid, p_is_manual_approval boolean DEFAULT false, p_approval_source integer DEFAULT 1, p_approval_reason integer DEFAULT 0, p_approved_by_resident_id integer DEFAULT NULL::integer)
2
RETURNS TABLE(visitor_id integer, total_visits_count integer, approved_visits_count integer, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
local_total_visits int;
7
local_approved_count int;
8
local_rejected_count int; -- Added to track rejections
9
local_new_visitor_status int;
10
BEGIN
11
-- Step 1: Update the status for the specific unit
12
UPDATE public.multi_unit_visits
13
SET visitor_statuses = p_visitor_status_id,
14
is_manual_approval = COALESCE(p_is_manual_approval, false),
15
approval_source = COALESCE(p_approval_source, 1),
16
approval_reason = COALESCE(p_approval_reason, 0),
17
approved_by_resident_id = p_approved_by_resident_id,
18
modified_on_utc = NOW(),
19
modified_by = p_modified_by
20
WHERE visitor_log_id = p_visitor_log_id
21
AND unit_id = p_unit_id
22
AND is_deleted = false;
23
24
-- Step 2: Check if the update was successful.
25
IF NOT FOUND THEN
26
RETURN;
27
END IF;
28
29
-- Step 3: Recalculate the overall visitor log status.
30
SELECT
31
COUNT(*),
32
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
33
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
34
INTO
35
local_total_visits,
36
local_approved_count,
37
local_rejected_count
38
FROM public.multi_unit_visits AS muv
39
WHERE muv.visitor_log_id = p_visitor_log_id
40
AND muv.is_deleted = false;
41
42
-- Step 4: Determine the new overall status with corrected logic.
43
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
44
local_new_visitor_status := 2; -- Rule 1: All units have approved.
45
ELSIF local_approved_count > 0 THEN
46
-- FIX: If at least one unit approves, the status is "Partial Approved",
47
-- even if another unit rejects.
48
local_new_visitor_status := 7; -- Rule 2: At least one approval, but not all.
49
ELSIF local_rejected_count > 0 THEN
50
local_new_visitor_status := 6; -- Rule 3: No approvals yet, but at least one rejection.
51
ELSE
52
local_new_visitor_status := 1; -- Rule 4: No approvals and no rejections yet.
53
END IF;
54
55
-- Step 5: Update the parent visitor_logs table.
56
UPDATE public.visitor_logs
57
SET visitor_status_id = local_new_visitor_status,
58
modified_on_utc = NOW(),
59
modified_by = p_modified_by
60
WHERE id = p_visitor_log_id;
61
62
-- Step 6: On success, return the calculated values.
63
RETURN QUERY SELECT p_visitor_log_id, local_total_visits, local_approved_count, local_new_visitor_status;
64
END;
65
$function$
|
|||||
| Function | get_unit_visitors | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_visitors(p_unit_id integer, p_for_date date DEFAULT CURRENT_DATE)
2
RETURNS TABLE(id integer, source text, visitor_id bigint, possible_visitor_id bigint, pre_approved_entry_id integer, unit_id integer, visitor_type_id integer, visitor_type text, visitor_name text, phone_number text, vehicle_number text, entry_time timestamp without time zone, exit_time timestamp without time zone, expected_from timestamp without time zone, expected_until timestamp without time zone, visitor_status_id integer, visitor_status_name text)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
7
WITH unit_ctx AS (
8
SELECT
9
u.id AS unit_id,
10
u.apartment_id AS apartment_id
11
FROM units u
12
WHERE u.id = p_unit_id
13
AND u.is_deleted = false
14
)
15
16
SELECT
17
ROW_NUMBER() OVER (
18
ORDER BY
19
entry_time DESC NULLS LAST,
20
expected_from DESC NULLS LAST
21
)::int AS id,
22
q.*
23
FROM (
24
/* ===============================
25
1οΈβ£ ACTUAL VISITS (LOG)
26
=============================== */
27
SELECT
28
'LOG' AS source,
29
v.id AS visitor_id,
30
NULL::bigint AS possible_visitor_id,
31
vl.pre_approved_entry_id,
32
muv.unit_id,
33
v.visitor_type_id,
34
vt.name AS visitor_type,
35
concat_ws(' ', v.first_name, v.last_name) AS visitor_name,
36
NULL::text AS phone_number,
37
vl.vehicle_number,
38
vl.entry_time,
39
vl.exit_time,
40
NULL::timestamp AS expected_from,
41
NULL::timestamp AS expected_until,
42
muv.visitor_statuses AS visitor_status_id,
43
vs.name AS visitor_status_name
44
FROM unit_ctx uc
45
JOIN visitor_logs vl
46
ON vl.apartment_id = uc.apartment_id
47
Join visitor_types vt
48
On vl.visitor_type_id = vt.id
49
JOIN visitors v
50
ON v.id = vl.visitor_id
51
AND v.is_deleted = false
52
JOIN multi_unit_visits muv
53
ON muv.visitor_log_id = vl.id
54
AND muv.is_deleted = false
55
AND muv.unit_id = uc.unit_id
56
JOIN visitor_statuses vs
57
ON vs.id = muv.visitor_statuses
58
AND vs.is_deleted = false
59
WHERE
60
DATE(vl.created_on_utc) = p_for_date
61
62
UNION ALL
63
64
/* ===============================
65
2οΈβ£ EXPECTED VISITORS (PRE_APPROVED)
66
=============================== */
67
SELECT
68
'PRE_APPROVED' AS source,
69
pv2.id AS visitor_id, -- optional (known visitor)
70
pv.id AS possible_visitor_id,
71
pae.id AS pre_approved_entry_id,
72
pae.unit_id,
73
pv.visitor_type_id,
74
vt.name AS visitor_type,
75
concat_ws(' ', pv.first_name, pv.last_name) AS visitor_name,
76
pv.phone_number,
77
pv.vehicle_number,
78
NULL::timestamp AS entry_time,
79
NULL::timestamp AS exit_time,
80
pae.valid_from AS expected_from,
81
pae.valid_until AS expected_until,
82
8 AS visitor_status_id, -- virtual EXPECTED
83
'Expected' AS visitor_status_name
84
FROM unit_ctx uc
85
JOIN pre_approved_entries pae
86
ON pae.unit_id = uc.unit_id
87
AND pae.apartment_id = uc.apartment_id
88
AND pae.is_deleted = false
89
JOIN possible_visitors pv
90
ON pv.id = pae.possible_visitor_id
91
AND pv.is_deleted = false
92
Join visitor_types vt
93
On pv.visitor_type_id = vt.id
94
LEFT JOIN visitors pv2
95
ON pv2.id = pae.visitor_id
96
AND pv2.is_deleted = false
97
WHERE
98
pae.valid_from::date <= p_for_date
99
AND (pae.valid_until IS NULL OR pae.valid_until::date >= p_for_date)
100
) q;
101
$function$
|
|||||
| Function | get_visitors_inside_with_units | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitors_inside_with_units(p_apartment_id uuid, p_today_only boolean DEFAULT false)
2
RETURNS TABLE(id integer, visitor_log_id bigint, visitor_id bigint, first_name text, last_name text, visitor_type_id integer, visitor_type_name text, visiting_units jsonb, entry_time timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
CAST(row_number() OVER (ORDER BY vl.entry_time DESC) AS integer) AS id,
9
vl.id AS visitor_log_id,
10
v.id AS visitor_id,
11
v.first_name,
12
v.last_name,
13
v.visitor_type_id,
14
vt.name::text AS visitor_type_name,
15
COALESCE(
16
jsonb_agg(
17
jsonb_build_object(
18
'unitId', u.id,
19
'unitName', u.name
20
)
21
ORDER BY u.id
22
) FILTER (WHERE u.id IS NOT NULL),
23
'[]'::jsonb
24
) AS visiting_units,
25
vl.entry_time
26
FROM
27
visitor_logs vl
28
JOIN
29
visitors v ON v.id = vl.visitor_id
30
JOIN
31
visitor_types vt ON vt.id = vl.visitor_type_id
32
LEFT JOIN
33
multi_unit_visits muv
34
ON muv.visitor_log_id = vl.id
35
AND muv.is_deleted = FALSE
36
LEFT JOIN
37
units u
38
ON u.id = muv.unit_id
39
AND u.is_deleted = FALSE
40
And u.apartment_id = p_apartment_id
41
WHERE
42
vl.apartment_id = p_apartment_id
43
AND vl.exit_time IS NULL
44
AND v.is_deleted = FALSE
45
AND vt.is_deleted = FALSE
46
--AND (p_today_only = FALSE OR vl.entry_time::date = CURRENT_DATE)
47
AND (NOT p_today_only OR vl.entry_time::date = CURRENT_DATE)
48
GROUP BY
49
vl.id,
50
v.id,
51
v.first_name,
52
v.last_name,
53
v.visitor_type_id,
54
vt.name,
55
vl.entry_time
56
ORDER BY
57
vl.entry_time DESC;
58
END;
59
$function$
|
|||||
| Function | get_visitors_inside_counts_by_type | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitors_inside_counts_by_type(p_apartment_id uuid, p_today_only boolean)
2
RETURNS TABLE(id integer, visitor_type_id integer, visitor_type_name text, count integer)
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
ROW_NUMBER() OVER (ORDER BY vt.id)::integer AS id,
7
vt.id AS visitor_type_id,
8
vt.name::text AS visitor_type_name,
9
COUNT(*) AS count
10
FROM visitor_logs vl
11
JOIN visitor_types vt ON vt.id = vl.visitor_type_id
12
JOIN visitors v ON v.id = vl.visitor_id
13
WHERE
14
vl.apartment_id = p_apartment_id
15
AND vl.exit_time IS NULL
16
AND (NOT p_today_only OR vl.entry_time::date = CURRENT_DATE)
17
AND v.is_deleted = FALSE
18
AND vt.is_deleted = FALSE
19
GROUP BY
20
vt.id,
21
vt.name
22
ORDER BY
23
vt.id;
24
$function$
|
|||||
| Function | get_unit_related_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_related_details(unit_id integer)
2
RETURNS jsonb
3
LANGUAGE sql
4
AS $function$
5
SELECT jsonb_build_object(
6
'id', u.id,
7
'name', u.name,
8
'unit_type_id', u.unit_type_id,
9
'phone_extension', u.phone_extention,
10
'area', u.area,
11
'bhk_type', u.bhk_type,
12
'occupant_type_id', u.occupant_type_id,
13
'occupancy_type_id', u.occupancy_type_id,
14
'residents', COALESCE(
15
(SELECT jsonb_agg(DISTINCT jsonb_build_object(
16
'id', r.id,
17
'first_name', r.first_name,
18
'last_name', r.last_name,
19
'email', r.email,
20
'contact_number', r.contact_number,
21
'resident_type', rt.name
22
))
23
FROM resident_units ru
24
JOIN residents r ON r.id = ru.resident_id AND r.is_deleted = false
25
JOIN resident_types rt ON rt.id = r.resident_type_id AND rt.is_deleted = false
26
WHERE ru.unit_id = u.id AND ru.is_deleted = false),
27
'[]'::jsonb),
28
'vehicles', COALESCE(
29
(SELECT jsonb_agg(DISTINCT jsonb_build_object(
30
'id', v.id,
31
'vehicle_number', v.vehicle_number,
32
'vehicle_type', vt.name
33
))
34
FROM vehicles v
35
JOIN vehicle_types vt ON vt.id = v.vehicle_type_id AND vt.is_deleted = false
36
WHERE v.unit_id = u.id AND v.is_deleted = false),
37
'[]'::jsonb)
38
)
39
FROM units u
40
WHERE u.id = unit_id AND u.is_deleted = false;
41
$function$
|
|||||
| Function | approve_visitor_for_unit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.approve_visitor_for_unit(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_resident_id integer)
2
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
local_total_visits INT;
7
local_approved_count INT;
8
local_rejected_count INT;
9
local_new_visitor_status INT;
10
BEGIN
11
-- Step 1: Approve for this unit only
12
UPDATE public.multi_unit_visits
13
SET visitor_statuses = 2, -- Approved
14
modified_on_utc = NOW(),
15
modified_by = p_resident_user
16
WHERE visitor_log_id = p_visitor_log_id
17
AND unit_id = p_unit_id
18
AND is_deleted = FALSE;
19
20
IF NOT FOUND THEN
21
-- No update happened (invalid visitor_log_id or unit_id)
22
RETURN;
23
END IF;
24
25
-- Step 2: Recalculate parent visitor log status
26
SELECT
27
COUNT(*),
28
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
29
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
30
INTO
31
local_total_visits,
32
local_approved_count,
33
local_rejected_count
34
FROM public.multi_unit_visits muv
35
WHERE muv.visitor_log_id = p_visitor_log_id
36
AND muv.is_deleted = FALSE;
37
38
-- Step 3: Decide new parent status
39
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
40
local_new_visitor_status := 2; -- All Approved
41
ELSIF local_approved_count > 0 THEN
42
local_new_visitor_status := 7; -- Partial Approved
43
ELSIF local_rejected_count > 0 THEN
44
local_new_visitor_status := 6; -- Rejected
45
ELSE
46
local_new_visitor_status := 1; -- Pending
47
END IF;
48
49
UPDATE public.visitor_logs
50
SET visitor_status_id = local_new_visitor_status,
51
modified_on_utc = NOW(),
52
modified_by = p_resident_user
53
WHERE id = p_visitor_log_id;
54
55
-- Step 4: Return minimal information for the service layer
56
RETURN QUERY
57
SELECT
58
p_visitor_log_id,
59
p_unit_id,
60
p_resident_id, -- approver resident
61
local_new_visitor_status;
62
63
END;
64
$function$
|
|||||
| Function | get_notice_list_by_date_range | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_notice_list_by_date_range(p_apartment_id uuid, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
2
RETURNS TABLE(id bigint, title character varying, short_description character varying, category_id integer, category_name character varying, priority_id integer, priority_name character varying, expires_on timestamp without time zone, created_by uuid, created_by_name character varying, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name character varying, modified_on_utc timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
n.id::bigint AS id,
9
n.title::varchar AS title,
10
n.short_description::varchar AS short_description,
11
n.category_id AS category_id,
12
nc.name::varchar AS category_name,
13
n.priority_id AS priority_id,
14
np.name::varchar AS priority_name,
15
n.expires_on AS expires_on,
16
n.created_by AS created_by,
17
(cu.first_name || ' ' || cu.last_name)::varchar AS created_by_name,
18
n.created_on_utc AS created_on_utc,
19
n.modified_by AS modified_by,
20
COALESCE(
21
(mu.first_name || ' ' || mu.last_name)::varchar,
22
''
23
) AS modified_by_name,
24
n.modified_on_utc AS modified_on_utc
25
FROM notices n
26
JOIN notice_categories nc ON n.category_id = nc.id
27
JOIN notice_priorities np ON n.priority_id = np.id
28
JOIN users cu ON n.created_by = cu.id
29
LEFT JOIN users mu ON n.modified_by = mu.id
30
WHERE
31
n.is_deleted = false
32
AND n.apartment_id = p_apartment_id
33
AND n.expires_on >= p_start_date
34
AND n.expires_on < p_end_date
35
ORDER BY n.expires_on DESC, n.id DESC;
36
END;
37
$function$
|
|||||
| Function | get_visitor_multiple_unit_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitor_multiple_unit_logs(v_apartment_id uuid, log_date timestamp without time zone)
2
RETURNS TABLE(id bigint, visitor_id bigint, first_name text, last_name text, unit_details jsonb, latest_entry_time timestamp without time zone, latest_exit_time timestamp without time zone, contact_number text, visitor_type_id integer, visitor_type_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
vl.id,
9
vl.visitor_id::BIGINT AS visitor_id,
10
v.first_name,
11
v.last_name,
12
13
/* units aggregation */
14
COALESCE(
15
jsonb_agg(
16
jsonb_build_object(
17
'unit_id', muv.unit_id,
18
'unit_name', u.name
19
)
20
) FILTER (WHERE muv.unit_id IS NOT NULL),
21
'[]'::jsonb
22
) AS unit_details,
23
24
/* entry / exit from log */
25
vl.entry_time AS latest_entry_time,
26
vl.exit_time AS latest_exit_time,
27
28
/* single phone per visitor (no fan-out, no ambiguity) */
29
vp.phone_number AS contact_number,
30
31
/* visitor type from log */
32
vl.visitor_type_id,
33
vt.name::TEXT AS visitor_type_name
34
35
FROM visitor_logs vl
36
37
/* visitor metadata */
38
LEFT JOIN visitors v
39
ON v.id = vl.visitor_id
40
AND v.is_deleted = FALSE
41
42
/* SAFE phone join (fixes ambiguity + fan-out) */
43
LEFT JOIN LATERAL (
44
SELECT vp2.phone_number
45
FROM visitor_phones vp2
46
WHERE vp2.visitor_id = vl.visitor_id
47
ORDER BY vp2.id
48
LIMIT 1
49
) vp ON true
50
51
/* unit mapping */
52
LEFT JOIN public.multi_unit_visits muv
53
ON muv.visitor_log_id = vl.id
54
AND muv.is_deleted = FALSE
55
56
LEFT JOIN units u
57
ON u.id = muv.unit_id
58
59
/* type lookup from LOG */
60
LEFT JOIN visitor_types vt
61
ON vt.id = vl.visitor_type_id
62
63
WHERE
64
vl.apartment_id = v_apartment_id
65
AND vl.created_on_utc >= date_trunc('day', log_date)
66
AND vl.created_on_utc < date_trunc('day', log_date) + interval '1 day'
67
68
GROUP BY
69
vl.id,
70
vl.visitor_id,
71
v.first_name,
72
v.last_name,
73
vl.entry_time,
74
vl.exit_time,
75
vp.phone_number,
76
vl.visitor_type_id,
77
vt.name
78
79
ORDER BY MAX(vl.created_on_utc) DESC;
80
END;
81
$function$
|
|||||
| Function | get_apartment_visitors | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_apartment_visitors(p_apartment_id uuid, p_unit_id integer DEFAULT NULL::integer, p_for_date date DEFAULT CURRENT_DATE)
2
RETURNS TABLE(row_id integer, source text, visitor_id bigint, possible_visitor_id bigint, pre_approved_entry_id integer, unit_id integer, visitor_type_id integer, visitor_name text, phone_number text, vehicle_number text, entry_time timestamp without time zone, exit_time timestamp without time zone, expected_from timestamp without time zone, expected_until timestamp without time zone, visitor_status_id integer, visitor_status_name text)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
7
SELECT
8
ROW_NUMBER() OVER (ORDER BY
9
entry_time DESC NULLS LAST,
10
expected_from DESC NULLS LAST
11
)::int AS row_id,
12
q.*
13
FROM (
14
/* ===============================
15
1οΈβ£ ACTUAL VISITS (LOG)
16
=============================== */
17
SELECT
18
'LOG' AS source,
19
v.id AS visitor_id,
20
NULL::bigint AS possible_visitor_id,
21
vl.pre_approved_entry_id,
22
muv.unit_id,
23
v.visitor_type_id,
24
concat_ws(' ', v.first_name, v.last_name) AS visitor_name,
25
NULL::text AS phone_number,
26
vl.vehicle_number,
27
vl.entry_time,
28
vl.exit_time,
29
NULL::timestamp AS expected_from,
30
NULL::timestamp AS expected_until,
31
muv.visitor_statuses AS visitor_status_id,
32
vs.name AS visitor_status_name
33
FROM visitor_logs vl
34
JOIN visitors v
35
ON v.id = vl.visitor_id
36
AND v.is_deleted = false
37
JOIN multi_unit_visits muv
38
ON muv.visitor_log_id = vl.id
39
AND muv.is_deleted = false
40
JOIN visitor_statuses vs
41
ON vs.id = muv.visitor_statuses
42
AND vs.is_deleted = false
43
WHERE
44
vl.apartment_id = p_apartment_id
45
AND DATE(vl.created_on_utc) = p_for_date
46
AND (p_unit_id IS NULL OR muv.unit_id = p_unit_id)
47
48
UNION ALL
49
50
/* ===============================
51
2οΈβ£ EXPECTED VISITORS
52
=============================== */
53
SELECT
54
'PRE_APPROVED' AS source,
55
pv2.id AS visitor_id,
56
pv.id AS possible_visitor_id,
57
pae.id AS pre_approved_entry_id,
58
pae.unit_id,
59
pv.visitor_type_id,
60
concat_ws(' ', pv.first_name, pv.last_name) AS visitor_name,
61
pv.phone_number,
62
pv.vehicle_number,
63
NULL::timestamp AS entry_time,
64
NULL::timestamp AS exit_time,
65
pae.valid_from AS expected_from,
66
pae.valid_until AS expected_until,
67
8 AS visitor_status_id,
68
'Expected' AS visitor_status_name
69
FROM pre_approved_entries pae
70
JOIN possible_visitors pv
71
ON pv.id = pae.possible_visitor_id
72
AND pv.is_deleted = false
73
LEFT JOIN visitors pv2
74
ON pv2.id = pae.visitor_id
75
AND pv2.is_deleted = false
76
WHERE
77
pae.apartment_id = p_apartment_id
78
AND pae.is_deleted = false
79
AND pae.valid_from::date <= p_for_date
80
AND (pae.valid_until IS NULL OR pae.valid_until::date >= p_for_date)
81
AND (p_unit_id IS NULL OR pae.unit_id = p_unit_id)
82
) q;
83
84
$function$
|
|||||
| Function | take_visitor_action | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.take_visitor_action(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_action_status integer, p_approval_reason integer, p_approval_source integer, p_approved_by_resident_id integer, p_is_manual_approval boolean)
2
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, approver_first_name text, approver_last_name text, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
6
DECLARE
7
local_total_visits INT;
8
local_approved_count INT;
9
local_rejected_count INT;
10
local_new_visitor_status INT;
11
12
v_pending CONSTANT INT := 1;
13
v_partial_approved CONSTANT INT := 2;
14
v_approved CONSTANT INT := 3;
15
v_rejected CONSTANT INT := 7;
16
v_checked_in CONSTANT INT := 4;
17
BEGIN
18
/*
19
* 1. Update unit-level status (approve / reject)
20
*/
21
UPDATE public.multi_unit_visits AS muv
22
SET visitor_statuses = p_action_status,
23
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
24
modified_by = p_resident_user,
25
approval_reason = p_approval_reason,
26
approval_source = p_approval_source,
27
approved_by_resident_id = p_approved_by_resident_id,
28
is_manual_approval = p_is_manual_approval
29
WHERE muv.visitor_log_id = p_visitor_log_id
30
AND muv.unit_id = p_unit_id
31
AND muv.is_deleted = FALSE;
32
33
IF NOT FOUND THEN
34
RETURN;
35
END IF;
36
37
/*
38
* 2. Recalculate unit status counts
39
*/
40
SELECT
41
COUNT(*),
42
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_approved),
43
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_rejected)
44
INTO
45
local_total_visits,
46
local_approved_count,
47
local_rejected_count
48
FROM public.multi_unit_visits AS muv
49
WHERE muv.visitor_log_id = p_visitor_log_id
50
AND muv.is_deleted = FALSE;
51
52
/*
53
* 3. Decide parent visitor_logs status
54
*/
55
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
56
local_new_visitor_status := v_approved; -- 3 Approved
57
ELSIF local_approved_count > 0 THEN
58
local_new_visitor_status := v_partial_approved; -- 2 Partial Approved
59
ELSIF local_rejected_count > 0 THEN
60
local_new_visitor_status := v_rejected; -- 7 Rejected
61
ELSE
62
local_new_visitor_status := v_pending; -- 1 Pending
63
END IF;
64
65
/*
66
* 4. Update visitor_logs parent status
67
*/
68
UPDATE public.visitor_logs AS vl
69
SET
70
--visitor_status_id = local_new_visitor_status,
71
visitor_status_id = CASE
72
WHEN (
73
(local_new_visitor_status IN (v_approved, v_partial_approved)
74
AND vl.entry_time IS NULL)
75
OR COALESCE(p_is_manual_approval, false) = true
76
)
77
THEN v_checked_in
78
ELSE visitor_status_id
79
END,
80
81
entry_time = CASE
82
WHEN (
83
(local_new_visitor_status IN (v_approved, v_partial_approved)
84
AND vl.entry_time IS NULL)
85
OR COALESCE(p_is_manual_approval, false) = true
86
)
87
AND vl.entry_time IS NULL
88
THEN (NOW() AT TIME ZONE 'Asia/Kolkata')
89
ELSE vl.entry_time
90
END,
91
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
92
modified_by = p_resident_user
93
WHERE vl.id = p_visitor_log_id;
94
95
/*
96
* 5. Return response with approver details
97
*/
98
RETURN QUERY
99
SELECT
100
p_visitor_log_id,
101
p_unit_id,
102
r.id,
103
r.first_name,
104
r.last_name,
105
local_new_visitor_status
106
FROM public.residents r
107
WHERE r.user_id = p_resident_user
108
AND r.is_deleted = FALSE;
109
END;
110
$function$
|
|||||
| Function | create_poll | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_poll(p_apartment_id uuid, p_title character varying, p_description text, p_poll_type_id integer, p_poll_eligibility_type integer, p_status_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone, p_created_by uuid)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_poll_id int;
7
BEGIN
8
/* -----------------------------------------
9
1οΈβ£ Validate eligibility type
10
----------------------------------------- */
11
IF p_poll_eligibility_type NOT IN (1, 2) THEN
12
RAISE EXCEPTION
13
'Invalid poll eligibility type %',
14
p_poll_eligibility_type;
15
END IF;
16
17
/* -----------------------------------------
18
2οΈβ£ Create poll (NO SNAPSHOT DATA)
19
----------------------------------------- */
20
INSERT INTO polls (
21
apartment_id,
22
title,
23
description,
24
poll_type_id,
25
poll_eligibility_type_id,
26
status_id,
27
start_date,
28
end_date,
29
created_on_utc,
30
created_by,
31
is_deleted
32
)
33
VALUES (
34
p_apartment_id,
35
p_title,
36
p_description,
37
p_poll_type_id,
38
p_poll_eligibility_type,
39
p_status_id,
40
p_start_date,
41
p_end_date,
42
(NOW() AT TIME ZONE 'UTC'),
43
p_created_by,
44
false
45
)
46
RETURNING id INTO v_poll_id;
47
48
RETURN v_poll_id;
49
END;
50
$function$
|
|||||
| Function | get_visitors_by_unit_and_date_range | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitors_by_unit_and_date_range(p_apartment_id uuid, p_unit_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
2
RETURNS TABLE(id bigint, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_status_id integer, visitor_status text, first_name text, last_name text, image_url text, email text, visiting_from text, contact_number text, visitor_type_id integer, visitor_type text, vehicle_number text, identity_type_id integer, identity_type text, identity_number text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
6
BEGIN
7
RETURN QUERY
8
SELECT
9
vl.id AS id,
10
vl.entry_time AS entry_time,
11
vl.exit_time AS exit_time,
12
vl.visitor_status_id AS visitor_status_id,
13
vs.name::text AS visitor_status,
14
v.first_name::text,
15
v.last_name::text,
16
COALESCE(v.image_url, '')::text AS image_url,
17
18
ve.email::text,
19
vd.visiting_from::text,
20
vp.phone_number::text AS contact_number,
21
22
vl.visitor_type_id AS visitor_type_id,
23
vt.name::text AS visitor_type, -- β
FIX
24
25
COALESCE(vd.vehicle_number, vl.vehicle_number)::text AS vehicle_number,
26
27
vi.identity_type_id,
28
it.name::text AS identity_type, -- β
FIX
29
vi.identity_number::text,
30
31
v.created_by,
32
v.created_on_utc,
33
v.modified_by,
34
v.modified_on_utc
35
36
FROM multi_unit_visits muv
37
JOIN visitor_logs vl
38
ON vl.id = muv.visitor_log_id
39
40
JOIN visitors v
41
ON v.id = vl.visitor_id
42
AND v.is_deleted = false
43
44
JOIN visitor_types vt
45
ON vt.id = vl.visitor_type_id
46
AND vt.is_deleted = false
47
48
JOIN visitor_statuses vs
49
ON vs.id = vl.visitor_status_id
50
AND vs.is_deleted = false
51
52
JOIN units u
53
ON u.id = muv.unit_id
54
AND u.apartment_id = p_apartment_id
55
AND u.is_deleted = false
56
57
-- OPTIONAL DATA (SAFE LEFT JOINS)
58
LEFT JOIN visitor_details vd
59
ON vd.visitor_id = v.id
60
61
LEFT JOIN visitor_emails ve
62
ON ve.visitor_id = v.id
63
AND ve.is_active = true
64
65
LEFT JOIN visitor_phones vp
66
ON vp.visitor_id = v.id
67
AND vp.is_active = true
68
69
LEFT JOIN visitor_identities vi
70
ON vi.visitor_id = v.id
71
AND vi.is_active = true
72
73
LEFT JOIN identity_types it
74
ON it.id = vi.identity_type_id
75
AND it.is_deleted = false
76
77
WHERE
78
muv.unit_id = p_unit_id
79
AND muv.is_deleted = false
80
AND (
81
(vl.entry_time IS NOT NULL AND vl.entry_time >= p_start_date AND vl.entry_time < p_end_date)
82
OR
83
(vl.entry_time IS NULL AND vl.created_on_utc BETWEEN p_start_date AND p_end_date)
84
)
85
86
ORDER BY vl.entry_time DESC;
87
END;
88
$function$
|
|||||
| Function | get_unit_by_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_unit_by_id(p_unit_id integer)
2
RETURNS TABLE(unit_id integer, unit_name text, floor_id integer, building_id integer, unit_type_id integer, customer_id uuid, occupant_type_id integer, occupancy_type_id integer, area numeric, bhk_type text, phone_extension integer, e_intercom integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_user_name text, modified_by_user_name text, customer_name text, customer_contact_number text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
unit.id AS unit_id,
9
unit.name AS unit_name,
10
unit.floor_id,
11
unit.building_id,
12
unit.unit_type_id,
13
unit.customer_id, -- UUID
14
unit.occupant_type_id,
15
unit.occupancy_type_id,
16
unit.area::NUMERIC, -- Explicit casting to NUMERIC
17
unit.bhk_type,
18
unit.phone_extention::INT AS phone_extension, -- Explicit casting to INT
19
unit.e_intercom::INT AS e_intercom, -- Explicit casting to INT
20
unit.created_on_utc,
21
unit.modified_on_utc,
22
unit.created_by, -- UUID
23
unit.modified_by, -- UUID
24
COALESCE(created_by_user.first_name, '') || ' ' || COALESCE(created_by_user.last_name, '') AS created_by_user_name,
25
COALESCE(modified_by_user.first_name, '') || ' ' || COALESCE(modified_by_user.last_name, '') AS modified_by_user_name,
26
u.first_name || ' ' || u.last_name AS customer_name,
27
u.contact_number AS customer_contact_number
28
FROM units unit
29
LEFT JOIN users created_by_user
30
ON unit.created_by = created_by_user.id
31
LEFT JOIN users modified_by_user
32
ON unit.modified_by = modified_by_user.id
33
JOIN resident_units ru
34
ON unit.id = ru.unit_id
35
JOIN residents r
36
ON ru.resident_id = r.id
37
JOIN users u
38
ON r.user_id = u.id
39
WHERE unit.id = p_unit_id
40
AND ru.is_deleted = false;
41
END;
42
$function$
|
|||||
| Function | get_visitor_approval_info_by_approval_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitor_approval_info_by_approval_id(approval_id integer)
2
RETURNS TABLE(visitor_id integer, first_name text, last_name text, contact_number text, start_date date, end_date date, entry_time time without time zone, exit_time time without time zone, created_by_first_name text, created_by_last_name text, unit_name text, apartment_name text, city_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
-- Correct syntax for returning the query result
7
RETURN QUERY
8
SELECT
9
v.id AS visitor_id,
10
v.first_name,
11
v.last_name,
12
v.contact_number,
13
av.start_date,
14
av.end_date,
15
av.entry_time,
16
av.exit_time,
17
u.first_name::text AS created_by_first_name,
18
u.last_name::text AS created_by_last_name,
19
ut.name, -- Fetching unit_name from resident-units table,
20
ap.name::text as apartment_name,
21
ct.name::text as apartment_name
22
FROM
23
public.visitors v
24
JOIN
25
public.visitor_approvals av ON v.id = av.visitor_id
26
LEFT JOIN
27
public.users u ON av.created_by = u.id
28
LEFT JOIN
29
public.residents r ON u.id = r.user_id
30
LEFT JOIN
31
public.resident_units ru ON r.id = ru.resident_id
32
LEFT JOIN
33
public.units ut ON ut.id = ru.unit_id
34
LEFT JOIN
35
public.apartments ap ON ap.id = ut.apartment_id
36
LEFT JOIN
37
public.addresses ad ON ad.id = ap.address_id
38
LEFT JOIN
39
public.cities ct ON ct.id = ad.city_id
40
WHERE
41
av.id = approval_id AND av.is_deleted = false;
42
END;
43
$function$
|
|||||
| Function | get_water_tanker_deliveries | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_water_tanker_deliveries(p_company_id uuid, p_start_date date, p_end_date date, p_vendor_id uuid DEFAULT NULL::uuid)
2
RETURNS TABLE(id integer, delivery_date date, delivery_time time without time zone, vendor_id uuid, vendor_name text, tanker_capacity_liters integer, actual_received_liters integer, created_by uuid, created_by_name text, created_on_utc timestamp with time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp with time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
d.id,
9
d.delivery_date,
10
d.delivery_time,
11
d.vendor_id,
12
vu.first_name || ' ' || vu.last_name AS vendor_name,
13
d.tanker_capacity_liters,
14
d.actual_received_liters,
15
d.created_by,
16
cu.first_name || ' ' || cu.last_name AS created_by_name,
17
d.created_on_utc,
18
d.modified_by,
19
mu.first_name || ' ' || mu.last_name AS modified_by_name,
20
d.modified_on_utc
21
FROM public.water_tanker_deliveries d
22
LEFT JOIN public.users vu ON vu.id = d.vendor_id AND NOT vu.is_deleted
23
LEFT JOIN public.users cu ON cu.id = d.created_by AND NOT cu.is_deleted
24
LEFT JOIN public.users mu ON mu.id = d.modified_by AND NOT mu.is_deleted
25
WHERE d.company_id = p_company_id
26
AND (p_vendor_id IS NULL OR d.vendor_id = p_vendor_id)
27
AND d.delivery_date BETWEEN p_start_date AND p_end_date
28
AND NOT d.is_deleted
29
ORDER BY d.delivery_date, d.delivery_time;
30
END;
31
$function$
|
|||||
| Function | insert_bulk_water_tanker_deliveries | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.insert_bulk_water_tanker_deliveries(p_deliveries jsonb, p_created_by uuid, p_created_on_utc timestamp with time zone)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
inserted_count INTEGER := 0;
7
delivery_record RECORD;
8
BEGIN
9
FOR delivery_record IN
10
SELECT *
11
FROM jsonb_to_recordset(p_deliveries)
12
AS (
13
company_id UUID,
14
vendor_id UUID,
15
delivery_date DATE,
16
delivery_time TIME,
17
tanker_capacity_liters INTEGER,
18
actual_received_liters INTEGER
19
)
20
LOOP
21
INSERT INTO public.water_tanker_deliveries (
22
company_id,
23
vendor_id,
24
delivery_date,
25
delivery_time,
26
tanker_capacity_liters,
27
actual_received_liters,
28
created_by,
29
created_on_utc
30
)
31
VALUES (
32
delivery_record.company_id,
33
delivery_record.vendor_id,
34
delivery_record.delivery_date,
35
delivery_record.delivery_time,
36
delivery_record.tanker_capacity_liters,
37
delivery_record.actual_received_liters,
38
p_created_by,
39
p_created_on_utc
40
);
41
42
inserted_count := inserted_count + 1;
43
END LOOP;
44
45
RETURN inserted_count;
46
END;
47
$function$
|
|||||
| Function | approve_visitor_for_unit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.approve_visitor_for_unit(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid)
2
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, approver_first_name text, approver_last_name text, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
local_total_visits INT;
7
local_approved_count INT;
8
local_rejected_count INT;
9
local_new_visitor_status INT;
10
local_resident_id INT;
11
BEGIN
12
-- Step 0: Resolve user_id β resident_id
13
SELECT r.id
14
INTO local_resident_id
15
FROM public.residents r
16
INNER JOIN public.resident_units ru ON ru.resident_id = r.id
17
WHERE r.user_id = p_resident_user
18
AND ru.unit_id = p_unit_id
19
AND r.is_deleted = FALSE
20
AND ru.is_deleted = FALSE
21
LIMIT 1;
22
23
IF local_resident_id IS NULL THEN
24
-- invalid user or unit
25
RETURN;
26
END IF;
27
28
-- Step 1: Approve for this unit only
29
UPDATE public.multi_unit_visits
30
SET visitor_statuses = 2, -- Approved
31
modified_on_utc = NOW(),
32
modified_by = p_resident_user
33
WHERE visitor_log_id = p_visitor_log_id
34
AND unit_id = p_unit_id
35
AND is_deleted = FALSE;
36
37
IF NOT FOUND THEN
38
RETURN;
39
END IF;
40
41
-- Step 2: Recalculate parent visitor log status
42
SELECT
43
COUNT(*),
44
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2),
45
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6)
46
INTO
47
local_total_visits,
48
local_approved_count,
49
local_rejected_count
50
FROM public.multi_unit_visits muv
51
WHERE muv.visitor_log_id = p_visitor_log_id
52
AND muv.is_deleted = FALSE;
53
54
-- Step 3: Decide new parent status
55
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
56
local_new_visitor_status := 2; -- All Approved
57
ELSIF local_approved_count > 0 THEN
58
local_new_visitor_status := 7; -- Partial Approved
59
ELSIF local_rejected_count > 0 THEN
60
local_new_visitor_status := 6; -- Rejected
61
ELSE
62
local_new_visitor_status := 1; -- Pending
63
END IF;
64
65
UPDATE public.visitor_logs
66
SET visitor_status_id = local_new_visitor_status,
67
modified_on_utc = NOW(),
68
modified_by = p_resident_user
69
WHERE id = p_visitor_log_id;
70
71
-- Step 4: Return info + approver's name
72
RETURN QUERY
73
SELECT
74
p_visitor_log_id,
75
p_unit_id,
76
r.id,
77
r.first_name,
78
r.last_name,
79
local_new_visitor_status
80
FROM public.residents r
81
WHERE r.id = local_resident_id
82
AND r.is_deleted = FALSE;
83
84
END;
85
$function$
|
|||||
| Function | get_current_visitors_and_service_providers | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_current_visitors_and_service_providers(p_unit_id integer)
2
RETURNS TABLE(id integer, visitor_type_id integer, visitor_type text, serial_id integer, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_person_type text, visitor_person_sub_type text, visitor_status_id integer, visitor_status text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
CAST(ROW_NUMBER() OVER (ORDER BY combined.entry_time, combined.id) AS INT) AS serial_id,
9
combined.visitor_type_id,
10
combined.visitor_type,
11
combined.id,
12
combined.first_name,
13
combined.last_name,
14
combined.entry_time,
15
combined.exit_time,
16
combined.visitor_person_type,
17
combined.visitor_person_sub_type,
18
combined.visitor_status_id,
19
combined.visitor_status_name
20
FROM (
21
-- Visitors
22
SELECT
23
vt.id AS visitor_type_id,
24
'visitor'::TEXT AS visitor_type,
25
v.id,
26
v.first_name::TEXT,
27
v.last_name::TEXT,
28
vl.entry_time,
29
vl.exit_time,
30
vt."name"::TEXT AS visitor_person_type,
31
NULL::TEXT AS visitor_person_sub_type,
32
CASE
33
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 3
34
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 4
35
ELSE vl.visitor_status_id
36
END AS visitor_status_id,
37
CASE
38
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 'Checked In'
39
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 'Checked Out'
40
ELSE vs."name"::TEXT
41
END AS visitor_status_name
42
FROM
43
public.visitor_logs vl
44
JOIN public.visitors v ON vl.visitor_id = v.id
45
JOIN public.multi_unit_visits muv ON muv.visitor_log_id = vl.id
46
JOIN public.visitor_types vt ON v.visitor_type_id = vt.id
47
LEFT JOIN public.visitor_statuses vs ON vl.visitor_status_id = vs.id
48
WHERE
49
muv.unit_id = p_unit_id
50
AND vl.entry_time >= NOW() - INTERVAL '120 hour'
51
AND vl.is_deleted = FALSE
52
AND v.is_deleted = FALSE
53
AND muv.is_deleted = FALSE
54
UNION ALL
55
-- Service Providers
56
SELECT
57
vt_sp.id AS visitor_type_id,
58
'service_provider'::TEXT AS visitor_type,
59
sp.id,
60
sp.first_name::TEXT,
61
sp.last_name::TEXT,
62
spl.entry_time,
63
spl.exit_time,
64
spt."name"::TEXT AS visitor_person_type,
65
spst."name"::TEXT AS visitor_person_sub_type,
66
CASE
67
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 3
68
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 4
69
ELSE NULL
70
END AS visitor_status_id,
71
CASE
72
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 'Checked In'
73
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 'Checked Out'
74
ELSE NULL::TEXT
75
END AS visitor_status_name
76
FROM
77
public.service_provider_logs spl
78
JOIN public.service_providers sp ON spl.service_provider_id = sp.id
79
JOIN public.service_provider_types spt ON sp.service_provider_type_id = spt.id
80
JOIN public.service_provider_sub_types spst ON sp.service_provider_sub_type_id = spst.id
81
JOIN public.multi_unit_visits muv ON muv.unit_id = p_unit_id
82
LEFT JOIN public.visitor_types vt_sp ON vt_sp."name" = 'Service Provider' AND vt_sp.is_deleted = FALSE
83
WHERE
84
spl.entry_time >= NOW() - INTERVAL '120 hour'
85
AND spl.is_deleted = FALSE
86
AND sp.is_deleted = FALSE
87
) AS combined;
88
END;
89
$function$
|
|||||
| Function | insert_pin_record | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.insert_pin_record(p_effective_start_date_time timestamp without time zone, p_service_provider_id integer DEFAULT NULL::integer, p_visitor_id integer DEFAULT NULL::integer, p_delivery_id integer DEFAULT NULL::integer, p_effective_end_date_time timestamp without time zone DEFAULT NULL::timestamp without time zone)
2
RETURNS void
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_pin_code VARCHAR(6);
7
is_unique BOOLEAN := FALSE;
8
max_attempts INT := 1000; -- Limit the number of attempts to avoid infinite loop
9
attempts INT := 0;
10
BEGIN
11
-- Loop until a unique PIN is found or max attempts are reached
12
WHILE NOT is_unique AND attempts < max_attempts LOOP
13
v_pin_code := generate_random_pin();
14
15
-- Check if the PIN is unique
16
PERFORM 1 FROM pins WHERE pins.pin_code = v_pin_code;
17
18
IF NOT FOUND THEN
19
is_unique := TRUE;
20
ELSE
21
attempts := attempts + 1;
22
END IF;
23
END LOOP;
24
25
IF NOT is_unique THEN
26
RAISE EXCEPTION 'Could not generate a unique PIN after % attempts', max_attempts;
27
END IF;
28
29
-- Insert the new record
30
INSERT INTO pins (service_provider_id, visitor_id, delivery_id, pin_code, effective_start_date_time, effective_end_date_time)
31
VALUES (p_service_provider_id, p_visitor_id, p_delivery_id, v_pin_code, p_effective_start_date_time, p_effective_end_date_time);
32
END;
33
$function$
|
|||||
| Function | update_visitor_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.update_visitor_status(p_visitor_log_id integer, p_unit_id integer, p_visitor_status_id integer, p_modified_by uuid)
2
RETURNS TABLE(visitor_id integer, total_visits_count integer, approved_visits_count integer, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
local_total_visits int;
7
local_approved_count int;
8
local_rejected_count int; -- Added to track rejections
9
local_new_visitor_status int;
10
BEGIN
11
-- Step 1: Update the status for the specific unit.
12
UPDATE public.multi_unit_visits
13
SET visitor_statuses = p_visitor_status_id,
14
modified_on_utc = NOW(),
15
modified_by = p_modified_by
16
WHERE public.multi_unit_visits.visitor_log_id = p_visitor_log_id
17
AND public.multi_unit_visits.unit_id = p_unit_id
18
AND public.multi_unit_visits.is_deleted = false;
19
20
-- Step 2: Check if the update was successful.
21
IF NOT FOUND THEN
22
RETURN;
23
END IF;
24
25
-- Step 3: Recalculate the overall visitor log status.
26
SELECT
27
COUNT(*),
28
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
29
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
30
INTO
31
local_total_visits,
32
local_approved_count,
33
local_rejected_count
34
FROM public.multi_unit_visits AS muv
35
WHERE muv.visitor_log_id = p_visitor_log_id
36
AND muv.is_deleted = false;
37
38
-- Step 4: Determine the new overall status with corrected logic.
39
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
40
local_new_visitor_status := 2; -- Rule 1: All units have approved.
41
ELSIF local_approved_count > 0 THEN
42
-- FIX: If at least one unit approves, the status is "Partial Approved",
43
-- even if another unit rejects.
44
local_new_visitor_status := 7; -- Rule 2: At least one approval, but not all.
45
ELSIF local_rejected_count > 0 THEN
46
local_new_visitor_status := 6; -- Rule 3: No approvals yet, but at least one rejection.
47
ELSE
48
local_new_visitor_status := 1; -- Rule 4: No approvals and no rejections yet.
49
END IF;
50
51
-- Step 5: Update the parent visitor_logs table.
52
UPDATE public.visitor_logs
53
SET visitor_status_id = local_new_visitor_status,
54
modified_on_utc = NOW(),
55
modified_by = p_modified_by
56
WHERE id = p_visitor_log_id;
57
58
-- Step 6: On success, return the calculated values.
59
RETURN QUERY SELECT p_visitor_log_id, local_total_visits, local_approved_count, local_new_visitor_status;
60
END;
61
$function$
|
|||||
| Function | check_or_create_event_occurrence | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.check_or_create_event_occurrence(p_event_uuid uuid, p_occ_date date, p_user_uuid uuid)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_occurrence_id INT;
7
v_start_time TIME;
8
v_end_time TIME;
9
BEGIN
10
SELECT id INTO v_occurrence_id
11
FROM event_occurrences
12
WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date AND is_deleted = false;
13
14
IF v_occurrence_id IS NOT NULL THEN
15
RETURN v_occurrence_id;
16
END IF;
17
18
-- Extract only the TIME part from the event's start and end time
19
SELECT start_time::time, end_time::time
20
INTO v_start_time, v_end_time
21
FROM events
22
WHERE id = p_event_uuid;
23
24
-- Insert new occurrence with time applied to p_occ_date
25
INSERT INTO event_occurrences (
26
event_id, occurrence_date, start_time, end_time,
27
generated_from_recurrence, created_on_utc, created_by, is_deleted
28
)
29
VALUES (
30
p_event_uuid,
31
p_occ_date,
32
(p_occ_date + v_start_time)::timestamp,
33
(p_occ_date + v_end_time)::timestamp,
34
(SELECT is_recurring FROM events WHERE id = p_event_uuid),
35
now(),
36
p_user_uuid,
37
false
38
)
39
RETURNING id INTO v_occurrence_id;
40
41
RETURN v_occurrence_id;
42
END;
43
$function$
|
|||||
| Function | debug_visitor_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.debug_visitor_logs(v_apartment_id uuid, log_date timestamp without time zone)
2
RETURNS TABLE(id integer, visitor_id integer, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visiting_from text, contact_number text, visitor_type_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
vl.id, v.id, v.first_name, v.last_name, vl.entry_time, vl.exit_time,
9
vl.visiting_from, v.contact_number, v.visitor_type_id
10
FROM
11
visitors v
12
JOIN
13
visitor_logs vl ON v.id = vl.visitor_id
14
WHERE
15
v.apartment_id = v_apartment_id
16
AND DATE(vl.entry_time) = DATE(log_date)
17
AND v.is_deleted = FALSE
18
AND vl.is_deleted = FALSE;
19
END;
20
$function$
|
|||||
| Function | get_all_apartment_or_org_residents | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_apartment_or_org_residents(apartmentid uuid, isgetall boolean)
2
RETURNS TABLE(id integer, resident_name text, user_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
org_id uuid;
7
BEGIN
8
IF isGetAll THEN
9
SELECT a.organization_id INTO org_id
10
FROM public.apartments a
11
WHERE a.id = apartmentid
12
AND a.is_deleted = false;
13
RETURN QUERY
14
SELECT
15
row_number() OVER ()::int AS id,
16
r.first_name || ' ' || r.last_name AS resident_name,
17
r.user_id
18
FROM public.apartments a
19
INNER JOIN public.residents r ON r.apartment_id = a.id
20
WHERE a.organization_id = org_id
21
AND r.is_deleted = false
22
AND a.is_deleted = false;
23
ELSE
24
RETURN QUERY
25
SELECT
26
row_number() OVER ()::int AS id,
27
r.first_name || ' ' || r.last_name AS resident_name,
28
r.user_id
29
FROM public.apartments a
30
INNER JOIN public.residents r ON r.apartment_id = a.id
31
WHERE a.id = apartmentid
32
AND r.is_deleted = false
33
AND a.is_deleted = false;
34
END IF;
35
END;
36
$function$
|
|||||
| Function | get_all_visitor_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_visitor_logs(v_apartment_id uuid, log_date timestamp without time zone)
2
RETURNS TABLE(id integer, visitor_id integer, first_name text, last_name text, unit_id integer, unit_name text, latest_entry_time timestamp without time zone, latest_exit_time timestamp without time zone, visiting_from text, contact_number text, visitor_type_id integer, visitor_type_name text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
vl.id as id,
9
v.id AS visitor_id,
10
v.first_name,
11
v.last_name,
12
vul.unit_id,
13
u.name AS unit_name,
14
MAX(vl.entry_time) AS latest_entry_time,
15
MAX(vl.exit_time) AS latest_exit_time,
16
vl.visiting_from,
17
v.contact_number,
18
v.visitor_type_id,
19
vt.name :: text
20
FROM
21
visitors v
22
JOIN
23
visitor_logs vl ON v.id = vl.visitor_id
24
LEFT JOIN
25
visitor_unit_logs vul ON vul.visitor_log_id = vl.id -- Join visitor_logs to visitor_unit_logs
26
LEFT JOIN
27
units u ON u.id = vul.unit_id -- Join visitor_unit_logs to units to get unit_name
28
LEFT JOIN
29
visitor_types vt ON vt.id = v.visitor_type_id
30
WHERE
31
v.apartment_id = v_apartment_id -- Use the renamed parameter 'v_apartment_id'
32
AND DATE(vl.entry_time) = DATE(log_date) -- Match the date part of timestamp
33
AND v.is_deleted = FALSE
34
AND vl.is_deleted = FALSE
35
AND vul.is_deleted = FALSE -- Ensure visitor_unit_logs is not deleted
36
GROUP BY
37
vl.Id, v.id, v.first_name, v.last_name, vul.unit_id, u.name, v.identity_number,
38
vl.visiting_from, v.contact_number, v.visitor_type_id, vt.name;
39
END;
40
$function$
|
|||||
| Function | process_visitor_unit_response | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.process_visitor_unit_response(p_visitor_log_id integer, p_unit_id integer, p_visitor_status_id integer, p_modified_by uuid)
2
RETURNS TABLE(visitor_log_id integer, total_visits_count integer, approved_visits_count integer, final_status_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
local_total_visits int;
7
local_approved_count int;
8
local_new_visitor_status int;
9
BEGIN
10
-- Step 1: Update the status for the specific unit.
11
UPDATE public.multi_unit_visits
12
SET visitor_statuses = p_visitor_status_id,
13
modified_on_utc = NOW(),
14
modified_by = p_modified_by
15
-- FIX: Explicitly reference the table's column to avoid ambiguity
16
WHERE public.multi_unit_visits.visitor_log_id = p_visitor_log_id
17
AND public.multi_unit_visits.unit_id = p_unit_id
18
AND public.multi_unit_visits.is_deleted = false;
19
20
-- Step 2: Check if the update was successful.
21
IF NOT FOUND THEN
22
RETURN;
23
END IF;
24
25
-- Step 3: Recalculate the overall visitor log status.
26
SELECT
27
COUNT(*),
28
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2) -- Approved
29
INTO
30
local_total_visits,
31
local_approved_count
32
FROM public.multi_unit_visits AS muv
33
-- FIX: Use an alias for clarity and to prevent ambiguity
34
WHERE muv.visitor_log_id = p_visitor_log_id
35
AND muv.is_deleted = false;
36
37
-- Step 4: Determine the new overall status.
38
IF EXISTS (
39
SELECT 1
40
FROM public.multi_unit_visits
41
-- FIX: Explicitly reference the table's column
42
WHERE public.multi_unit_visits.visitor_log_id = p_visitor_log_id
43
AND public.multi_unit_visits.visitor_statuses = 6 -- Rejected
44
AND public.multi_unit_visits.is_deleted = false
45
) THEN
46
local_new_visitor_status := 6; -- Rejected
47
ELSIF local_approved_count > 0 AND local_approved_count = local_total_visits THEN
48
local_new_visitor_status := 2; -- Approved
49
ELSE
50
local_new_visitor_status := 1; -- Pending
51
END IF;
52
53
-- Step 5: Update the parent visitor_logs table.
54
UPDATE public.visitor_logs
55
SET visitor_status_id = local_new_visitor_status,
56
modified_on_utc = NOW(),
57
modified_by = p_modified_by
58
WHERE id = p_visitor_log_id;
59
60
-- Step 6: On success, return the calculated values.
61
RETURN QUERY SELECT p_visitor_log_id, local_total_visits, local_approved_count, local_new_visitor_status;
62
END;
63
$function$
|
|||||
| Function | save_visitor_and_pending | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.save_visitor_and_pending(p_apartment_id uuid, p_first_name text, p_last_name text, p_email text, p_contact_number text, p_vehicle_number text, p_identity_type_id integer, p_identity_number text, p_visitor_type_id integer, p_visiting_from text, p_unit_ids integer[], p_created_by uuid)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id INTEGER;
7
v_visitor_log_id INTEGER;
8
v_unit_id INTEGER;
9
BEGIN
10
-- Check if the visitor already exists based on first_name and contact_number
11
SELECT id INTO v_visitor_id
12
FROM public.visitors
13
WHERE first_name = p_first_name
14
AND contact_number = p_contact_number
15
AND is_deleted = false;
16
17
IF FOUND THEN
18
RAISE NOTICE 'Visitor already exists with ID: %, using existing ID.', v_visitor_id;
19
ELSE
20
RAISE NOTICE 'Visitor does not exist. Inserting new visitor.';
21
END IF;
22
23
-- If visitor exists, use the existing visitor_id, else insert a new visitor
24
IF NOT FOUND THEN
25
-- Insert into visitors table (auto-generated id will be used)
26
INSERT INTO public.visitors (
27
first_name,
28
last_name,
29
email,
30
contact_number,
31
visiting_from,
32
visitor_type_id,
33
vehicle_number,
34
identity_type_id,
35
identity_number,
36
created_on_utc,
37
created_by,
38
apartment_id
39
)
40
VALUES (
41
p_first_name,
42
p_last_name,
43
p_email,
44
p_contact_number,
45
p_visiting_from,
46
p_visitor_type_id,
47
p_vehicle_number,
48
p_identity_type_id,
49
p_identity_number,
50
NOW(),
51
p_created_by,
52
p_apartment_id
53
)
54
RETURNING id INTO v_visitor_id; -- Capture the auto-generated id
55
RAISE NOTICE 'New visitor inserted with ID: %', v_visitor_id;
56
END IF;
57
58
-- Insert into visitor_logs table using the visitor_id
59
INSERT INTO public.visitor_logs (
60
visitor_id,
61
visitor_type_id,
62
visiting_from,
63
visitor_status_id,
64
entry_time,
65
exit_time,
66
created_on_utc,
67
created_by
68
)
69
VALUES (
70
v_visitor_id,
71
p_visitor_type_id,
72
p_visiting_from,
73
1, -- Assuming 1 is the default visitor status
74
NULL,
75
NULL,
76
NOW(),
77
p_created_by
78
)
79
RETURNING id INTO v_visitor_log_id;
80
RAISE NOTICE 'Visitor log created for visitor ID: %', v_visitor_id;
81
82
-- Loop through each unit_id in the array and insert a record into multi_unit_visits table
83
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
84
INSERT INTO public.multi_unit_visits (
85
visitor_log_id,
86
unit_id,
87
visitor_statuses,
88
created_on_utc,
89
is_deleted,
90
created_by
91
)
92
VALUES (
93
v_visitor_log_id,
94
v_unit_id,
95
1, -- Assuming 1 is the default visitor status
96
NOW(),
97
FALSE,
98
p_created_by
99
);
100
RAISE NOTICE 'Multi-unit visit created for visitor_log_id: % and unit_id: %', v_visitor_log_id, v_unit_id;
101
END LOOP;
102
103
-- Return the visitor_log_id
104
RETURN v_visitor_log_id;
105
END;
106
$function$
|
|||||
| Function | get_visitors_and_service_providers_in_timespan | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitors_and_service_providers_in_timespan(p_unit_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
2
RETURNS TABLE(id integer, visitor_type_id integer, visitor_type text, serial_id integer, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_person_type text, visitor_person_sub_type text, visitor_status_id integer, visitor_status text)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
CAST(ROW_NUMBER() OVER (ORDER BY combined.entry_time, combined.id) AS INT) AS serial_id,
9
combined.visitor_type_id,
10
combined.visitor_type,
11
combined.id,
12
combined.first_name,
13
combined.last_name,
14
combined.entry_time,
15
combined.exit_time,
16
combined.visitor_person_type,
17
combined.visitor_person_sub_type,
18
combined.visitor_status_id,
19
combined.visitor_status_name
20
FROM (
21
-- Visitors
22
SELECT
23
vt.id AS visitor_type_id,
24
'visitor'::TEXT AS visitor_type,
25
v.id,
26
v.first_name::TEXT,
27
v.last_name::TEXT,
28
vl.entry_time,
29
vl.exit_time,
30
vt."name"::TEXT AS visitor_person_type,
31
NULL::TEXT AS visitor_person_sub_type,
32
CASE
33
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 3
34
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 4
35
ELSE vl.visitor_status_id
36
END AS visitor_status_id,
37
CASE
38
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 'Checked In'
39
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 'Checked Out'
40
ELSE vs."name"::TEXT
41
END AS visitor_status_name
42
FROM
43
public.visitor_logs vl
44
JOIN public.visitors v ON vl.visitor_id = v.id
45
JOIN public.multi_unit_visits muv ON muv.visitor_log_id = vl.id
46
JOIN public.visitor_types vt ON v.visitor_type_id = vt.id
47
LEFT JOIN public.visitor_statuses vs ON vl.visitor_status_id = vs.id
48
WHERE
49
muv.unit_id = p_unit_id
50
AND vl.entry_time BETWEEN p_start_date AND p_end_date
51
AND vl.is_deleted = FALSE
52
AND v.is_deleted = FALSE
53
AND muv.is_deleted = FALSE
54
UNION ALL
55
-- Service Providers
56
SELECT
57
vt_sp.id AS visitor_type_id,
58
'service_provider'::TEXT AS visitor_type,
59
sp.id,
60
sp.first_name::TEXT,
61
sp.last_name::TEXT,
62
spl.entry_time,
63
spl.exit_time,
64
spt."name"::TEXT AS visitor_person_type,
65
spst."name"::TEXT AS visitor_person_sub_type,
66
CASE
67
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 3
68
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 4
69
ELSE NULL
70
END AS visitor_status_id,
71
CASE
72
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 'Checked In'
73
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 'Checked Out'
74
ELSE NULL::TEXT
75
END AS visitor_status_name
76
FROM
77
public.service_provider_logs spl
78
JOIN public.service_providers sp ON spl.service_provider_id = sp.id
79
JOIN public.service_provider_types spt ON sp.service_provider_type_id = spt.id
80
JOIN public.service_provider_sub_types spst ON sp.service_provider_sub_type_id = spst.id
81
JOIN public.multi_unit_visits muv ON muv.unit_id = p_unit_id
82
LEFT JOIN public.visitor_types vt_sp ON vt_sp."name" = 'Service Provider' AND vt_sp.is_deleted = FALSE
83
WHERE
84
spl.entry_time BETWEEN p_start_date AND p_end_date
85
AND spl.is_deleted = FALSE
86
AND sp.is_deleted = FALSE
87
) AS combined;
88
END;
89
$function$
|
|||||
| Function | get_user_full_details | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_user_full_details(p_user_id uuid, p_apartment_id uuid)
2
RETURNS TABLE(user_id uuid, full_name character varying, email character varying, contact_number character varying, unit_name character varying, vehicles text[], address_line1 character varying, address_line2 character varying, zip_code character varying, country_name character varying, state_name character varying, city_name character varying, apartment_id uuid)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
u.id AS user_id,
9
CONCAT(u.first_name, ' ', u.last_name)::VARCHAR AS full_name,
10
u.email::VARCHAR,
11
u.contact_number::VARCHAR,
12
un.name::VARCHAR AS unit_name,
13
ARRAY_AGG(DISTINCT CONCAT(vt.name, ' - ', v.vehicle_number))::TEXT[] AS vehicles,
14
a.address_line1::VARCHAR,
15
a.address_line2::VARCHAR,
16
a.zip_code::VARCHAR,
17
co.name::VARCHAR AS country_name,
18
s.name::VARCHAR AS state_name,
19
ci.name::VARCHAR AS city_name,
20
un.apartment_id
21
FROM
22
users u
23
INNER JOIN
24
residents r
25
ON u.id = r.user_id
26
LEFT JOIN
27
resident_units ru
28
ON r.id = ru.resident_id
29
AND ru.is_deleted = FALSE
30
LEFT JOIN
31
units un
32
ON ru.unit_id = un.id
33
AND un.apartment_id = p_apartment_id -- β
Filter by company
34
LEFT JOIN
35
vehicles v
36
ON un.id = v.unit_id
37
AND v.is_deleted = FALSE
38
LEFT JOIN
39
vehicle_types vt
40
ON v.vehicle_type_id = vt.id
41
LEFT JOIN
42
addresses a
43
ON r.permanent_address_id = a.id
44
LEFT JOIN
45
countries co
46
ON a.country_id = co.id
47
LEFT JOIN
48
states s
49
ON a.state_id = s.id
50
LEFT JOIN
51
cities ci
52
ON a.city_id = ci.id
53
WHERE
54
u.id = p_user_id
55
AND u.is_deleted = FALSE
56
AND r.is_deleted = FALSE
57
AND r.apartment_id = p_apartment_id -- β
Restrict resident scope to the company
58
GROUP BY
59
u.id, u.first_name, u.last_name, u.email, u.contact_number,
60
un.name, a.address_line1, a.address_line2, a.zip_code,
61
co.name, s.name, ci.name, un.apartment_id
62
ORDER BY
63
un.name;
64
END;
65
$function$
|
|||||
| Function | get_all_visitors_by_unit | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_visitors_by_unit(p_apartment_id uuid, p_unit_id integer)
2
RETURNS TABLE(id integer, first_name text, last_name text, email text, visiting_from text, contact_number text, visitor_type_id integer, visitor_type_name character varying, vehicle_number text, identity_type_id integer, identity_number text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
v.id,
9
v.first_name,
10
v.last_name,
11
v.email,
12
v.visiting_from,
13
v.contact_number,
14
v.visitor_type_id,
15
vt.name AS visitor_type_name,
16
v.vehicle_number,
17
v.identity_type_id,
18
v.identity_number,
19
v.created_by,
20
v.created_on_utc,
21
v.modified_by,
22
v.modified_on_utc
23
FROM
24
public.visitors v
25
INNER JOIN
26
public.visitor_types vt ON v.visitor_type_id = vt.id
27
WHERE
28
v.apartment_id = p_apartment_id
29
AND v.unit_id = p_unit_id
30
AND v.is_deleted = false
31
AND (vt.is_deleted = false OR vt.is_deleted IS NULL);
32
END;
33
$function$
|
|||||
| Function | generate_ticket_number | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.generate_ticket_number(p_apartment_id uuid, p_category_id integer)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_today date := CURRENT_DATE;
7
v_short_code varchar(10);
8
v_last_number int;
9
v_formatted_date text;
10
v_formatted_sequence text;
11
v_ticket_number text;
12
BEGIN
13
-- 1οΈβ£ Fetch short code for the category
14
SELECT short_code INTO v_short_code
15
FROM public.ticket_categories
16
WHERE id = p_category_id AND is_deleted = false;
17
18
IF v_short_code IS NULL OR v_short_code = '' THEN
19
RAISE EXCEPTION 'ShortCode not found for category %', p_category_id;
20
END IF;
21
22
23
-- 2οΈβ£ Insert or update sequence (handles concurrency)
24
INSERT INTO public.ticket_number_sequences (apartment_id, ticket_date, category_id, last_number)
25
VALUES (p_apartment_id, v_today, p_category_id, 1)
26
ON CONFLICT (apartment_id, ticket_date, category_id)
27
DO UPDATE
28
SET last_number = ticket_number_sequences.last_number + 1
29
RETURNING last_number
30
INTO v_last_number;
31
32
33
-- 3οΈβ£ Format date as DDMMYY
34
v_formatted_date := to_char(v_today, 'DDMMYY');
35
36
-- 4οΈβ£ Format sequence as 2-digit (01, 02, 03β¦)
37
v_formatted_sequence := LPAD(v_last_number::text, 2, '0');
38
39
-- 5οΈβ£ Build final ticket number
40
v_ticket_number := v_formatted_date || '-' || v_short_code || '-' || v_formatted_sequence;
41
42
RETURN v_ticket_number;
43
END;
44
$function$
|
|||||
| Function | get_ticket_analytics | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_ticket_analytics(p_apartment_id uuid, p_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_to timestamp without time zone DEFAULT NULL::timestamp without time zone, p_recent_limit integer DEFAULT 5)
2
RETURNS jsonb
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
result jsonb;
7
BEGIN
8
WITH base AS (
9
SELECT *
10
FROM public.tickets t
11
WHERE t.apartment_id = p_apartment_id
12
AND NOT t.is_deleted
13
AND (p_from IS NULL OR t.created_on_utc >= p_from)
14
AND (p_to IS NULL OR t.created_on_utc <= p_to)
15
),
16
by_status AS (
17
SELECT
18
coalesce(t.ticket_status_id, 0) AS status_id,
19
count(*) AS cnt
20
FROM base t
21
GROUP BY coalesce(t.ticket_status_id, 0)
22
ORDER BY cnt DESC
23
),
24
by_priority AS (
25
SELECT
26
coalesce(t.ticket_priority_id, 0) AS priority_id,
27
count(*) AS cnt
28
FROM base t
29
GROUP BY coalesce(t.ticket_priority_id, 0)
30
ORDER BY cnt DESC
31
),
32
by_category AS (
33
SELECT
34
coalesce(t.ticket_category_id, 0) AS category_id,
35
count(*) AS cnt
36
FROM base t
37
GROUP BY coalesce(t.ticket_category_id, 0)
38
ORDER BY cnt DESC
39
),
40
recent AS (
41
SELECT
42
t.id,
43
t.ticket_number,
44
t.title,
45
t.ticket_status_id,
46
t.ticket_priority_id,
47
t.ticket_category_id,
48
t.is_on_hold,
49
t.created_on_utc
50
FROM base t
51
ORDER BY t.created_on_utc DESC
52
LIMIT p_recent_limit
53
)
54
SELECT jsonb_build_object(
55
'totalTickets', (SELECT count(*) FROM base),
56
'onHoldTickets', (SELECT count(*) FROM base WHERE is_on_hold),
57
'ticketsByStatus', COALESCE((
58
SELECT jsonb_agg(jsonb_build_object(
59
'id', s.status_id,
60
'name', ts.name,
61
'count', s.cnt
62
) ORDER BY s.cnt DESC)
63
FROM by_status s
64
LEFT JOIN public.ticket_statuses ts ON ts.id = s.status_id
65
), '[]'::jsonb),
66
'ticketsByPriority', COALESCE((
67
SELECT jsonb_agg(jsonb_build_object(
68
'id', p.priority_id,
69
'name', tp.name,
70
'count', p.cnt
71
) ORDER BY p.cnt DESC)
72
FROM by_priority p
73
LEFT JOIN public.ticket_priorities tp ON tp.id = p.priority_id
74
), '[]'::jsonb),
75
'ticketsByCategory', COALESCE((
76
SELECT jsonb_agg(jsonb_build_object(
77
'id', c.category_id,
78
'name', tc.name,
79
'count', c.cnt
80
) ORDER BY c.cnt DESC)
81
FROM by_category c
82
LEFT JOIN public.ticket_categories tc ON tc.id = c.category_id
83
), '[]'::jsonb),
84
'recentTickets', COALESCE((
85
SELECT jsonb_agg(row_to_json(r))
86
FROM (
87
SELECT
88
r.id,
89
r.ticket_number,
90
r.title,
91
COALESCE(ts.name, '') AS status,
92
COALESCE(tp.name, '') AS priority,
93
COALESCE(tc.name, '') AS category,
94
r.is_on_hold,
95
r.created_on_utc
96
FROM recent r
97
LEFT JOIN public.ticket_statuses ts ON ts.id = r.ticket_status_id
98
LEFT JOIN public.ticket_priorities tp ON tp.id = r.ticket_priority_id
99
LEFT JOIN public.ticket_categories tc ON tc.id = r.ticket_category_id
100
ORDER BY r.created_on_utc DESC
101
) r
102
), '[]'::jsonb)
103
) INTO result;
104
105
RETURN result;
106
END;
107
$function$
|
|||||
| Function | create_service_provider_api | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_service_provider_api(p_first_name text, p_last_name text, p_contact_number text, p_visitor_type_id integer, p_email text, p_visiting_from text, p_service_provider_type_id integer, p_service_provider_sub_type_id integer, p_vehicle_number text, p_identity_type_id integer, p_identity_number text, p_validity_date date, p_police_verification_status boolean, p_is_hireable boolean, p_is_visible boolean, p_is_frequent_visitor boolean, p_apartment_id uuid, p_pin text, p_created_by uuid, p_permanent_address_id uuid, p_present_address_id uuid)
2
RETURNS integer
3
LANGUAGE sql
4
AS $function$
5
SELECT service_provider_id
6
FROM public.create_service_provider(
7
p_first_name,
8
p_last_name,
9
p_contact_number,
10
p_visitor_type_id,
11
p_email,
12
p_visiting_from,
13
p_service_provider_type_id,
14
p_service_provider_sub_type_id,
15
p_vehicle_number,
16
p_identity_type_id,
17
p_identity_number,
18
p_validity_date,
19
p_police_verification_status,
20
p_is_hireable,
21
p_is_visible,
22
p_is_frequent_visitor,
23
p_apartment_id,
24
p_pin,
25
p_created_by,
26
p_permanent_address_id,
27
p_present_address_id
28
);
29
$function$
|
|||||
| Function | create_service_provider | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_service_provider(p_first_name character varying, p_last_name character varying, p_contact_number character varying, p_visitor_type_id integer, p_email character varying, p_visiting_from character varying, p_service_provider_type_id integer, p_service_provider_sub_type_id integer, p_vehicle_number character varying, p_identity_type_id integer, p_identity_number character varying, p_validity_date timestamp without time zone, p_police_verification_status boolean, p_is_hireable boolean, p_is_visible boolean, p_is_frequent_visitor boolean, p_apartment_id uuid, p_pin character varying, p_created_by uuid, p_permanent_address_id uuid, p_present_address_id uuid)
2
RETURNS TABLE(service_provider_id integer)
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id integer;
7
v_service_provider_id integer;
8
BEGIN
9
/* ----------------------------------------------------
10
1. Create Visitor
11
---------------------------------------------------- */
12
INSERT INTO visitors (
13
first_name,
14
last_name,
15
visitor_type_id,
16
created_by,
17
created_on_utc
18
)
19
VALUES (
20
p_first_name,
21
p_last_name,
22
p_visitor_type_id,
23
p_created_by,
24
now()
25
)
26
RETURNING id INTO v_visitor_id;
27
28
/* ----------------------------------------------------
29
2. Visitor Phone
30
---------------------------------------------------- */
31
INSERT INTO visitor_phones (
32
visitor_id,
33
phone_number,
34
is_active,
35
valid_from
36
)
37
VALUES (
38
v_visitor_id,
39
p_contact_number,
40
true,
41
now()
42
);
43
44
/* ----------------------------------------------------
45
3. Visitor Email (optional)
46
---------------------------------------------------- */
47
IF p_email IS NOT NULL THEN
48
INSERT INTO visitor_emails (
49
visitor_id,
50
email,
51
is_active,
52
valid_from
53
)
54
VALUES (
55
v_visitor_id,
56
p_email,
57
true,
58
now()
59
);
60
END IF;
61
62
/* ----------------------------------------------------
63
4. Visitor Identity (optional)
64
---------------------------------------------------- */
65
IF p_identity_type_id IS NOT NULL AND p_identity_number IS NOT NULL THEN
66
INSERT INTO visitor_identities (
67
visitor_id,
68
identity_type_id,
69
identity_number,
70
is_active,
71
created_by,
72
created_on_utc
73
)
74
VALUES (
75
v_visitor_id,
76
p_identity_type_id,
77
p_identity_number,
78
true,
79
p_created_by,
80
now()
81
);
82
END IF;
83
84
/* ----------------------------------------------------
85
5. Visitor Details (optional)
86
---------------------------------------------------- */
87
IF p_visiting_from IS NOT NULL OR p_vehicle_number IS NOT NULL THEN
88
INSERT INTO visitor_details (
89
visitor_id,
90
visiting_from,
91
vehicle_number,
92
created_by,
93
created_on_utc
94
)
95
VALUES (
96
v_visitor_id,
97
p_visiting_from,
98
p_vehicle_number,
99
p_created_by,
100
now()
101
);
102
END IF;
103
104
/* ----------------------------------------------------
105
6. Visitor β Apartment mapping
106
---------------------------------------------------- */
107
INSERT INTO visitor_apartments (
108
visitor_id,
109
visitor_apartment_id,
110
first_seen,
111
is_blocked
112
)
113
VALUES (
114
v_visitor_id,
115
p_apartment_id,
116
now(),
117
false
118
);
119
120
/* ----------------------------------------------------
121
7. Create Service Provider
122
---------------------------------------------------- */
123
INSERT INTO service_providers (
124
service_provider_type_id,
125
service_provider_sub_type_id,
126
visitor_id,
127
is_frequent_visitor,
128
is_hireable,
129
is_visible,
130
police_verification_status,
131
pin,
132
valid_from,
133
created_by,
134
created_on_utc
135
)
136
VALUES (
137
p_service_provider_type_id,
138
p_service_provider_sub_type_id,
139
v_visitor_id,
140
p_is_frequent_visitor,
141
p_is_hireable,
142
p_is_visible,
143
p_police_verification_status,
144
p_pin,
145
p_validity_date,
146
p_created_by,
147
now()
148
)
149
RETURNING id INTO v_service_provider_id;
150
151
/* ----------------------------------------------------
152
8. Addresses (optional)
153
---------------------------------------------------- */
154
IF p_permanent_address_id IS NOT NULL THEN
155
INSERT INTO service_provider_addresses (
156
service_provider_id,
157
address_id,
158
is_permanent_address,
159
is_present_address
160
)
161
VALUES (
162
v_service_provider_id,
163
p_permanent_address_id,
164
true,
165
false
166
);
167
END IF;
168
169
IF p_present_address_id IS NOT NULL THEN
170
INSERT INTO service_provider_addresses (
171
service_provider_id,
172
address_id,
173
is_permanent_address,
174
is_present_address
175
)
176
VALUES (
177
v_service_provider_id,
178
p_present_address_id,
179
false,
180
true
181
);
182
END IF;
183
184
/* ----------------------------------------------------
185
9. Return
186
---------------------------------------------------- */
187
service_provider_id := v_service_provider_id;
188
RETURN;
189
END;
190
$function$
|
|||||
| Function | get_visitor_inside_counts | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_visitor_inside_counts(p_apartment_id uuid, p_today_only boolean)
2
RETURNS TABLE(id bigint, visitor_log_id bigint, visitor_id bigint, first_name text, last_name text, visitor_type_id integer, visitor_type_name text, entry_time timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
ROW_NUMBER() OVER (ORDER BY vl.entry_time ASC)::bigint AS id, -- Incremental ID
9
vl.id AS visitor_log_id,
10
v.id AS visitor_id,
11
v.first_name,
12
v.last_name,
13
vl.visitor_type_id,
14
vt.name::text AS visitor_type_name,
15
vl.entry_time
16
FROM
17
visitor_logs vl
18
INNER JOIN
19
visitors v ON v.id = vl.visitor_id
20
INNER JOIN
21
visitor_types vt ON vt.id = vl.visitor_type_id
22
WHERE
23
vl.apartment_id = p_apartment_id
24
AND vl.exit_time IS NULL
25
AND (NOT p_today_only OR vl.entry_time::date = CURRENT_DATE)
26
AND v.is_deleted = FALSE
27
AND vt.is_deleted = FALSE
28
ORDER BY
29
vl.entry_time ASC;
30
END;
31
$function$
|
|||||
| Function | get_all_visitors_by_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_visitors_by_apartment(p_apartment_id uuid)
2
RETURNS TABLE(id bigint, first_name text, last_name text, email text, visiting_from text, contact_number text, visitor_type_id integer, visitor_type_name character varying, vehicle_number text, identity_type_id integer, identity_number text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone)
3
LANGUAGE plpgsql
4
AS $function$
5
BEGIN
6
RETURN QUERY
7
SELECT
8
v.id, -- this is bigint, matching the table definition
9
v.first_name,
10
v.last_name,
11
ve.email,
12
vd.visiting_from,
13
vp.phone_number AS contact_number,
14
v.visitor_type_id,
15
vt.name AS visitor_type_name,
16
vd.vehicle_number,
17
vi.identity_type_id,
18
vi.identity_number,
19
v.created_by,
20
v.created_on_utc,
21
v.modified_by,
22
v.modified_on_utc
23
FROM
24
public.visitors v
25
INNER JOIN
26
public.visitor_types vt ON v.visitor_type_id = vt.id
27
LEFT JOIN
28
public.visitor_emails ve ON v.id = ve.visitor_id AND ve.is_active = TRUE
29
LEFT JOIN
30
public.visitor_details vd ON v.id = vd.visitor_id
31
LEFT JOIN
32
public.visitor_phones vp ON v.id = vp.visitor_id AND vp.is_active = TRUE
33
LEFT JOIN
34
public.visitor_identities vi ON v.id = vi.visitor_id AND vi.is_active = TRUE
35
INNER JOIN
36
public.visitor_apartments va ON v.id = va.visitor_id
37
WHERE
38
va.visitor_apartment_id = p_apartment_id
39
AND v.is_deleted = FALSE
40
AND (vt.is_deleted = FALSE OR vt.is_deleted IS NULL);
41
END;
42
$function$
|
|||||
| Function | get_service_provider_companies | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_service_provider_companies(p_category_id integer DEFAULT NULL::integer, p_subtype_id integer DEFAULT NULL::integer)
2
RETURNS TABLE(id integer, name text, description text)
3
LANGUAGE sql
4
AS $function$
5
SELECT spc.id, spc.name, spc.description
6
FROM public.service_provider_companies spc
7
WHERE
8
(p_category_id IS NULL OR spc.category_id = p_category_id)
9
AND (p_subtype_id IS NULL OR spc.subtype_id = p_subtype_id);
10
$function$
|
|||||
| Function | create_visitor | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_visitor(p_first_name character varying, p_last_name character varying, p_contact_number character varying, p_visitor_type_id integer, p_email character varying, p_visiting_from character varying, p_vehicle_number character varying, p_identity_type_id integer, p_identity_number character varying, p_apartment_id uuid, p_created_by uuid, p_delivery_company_id integer, p_image_url character varying)
2
RETURNS bigint
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id BIGINT;
7
BEGIN
8
-- Insert into the visitors table
9
INSERT INTO public.visitors (
10
first_name,
11
last_name,
12
created_on_utc,
13
created_by,
14
is_active,
15
visitor_type_id,
16
image_url
17
)
18
VALUES (
19
p_first_name,
20
p_last_name,
21
current_timestamp,
22
p_created_by,
23
TRUE,
24
p_visitor_type_id,
25
p_image_url
26
)
27
RETURNING id INTO v_visitor_id; -- Store the newly created visitor's ID
28
29
-- Insert into visitor_apartments table
30
INSERT INTO public.visitor_apartments (
31
visitor_id,
32
visitor_apartment_id,
33
first_seen,
34
is_blocked
35
)
36
VALUES (
37
v_visitor_id,
38
p_apartment_id,
39
current_timestamp, -- Set first_seen as the current timestamp
40
FALSE -- Default is_blocked to FALSE
41
);
42
43
-- Insert into visitor_details table (if visiting_from or vehicle_number is provided)
44
IF p_visiting_from IS NOT NULL AND TRIM(p_visiting_from) <> '' OR p_vehicle_number IS NOT NULL AND TRIM(p_vehicle_number) <> '' THEN
45
INSERT INTO public.visitor_details (
46
visitor_id,
47
visiting_from,
48
vehicle_number,
49
created_by,
50
created_on_utc
51
) VALUES (
52
v_visitor_id,
53
p_visiting_from,
54
p_vehicle_number,
55
p_created_by,
56
current_timestamp
57
);
58
END IF;
59
60
-- Insert into visitor_phones table
61
INSERT INTO public.visitor_phones (
62
visitor_id,
63
phone_number,
64
is_active,
65
valid_from
66
) VALUES (
67
v_visitor_id,
68
p_contact_number,
69
TRUE,
70
current_timestamp
71
);
72
73
-- Insert into visitor_emails table (if email is provided and not an empty string)
74
IF p_email IS NOT NULL AND TRIM(p_email) <> '' THEN
75
INSERT INTO public.visitor_emails (
76
visitor_id,
77
email,
78
is_active,
79
valid_from
80
) VALUES (
81
v_visitor_id,
82
p_email,
83
TRUE,
84
current_timestamp
85
);
86
END IF;
87
88
-- Insert into visitor_identities table (if identity number is provided and not an empty string)
89
IF p_identity_number IS NOT NULL AND TRIM(p_identity_number) <> '' THEN
90
INSERT INTO public.visitor_identities (
91
visitor_id,
92
identity_type_id,
93
identity_number,
94
is_active,
95
valid_from,
96
created_by,
97
created_on_utc
98
) VALUES (
99
v_visitor_id,
100
p_identity_type_id,
101
p_identity_number,
102
TRUE,
103
current_timestamp,
104
p_created_by,
105
current_timestamp
106
);
107
END IF;
108
109
-- Insert into visitor_delivery_company table (if delivery_company_id is provided and not NULL)
110
IF p_delivery_company_id IS NOT NULL AND p_delivery_company_id > 0 THEN
111
INSERT INTO public.visitor_delivery_company (
112
visitor_id,
113
delivery_company_id,
114
is_active,
115
valid_from
116
) VALUES (
117
v_visitor_id,
118
p_delivery_company_id,
119
TRUE,
120
current_timestamp
121
);
122
END IF;
123
124
-- Return the newly created visitor ID
125
RETURN v_visitor_id;
126
END;
127
$function$
|
|||||
| Function | create_visitor_with_pending_approval | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_visitor_with_pending_approval(p_apartment_id uuid, p_first_name text, p_last_name text, p_image_url text, p_contact_number text, p_visitor_type_id integer, p_unit_ids integer[], p_created_by uuid)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id BIGINT;
7
v_visitor_log_id BIGINT;
8
v_unit_id INTEGER;
9
v_pending_status CONSTANT INTEGER := 1; -- Pending
10
BEGIN
11
/*
12
* 1. Find visitor by active phone number
13
*/
14
SELECT vp.visitor_id
15
INTO v_visitor_id
16
FROM visitor_phones vp
17
JOIN visitors v ON v.id = vp.visitor_id
18
WHERE vp.phone_number = p_contact_number
19
AND vp.is_active = TRUE
20
AND v.is_deleted = FALSE
21
LIMIT 1;
22
23
/*
24
* 2. Create visitor if not exists
25
*/
26
IF NOT FOUND THEN
27
INSERT INTO visitors (
28
first_name,
29
last_name,
30
image_url,
31
visitor_type_id,
32
created_on_utc,
33
created_by
34
)
35
VALUES (
36
p_first_name,
37
p_last_name,
38
p_image_url,
39
p_visitor_type_id,
40
NOW(),
41
p_created_by
42
)
43
RETURNING id INTO v_visitor_id;
44
45
INSERT INTO visitor_phones (
46
visitor_id,
47
phone_number,
48
is_active,
49
valid_from
50
)
51
VALUES (
52
v_visitor_id,
53
p_contact_number,
54
TRUE,
55
NOW()
56
);
57
END IF;
58
59
/*
60
* 3. Ensure apartment association exists
61
*/
62
IF NOT EXISTS (
63
SELECT 1
64
FROM visitor_apartments
65
WHERE visitor_id = v_visitor_id
66
AND visitor_apartment_id = p_apartment_id
67
) THEN
68
INSERT INTO visitor_apartments (
69
visitor_id,
70
visitor_apartment_id,
71
first_seen
72
)
73
VALUES (
74
v_visitor_id,
75
p_apartment_id,
76
NOW()
77
);
78
END IF;
79
80
/*
81
* 4. Insert visitor log with PENDING status
82
*/
83
INSERT INTO visitor_logs (
84
visitor_id,
85
apartment_id,
86
visitor_type_id,
87
entry_time,
88
created_on_utc,
89
created_by,
90
visitor_status_id
91
)
92
VALUES (
93
v_visitor_id,
94
p_apartment_id,
95
p_visitor_type_id,
96
NULL, -- Entry happens later
97
NOW(),
98
p_created_by,
99
v_pending_status
100
)
101
RETURNING id INTO v_visitor_log_id;
102
103
/*
104
* 5. Insert multi-unit visits with PENDING status
105
*/
106
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
107
INSERT INTO multi_unit_visits (
108
visitor_log_id,
109
unit_id,
110
visitor_statuses,
111
created_on_utc,
112
is_deleted,
113
created_by
114
)
115
VALUES (
116
v_visitor_log_id,
117
v_unit_id,
118
v_pending_status,
119
NOW(),
120
FALSE,
121
p_created_by
122
);
123
END LOOP;
124
125
RETURN v_visitor_log_id;
126
END;
127
$function$
|
|||||
| Function | get_all_tickets | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_tickets(p_apartment_id uuid)
2
RETURNS TABLE(id uuid, unit_id integer, unit text, title text, description text, ticket_category_id integer, ticket_category text, ticket_priority_id integer, ticket_priority text, ticket_status_id integer, sort_order integer, ticket_status text, ticket_assigned_to integer, ticket_assigned text, ticket_assignee_phone text, is_on_hold boolean, is_re_opened boolean, ticket_number text, ticket_for_id integer, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
SELECT
7
t.id,
8
t.unit_id,
9
u.name AS unit,
10
t.title,
11
t.description,
12
t.ticket_category_id,
13
tc.name AS ticket_category,
14
t.ticket_priority_id,
15
tp.name AS ticket_priority,
16
t.ticket_status_id,
17
t.sort_order,
18
ts.name AS ticket_status,
19
t.ticket_assigned_to,
20
COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''),'Not Assigned' ) AS ticket_assigned,
21
vp.phone_number AS ticket_assignee_phone,
22
t.is_on_hold,
23
t.is_re_opened,
24
t.ticket_number,
25
t.ticket_for_id,
26
t.created_by,
27
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS created_by_name,
28
t.created_on_utc,
29
t.modified_by,
30
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS modified_by_name,
31
t.modified_on_utc
32
FROM tickets t
33
JOIN units u
34
ON u.id = t.unit_id
35
AND u.is_deleted = false
36
JOIN ticket_categories tc
37
ON tc.id = t.ticket_category_id
38
AND tc.is_deleted = false
39
JOIN ticket_priorities tp
40
ON tp.id = t.ticket_priority_id
41
AND tp.is_deleted = false
42
JOIN ticket_statuses ts
43
ON ts.id = t.ticket_status_id
44
AND ts.is_deleted = false
45
LEFT JOIN service_providers sp
46
ON sp.id = t.ticket_assigned_to
47
AND sp.is_deleted = false
48
LEFT JOIN visitors v
49
ON v.id = sp.visitor_id
50
AND v.is_deleted = false
51
LEFT JOIN visitor_phones vp
52
ON vp.visitor_id = v.id
53
AND vp.is_active = TRUE
54
LEFT JOIN users uc
55
ON uc.id = t.created_by
56
LEFT JOIN users um
57
ON um.id = t.modified_by
58
59
WHERE
60
t.apartment_id = p_apartment_id
61
AND t.is_deleted = false
62
ORDER BY
63
t.ticket_status_id,
64
t.sort_order,
65
t.created_on_utc DESC;
66
$function$
|
|||||
| Function | get_all_tickets | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_tickets(p_apartment_id uuid, p_from_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_to_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_service_provider_id integer DEFAULT NULL::integer, p_is_active boolean DEFAULT false)
2
RETURNS TABLE(id uuid, unit_id integer, unit text, title text, description text, ticket_category_id integer, ticket_category text, ticket_priority_id integer, ticket_priority text, ticket_status_id integer, sort_order integer, ticket_status text, ticket_assigned_to integer, ticket_assigned text, ticket_assignee_phone text, is_on_hold boolean, is_re_opened boolean, ticket_number text, ticket_for_id integer, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
SELECT
7
t.id,
8
t.unit_id,
9
u.name AS unit,
10
t.title,
11
t.description,
12
t.ticket_category_id,
13
tc.name AS ticket_category,
14
t.ticket_priority_id,
15
tp.name AS ticket_priority,
16
t.ticket_status_id,
17
t.sort_order,
18
ts.name AS ticket_status,
19
t.ticket_assigned_to,
20
--COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''),'Not Assigned') AS ticket_assigned,
21
--NULLIF(concat_ws(' ', v.first_name, v.last_name), '') AS ticket_assigned,
22
COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''), NULL) AS ticket_assigned,
23
vp.phone_number AS ticket_assignee_phone,
24
t.is_on_hold,
25
t.is_re_opened,
26
t.ticket_number,
27
t.ticket_for_id,
28
t.created_by,
29
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS created_by_name,
30
t.created_on_utc,
31
t.modified_by,
32
COALESCE(NULLIF(TRIM(um.first_name || ' ' || um.last_name), ''), NULL) AS modified_by_name,
33
t.modified_on_utc
34
FROM tickets t
35
JOIN units u
36
ON u.id = t.unit_id
37
AND u.is_deleted = false
38
JOIN ticket_categories tc
39
ON tc.id = t.ticket_category_id
40
AND tc.is_deleted = false
41
JOIN ticket_priorities tp
42
ON tp.id = t.ticket_priority_id
43
AND tp.is_deleted = false
44
JOIN ticket_statuses ts
45
ON ts.id = t.ticket_status_id
46
AND ts.is_deleted = false
47
LEFT JOIN service_providers sp
48
ON sp.id = t.ticket_assigned_to
49
AND sp.is_deleted = false
50
LEFT JOIN visitors v
51
ON v.id = sp.visitor_id
52
AND v.is_deleted = false
53
LEFT JOIN visitor_phones vp
54
ON vp.visitor_id = v.id
55
AND vp.is_active = true
56
LEFT JOIN users uc
57
ON uc.id = t.created_by
58
LEFT JOIN users um
59
ON um.id = t.modified_by
60
WHERE
61
t.apartment_id = p_apartment_id
62
AND t.is_deleted = false
63
64
-- Date filters
65
AND (p_from_datetime IS NULL OR t.created_on_utc >= p_from_datetime)
66
AND (p_to_datetime IS NULL OR t.created_on_utc <= p_to_datetime)
67
68
-- Service provider filter
69
AND (p_service_provider_id IS NULL OR t.ticket_assigned_to = p_service_provider_id)
70
71
-- Active / All status filter
72
AND (
73
p_is_active = false
74
OR ts.name IN (
75
'In Queue',
76
'Assigned',
77
'In Progress',
78
'Reopened'
79
)
80
)
81
ORDER BY
82
t.ticket_status_id,
83
t.sort_order,
84
t.created_on_utc DESC;
85
$function$
|
|||||
| Function | get_defaulter_contact | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_defaulter_contact(p_customer_id uuid)
2
RETURNS text
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_contact_number text;
7
BEGIN
8
SELECT
9
r.contact_number INTO v_contact_number
10
FROM
11
public.units u
12
INNER JOIN
13
public.resident_units ru ON u.id = ru.unit_id
14
INNER JOIN
15
public.residents r ON ru.resident_id = r.id
16
WHERE
17
u.customer_id = p_customer_id
18
AND u.is_deleted = false
19
AND ru.is_deleted = false
20
AND r.is_deleted = false
21
AND r.contact_number IS NOT NULL
22
LIMIT 1;
23
24
RETURN v_contact_number;
25
END;
26
$function$
|
|||||
| Function | get_service_provider_subcategories | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_service_provider_subcategories(p_category_id integer)
2
RETURNS TABLE(id integer, name text, description text)
3
LANGUAGE sql
4
AS $function$
5
SELECT sps.id, sps.name, sps.description
6
FROM public.service_provider_company_subtypes sps
7
WHERE sps.category_id = p_category_id;
8
$function$
|
|||||
| Function | get_all_escalated_tickets | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.get_all_escalated_tickets(p_apartment_id uuid, p_from_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_to_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_service_provider_id integer DEFAULT NULL::integer, p_is_active boolean DEFAULT false)
2
RETURNS TABLE(id uuid, unit_id integer, unit text, title text, description text, ticket_category_id integer, ticket_category text, ticket_priority_id integer, ticket_priority text, ticket_status_id integer, sort_order integer, ticket_status text, ticket_assigned_to integer, ticket_assigned text, ticket_assignee_phone text, is_on_hold boolean, is_re_opened boolean, ticket_number text, ticket_for_id integer, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
3
LANGUAGE sql
4
STABLE
5
AS $function$
6
WITH latest_ticket_log AS (
7
SELECT DISTINCT ON (tl.ticket_id)
8
tl.ticket_id,
9
tl.is_escalated
10
FROM ticket_logs tl
11
ORDER BY tl.ticket_id, tl.created_on_utc DESC
12
)
13
SELECT
14
t.id,
15
t.unit_id,
16
u.name AS unit,
17
t.title,
18
t.description,
19
t.ticket_category_id,
20
tc.name AS ticket_category,
21
t.ticket_priority_id,
22
tp.name AS ticket_priority,
23
t.ticket_status_id,
24
t.sort_order,
25
ts.name AS ticket_status,
26
t.ticket_assigned_to,
27
COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''), NULL) AS ticket_assigned,
28
vp.phone_number AS ticket_assignee_phone,
29
t.is_on_hold,
30
t.is_re_opened,
31
t.ticket_number,
32
t.ticket_for_id,
33
t.created_by,
34
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS created_by_name,
35
t.created_on_utc,
36
t.modified_by,
37
COALESCE(NULLIF(TRIM(um.first_name || ' ' || um.last_name), ''), NULL) AS modified_by_name,
38
t.modified_on_utc
39
FROM tickets t
40
JOIN latest_ticket_log ltl
41
ON ltl.ticket_id = t.id
42
AND ltl.is_escalated = true
43
JOIN units u
44
ON u.id = t.unit_id
45
AND u.is_deleted = false
46
JOIN ticket_categories tc
47
ON tc.id = t.ticket_category_id
48
AND tc.is_deleted = false
49
JOIN ticket_priorities tp
50
ON tp.id = t.ticket_priority_id
51
AND tp.is_deleted = false
52
JOIN ticket_statuses ts
53
ON ts.id = t.ticket_status_id
54
AND ts.is_deleted = false
55
LEFT JOIN service_providers sp
56
ON sp.id = t.ticket_assigned_to
57
AND sp.is_deleted = false
58
LEFT JOIN visitors v
59
ON v.id = sp.visitor_id
60
AND v.is_deleted = false
61
LEFT JOIN visitor_phones vp
62
ON vp.visitor_id = v.id
63
AND vp.is_active = true
64
LEFT JOIN users uc
65
ON uc.id = t.created_by
66
LEFT JOIN users um
67
ON um.id = t.modified_by
68
WHERE
69
t.apartment_id = p_apartment_id
70
AND t.is_deleted = false
71
72
-- Date filters
73
AND (p_from_datetime IS NULL OR t.created_on_utc >= p_from_datetime)
74
AND (p_to_datetime IS NULL OR t.created_on_utc <= p_to_datetime)
75
76
-- Service provider filter
77
AND (p_service_provider_id IS NULL OR t.ticket_assigned_to = p_service_provider_id)
78
79
-- Active / All status filter
80
AND (
81
p_is_active = false
82
OR ts.name IN (
83
'In Queue',
84
'Assigned',
85
'In Progress',
86
'Reopened'
87
)
88
)
89
ORDER BY
90
t.ticket_status_id,
91
t.sort_order,
92
t.created_on_utc DESC;
93
$function$
|
|||||
| Function | visitor_approval_fcm_tokens_by_visitor_log_id | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.visitor_approval_fcm_tokens_by_visitor_log_id(p_visitorlogid bigint)
2
RETURNS TABLE(id integer, user_ids uuid[], visitor_id integer, first_name text, last_name text, fcm_tokens text[], device_ids text[])
3
LANGUAGE sql
4
AS $function$
5
SELECT
6
ROW_NUMBER() OVER ()::INT AS id,
7
ARRAY_AGG(DISTINCT r.user_id) AS user_ids,
8
v.id AS visitor_id,
9
v.first_name,
10
v.last_name,
11
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.fcm_token), NULL) AS fcm_tokens,
12
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.device_id), NULL) AS device_ids
13
FROM multi_unit_visits muv
14
INNER JOIN resident_units ru ON muv.unit_id = ru.unit_id
15
INNER JOIN residents r ON ru.resident_id = r.id
16
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
17
INNER JOIN visitors v ON v.id = vl.visitor_id
18
LEFT JOIN user_fcm_tokens uft ON uft.user_id = r.user_id
19
WHERE muv.visitor_log_id = p_visitorlogid
20
AND muv.is_deleted = FALSE
21
AND ru.is_deleted = FALSE
22
AND r.is_deleted = FALSE
23
GROUP BY v.id, v.first_name, v.last_name;
24
$function$
|
|||||
| Function | create_visitor_with_pending_approval1 | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_visitor_with_pending_approval1(p_apartment_id uuid, p_first_name text, p_last_name text, p_image_url text, p_contact_number text, p_visitor_type_id integer, p_unit_ids integer[], p_created_by uuid, p_sp_category_id integer DEFAULT NULL::integer, p_sp_subtype_id integer DEFAULT NULL::integer, p_sp_company_id integer DEFAULT NULL::integer)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id BIGINT;
7
v_visitor_log_id BIGINT;
8
v_unit_id INTEGER;
9
v_pending_status CONSTANT INTEGER := 1;
10
BEGIN
11
-- 1. Find visitor by phone
12
SELECT vp.visitor_id
13
INTO v_visitor_id
14
FROM visitor_phones vp
15
JOIN visitors v ON v.id = vp.visitor_id
16
WHERE vp.phone_number = p_contact_number
17
AND vp.is_active = TRUE
18
AND v.is_deleted = FALSE
19
LIMIT 1;
20
21
-- 2. Create visitor if not exists
22
IF NOT FOUND THEN
23
INSERT INTO visitors (
24
first_name,
25
last_name,
26
image_url,
27
visitor_type_id,
28
created_on_utc,
29
created_by
30
)
31
VALUES (
32
p_first_name,
33
p_last_name,
34
p_image_url,
35
p_visitor_type_id,
36
NOW(),
37
p_created_by
38
)
39
RETURNING id INTO v_visitor_id;
40
41
INSERT INTO visitor_phones (
42
visitor_id,
43
phone_number,
44
is_active,
45
valid_from
46
)
47
VALUES (
48
v_visitor_id,
49
p_contact_number,
50
TRUE,
51
NOW()
52
);
53
END IF;
54
55
-- 3. Apartment mapping
56
IF NOT EXISTS (
57
SELECT 1 FROM visitor_apartments
58
WHERE visitor_id = v_visitor_id
59
AND visitor_apartment_id = p_apartment_id
60
) THEN
61
INSERT INTO visitor_apartments (
62
visitor_id,
63
visitor_apartment_id,
64
first_seen
65
)
66
VALUES (
67
v_visitor_id,
68
p_apartment_id,
69
NOW()
70
);
71
END IF;
72
73
-- 4. Visitor log
74
INSERT INTO visitor_logs (
75
visitor_id,
76
apartment_id,
77
visitor_type_id,
78
entry_time,
79
created_on_utc,
80
created_by,
81
visitor_status_id
82
)
83
VALUES (
84
v_visitor_id,
85
p_apartment_id,
86
p_visitor_type_id,
87
NULL,
88
NOW(),
89
p_created_by,
90
v_pending_status
91
)
92
RETURNING id INTO v_visitor_log_id;
93
94
-- 5. Multi-unit visits
95
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
96
INSERT INTO multi_unit_visits (
97
visitor_log_id,
98
unit_id,
99
visitor_statuses,
100
created_on_utc,
101
is_deleted,
102
created_by
103
)
104
VALUES (
105
v_visitor_log_id,
106
v_unit_id,
107
v_pending_status,
108
NOW(),
109
FALSE,
110
p_created_by
111
);
112
END LOOP;
113
114
-- 6. OPTIONAL service provider company info
115
IF p_sp_company_id IS NOT NULL THEN
116
INSERT INTO visitor_sp_company_visits (
117
visitor_log_id,
118
sp_company_category_id,
119
sp_company_subtype_id,
120
sp_company_id
121
)
122
VALUES (
123
v_visitor_log_id,
124
p_sp_category_id,
125
p_sp_subtype_id,
126
p_sp_company_id
127
);
128
END IF;
129
130
RETURN v_visitor_log_id;
131
END;
132
$function$
|
|||||
| Function | create_visitor_with_pending_approval | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE FUNCTION public.create_visitor_with_pending_approval(p_apartment_id uuid, p_first_name text, p_last_name text, p_image_url text, p_contact_number text, p_visitor_type_id integer, p_unit_ids integer[], p_created_by uuid, p_sp_category_id integer DEFAULT NULL::integer, p_sp_subtype_id integer DEFAULT NULL::integer, p_sp_company_id integer DEFAULT NULL::integer)
2
RETURNS integer
3
LANGUAGE plpgsql
4
AS $function$
5
DECLARE
6
v_visitor_id BIGINT;
7
v_visitor_log_id BIGINT;
8
v_unit_id INTEGER;
9
v_pending_status CONSTANT INTEGER := 1;
10
BEGIN
11
-- 1. Find visitor by phone
12
SELECT vp.visitor_id
13
INTO v_visitor_id
14
FROM visitor_phones vp
15
JOIN visitors v ON v.id = vp.visitor_id
16
WHERE vp.phone_number = p_contact_number
17
AND vp.is_active = TRUE
18
AND v.is_deleted = FALSE
19
LIMIT 1;
20
21
-- 2. Create visitor if not exists
22
IF NOT FOUND THEN
23
INSERT INTO visitors (
24
first_name,
25
last_name,
26
image_url,
27
visitor_type_id,
28
created_on_utc,
29
created_by
30
)
31
VALUES (
32
p_first_name,
33
p_last_name,
34
p_image_url,
35
p_visitor_type_id,
36
NOW(),
37
p_created_by
38
)
39
RETURNING id INTO v_visitor_id;
40
41
INSERT INTO visitor_phones (
42
visitor_id,
43
phone_number,
44
is_active,
45
valid_from
46
)
47
VALUES (
48
v_visitor_id,
49
p_contact_number,
50
TRUE,
51
NOW()
52
);
53
END IF;
54
55
-- 3. Apartment mapping
56
IF NOT EXISTS (
57
SELECT 1 FROM visitor_apartments
58
WHERE visitor_id = v_visitor_id
59
AND visitor_apartment_id = p_apartment_id
60
) THEN
61
INSERT INTO visitor_apartments (
62
visitor_id,
63
visitor_apartment_id,
64
first_seen
65
)
66
VALUES (
67
v_visitor_id,
68
p_apartment_id,
69
NOW()
70
);
71
END IF;
72
73
-- 4. Visitor log
74
INSERT INTO visitor_logs (
75
visitor_id,
76
apartment_id,
77
visitor_type_id,
78
entry_time,
79
created_on_utc,
80
created_by,
81
visitor_status_id
82
)
83
VALUES (
84
v_visitor_id,
85
p_apartment_id,
86
p_visitor_type_id,
87
NULL,
88
NOW(),
89
p_created_by,
90
v_pending_status
91
)
92
RETURNING id INTO v_visitor_log_id;
93
94
-- 5. Multi-unit visits
95
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
96
INSERT INTO multi_unit_visits (
97
visitor_log_id,
98
unit_id,
99
visitor_statuses,
100
created_on_utc,
101
is_deleted,
102
created_by
103
)
104
VALUES (
105
v_visitor_log_id,
106
v_unit_id,
107
v_pending_status,
108
NOW(),
109
FALSE,
110
p_created_by
111
);
112
END LOOP;
113
114
-- 6. OPTIONAL service provider company info
115
IF p_sp_company_id IS NOT NULL THEN
116
INSERT INTO visitor_sp_company_visits (
117
visitor_log_id,
118
sp_company_category_id,
119
sp_company_subtype_id,
120
sp_company_id
121
)
122
VALUES (
123
v_visitor_log_id,
124
p_sp_category_id,
125
p_sp_subtype_id,
126
p_sp_company_id
127
);
128
END IF;
129
130
RETURN v_visitor_log_id;
131
END;
132
$function$
|
|||||
| Procedure | purge_community_organization_data | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.purge_community_organization_data(IN p_organization_ids uuid[] DEFAULT NULL::uuid[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
-- Retention policy (used only for org selection window)
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[] := '{}'; -- company ids under the orgs
12
v_apartments uuid[] := '{}'; -- apartment-scope ids (companies + apartments under orgs)
13
14
-- Events
15
v_event_ids uuid[] := '{}';
16
v_event_occurrence_ids int[] := '{}';
17
18
-- Tickets
19
v_ticket_ids uuid[] := '{}';
20
21
-- Service providers and related
22
v_service_provider_ids int[] := '{}';
23
24
-- Units and related
25
v_unit_ids int[] := '{}';
26
27
-- Residents and related
28
v_resident_ids int[] := '{}';
29
30
-- Visitors and related
31
v_visitor_ids int[] := '{}';
32
v_visitor_log_ids int[] := '{}';
33
34
BEGIN
35
START TRANSACTION;
36
37
--------------------------------------------------------------------
38
-- 1) Resolve target organizations and containers (companies/apartments)
39
--------------------------------------------------------------------
40
IF p_organization_ids IS NULL OR array_length(p_organization_ids, 1) IS NULL THEN
41
SELECT COALESCE(array_agg(id), '{}')
42
INTO v_orgs
43
FROM organizations
44
WHERE created_on_utc > '2025-05-23'
45
AND created_on_utc < (NOW() - interval '24 hours');
46
ELSE
47
v_orgs := p_organization_ids;
48
END IF;
49
50
IF v_orgs IS NULL OR array_length(v_orgs, 1) IS NULL THEN
51
RAISE NOTICE 'No organizations found for community cleanup.';
52
COMMIT;
53
RETURN;
54
END IF;
55
56
-- Companies by organization (community uses company_id as scope for many tables)
57
SELECT COALESCE(array_agg(id), '{}')
58
INTO v_companies
59
FROM companies
60
WHERE organization_id = ANY(v_orgs);
61
62
IF v_companies IS NULL OR array_length(v_companies, 1) IS NULL THEN
63
RAISE NOTICE 'No companies resolved for community purge. Orgs: %', v_orgs;
64
COMMIT;
65
RETURN;
66
END IF;
67
68
-- Apartments by organization (some tables use apartment_id = companies.id; also apartments can have organization_id)
69
SELECT COALESCE(array_agg(DISTINCT x.id), '{}')
70
INTO v_apartments
71
FROM (
72
SELECT UNNEST(v_companies) AS id
73
UNION
74
SELECT a.id FROM apartments a WHERE a.organization_id = ANY(v_orgs)
75
) AS x;
76
77
RAISE NOTICE 'Community purge targets - Organizations: %; Companies: %; Apartments: %', v_orgs, v_companies, v_apartments;
78
79
--------------------------------------------------------------------
80
-- 2) Collect IDs needed for childβparent deletions
81
--------------------------------------------------------------------
82
-- Events and occurrences (company scoped)
83
SELECT COALESCE(array_agg(id), '{}')
84
INTO v_event_ids
85
FROM events
86
WHERE company_id = ANY(v_companies);
87
88
SELECT COALESCE(array_agg(id), '{}')
89
INTO v_event_occurrence_ids
90
FROM event_occurrences
91
WHERE event_id = ANY(v_event_ids);
92
93
-- Tickets (apartment scoped)
94
SELECT COALESCE(array_agg(id), '{}')
95
INTO v_ticket_ids
96
FROM tickets
97
WHERE apartment_id = ANY(v_apartments);
98
99
-- Service providers (apartment scoped)
100
SELECT COALESCE(array_agg(id), '{}')
101
INTO v_service_provider_ids
102
FROM service_providers
103
WHERE apartment_id = ANY(v_apartments);
104
105
-- Units (apartment scoped)
106
SELECT COALESCE(array_agg(id), '{}')
107
INTO v_unit_ids
108
FROM units
109
WHERE apartment_id = ANY(v_apartments);
110
111
-- Residents (apartment scoped)
112
SELECT COALESCE(array_agg(id), '{}')
113
INTO v_resident_ids
114
FROM residents
115
WHERE apartment_id = ANY(v_apartments);
116
117
-- Visitors and their logs (apartment scoped)
118
SELECT COALESCE(array_agg(id), '{}')
119
INTO v_visitor_ids
120
FROM visitors
121
WHERE apartment_id = ANY(v_apartments);
122
123
SELECT COALESCE(array_agg(id), '{}')
124
INTO v_visitor_log_ids
125
FROM visitor_logs
126
WHERE visitor_id = ANY(v_visitor_ids);
127
128
--------------------------------------------------------------------
129
-- 3) Purge in strict child β parent order (avoid FK violations)
130
--------------------------------------------------------------------
131
132
-- Ticket OTPs β Tickets β Ticket workflow
133
DELETE FROM ticket_service_provider_otps
134
WHERE ticket_id = ANY(v_ticket_ids);
135
136
DELETE FROM tickets
137
WHERE id = ANY(v_ticket_ids);
138
139
DELETE FROM ticket_workflow
140
WHERE apartment_id = ANY(v_apartments);
141
142
-- Meeting data tied to event occurrences: agenda, notes, participants, actions
143
DELETE FROM meeting_agenda_items
144
WHERE occurrence_id = ANY(v_event_occurrence_ids);
145
146
DELETE FROM meeting_notes
147
WHERE occurrence_id = ANY(v_event_occurrence_ids);
148
149
DELETE FROM meeting_participants
150
WHERE occurrence_id = ANY(v_event_occurrence_ids);
151
152
DELETE FROM meeting_action_items
153
WHERE occurrence_id = ANY(v_event_occurrence_ids);
154
155
-- Event participants referencing event_id
156
DELETE FROM event_participants
157
WHERE event_id = ANY(v_event_ids);
158
159
-- Event occurrences β events
160
DELETE FROM event_occurrences
161
WHERE id = ANY(v_event_occurrence_ids);
162
163
DELETE FROM events
164
WHERE id = ANY(v_event_ids);
165
166
-- Visitor related: vehicles β multi_unit_visits β guest_approvals β visitor_logs β contacts/approvals β visitors
167
DELETE FROM visitor_vehicles
168
WHERE visitor_log_id = ANY(v_visitor_log_ids);
169
170
DELETE FROM multi_unit_visits
171
WHERE visitor_log_id = ANY(v_visitor_log_ids)
172
OR unit_id = ANY(v_unit_ids);
173
174
DELETE FROM guest_approvals
175
WHERE visitor_log_id = ANY(v_visitor_log_ids);
176
177
-- Partitioned table; deleting from parent will route to partitions
178
DELETE FROM visitor_logs
179
WHERE id = ANY(v_visitor_log_ids);
180
181
DELETE FROM visitor_logs_old
182
WHERE visitor_id = ANY(v_visitor_ids);
183
184
DELETE FROM visitor_contacts
185
WHERE visitor_id = ANY(v_visitor_ids);
186
187
DELETE FROM visitor_approvals
188
WHERE visitor_id = ANY(v_visitor_ids);
189
190
DELETE FROM visitors
191
WHERE id = ANY(v_visitor_ids);
192
193
-- Service provider related: verifications β ticket categories β logs β unit_service_providers β pins β service_providers
194
DELETE FROM service_provider_verifications
195
WHERE "service_Provider_id" = ANY(v_service_provider_ids);
196
197
DELETE FROM service_provider_ticket_categories
198
WHERE service_provider_id = ANY(v_service_provider_ids);
199
200
-- Partitioned table; deleting from parent will route to partitions
201
DELETE FROM service_provider_logs
202
WHERE service_provider_id = ANY(v_service_provider_ids);
203
204
DELETE FROM unit_service_providers
205
WHERE service_provider_id = ANY(v_service_provider_ids)
206
OR unit_id = ANY(v_unit_ids);
207
208
DELETE FROM pins
209
WHERE service_provider_id = ANY(v_service_provider_ids)
210
OR visitor_id IN (SELECT id FROM visitors WHERE apartment_id = ANY(v_apartments));
211
212
DELETE FROM service_providers
213
WHERE id = ANY(v_service_provider_ids);
214
215
-- Unit related: vehicles β unit vehicle limits β resident_units β units
216
DELETE FROM vehicles
217
WHERE unit_id = ANY(v_unit_ids);
218
219
DELETE FROM unit_vehicle_limits
220
WHERE unit_id = ANY(v_unit_ids);
221
222
DELETE FROM resident_units
223
WHERE unit_id = ANY(v_unit_ids)
224
OR resident_id = ANY(v_resident_ids);
225
226
DELETE FROM units
227
WHERE id = ANY(v_unit_ids);
228
229
-- Residents: tokens β residents
230
DELETE FROM resident_tokens
231
WHERE resident_id = ANY(v_resident_ids);
232
233
DELETE FROM residents
234
WHERE id = ANY(v_resident_ids);
235
236
-- Gates, Floors, Buildings (all apartment scoped; ensure child tables removed first)
237
DELETE FROM gates
238
WHERE apartment_id = ANY(v_apartments);
239
240
DELETE FROM floors
241
WHERE apartment_id = ANY(v_apartments);
242
243
DELETE FROM buildings
244
WHERE apartment_id = ANY(v_apartments);
245
246
-- Committee members and portfolios (apartment scoped)
247
DELETE FROM committee_members
248
WHERE apartment_id = ANY(v_apartments);
249
250
DELETE FROM portfolios
251
WHERE apartment_id = ANY(v_apartments);
252
253
-- Water tanker deliveries (company scoped)
254
DELETE FROM water_tanker_deliveries
255
WHERE company_id = ANY(v_companies);
256
257
RAISE NOTICE 'Community purge complete for companies: % (orgs: %).', v_companies, v_orgs;
258
259
COMMIT;
260
261
EXCEPTION
262
WHEN OTHERS THEN
263
ROLLBACK;
264
RAISE NOTICE 'purge_community_organization_data failed: %', SQLERRM;
265
-- Optionally rethrow
266
-- RAISE;
267
END;
268
$procedure$
|
|||||
| Procedure | create_vehicle | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_vehicle(IN p_vehicle_number text, IN p_vehicle_type_id integer, IN p_unit_id integer, IN p_vehicle_rf_id text, IN p_vehicle_rf_id_secretcode text, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
INSERT INTO public.vehicles (
6
vehicle_number,
7
vehicle_type_id,
8
unit_id,
9
vehicle_rf_id,
10
vehicle_rf_id_secretcode,
11
created_on_utc,
12
is_deleted,
13
created_by
14
) VALUES (
15
p_vehicle_number,
16
p_vehicle_type_id,
17
p_unit_id,
18
p_vehicle_rf_id,
19
p_vehicle_rf_id_secretcode,
20
now() at time zone 'utc',
21
false,
22
p_created_by
23
);
24
END;
25
$procedure$
|
|||||
| Procedure | delete_residents_by_apartment | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.delete_residents_by_apartment(IN p_apartment_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
DELETE FROM public.resident_units
6
WHERE resident_id IN (
7
SELECT id FROM public.residents
8
WHERE apartment_id = p_apartment_id
9
);
10
11
DELETE FROM public.residents
12
WHERE apartment_id = p_apartment_id;
13
END;
14
$procedure$
|
|||||
| Procedure | hard_delete_apartment_data | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_apartment_data(IN p_apartment_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_company_id uuid;
6
BEGIN
7
-- Get the company_id from the apartment
8
SELECT company_id INTO v_company_id
9
FROM apartments
10
WHERE id = p_apartment_id;
11
12
-- === Delete apartment-linked data ===
13
14
DELETE FROM visitors
15
WHERE apartment_id = p_apartment_id;
16
17
DELETE FROM units
18
WHERE apartment_id = p_apartment_id;
19
20
DELETE FROM tickets
21
WHERE apartment_id = p_apartment_id;
22
23
DELETE FROM ticket_workflow
24
WHERE apartment_id = p_apartment_id;
25
26
DELETE FROM service_providers
27
WHERE apartment_id = p_apartment_id;
28
29
DELETE FROM residents
30
WHERE apartment_id = p_apartment_id;
31
32
DELETE FROM resident_requests
33
WHERE apartment_id = p_apartment_id;
34
35
DELETE FROM gates
36
WHERE apartment_id = p_apartment_id;
37
38
DELETE FROM floors
39
WHERE apartment_id = p_apartment_id;
40
41
DELETE FROM buildings
42
WHERE apartment_id = p_apartment_id;
43
44
-- === Delete company-linked data (derived from apartment) ===
45
46
IF v_company_id IS NOT NULL THEN
47
DELETE FROM events
48
WHERE company_id = v_company_id;
49
50
DELETE FROM users
51
WHERE company_id = v_company_id;
52
END IF;
53
54
-- Delete apartment record last
55
DELETE FROM apartments
56
WHERE id = p_apartment_id;
57
58
RAISE NOTICE 'Deleted data for apartment_id: % and company_id: %', p_apartment_id, v_company_id;
59
END;
60
$procedure$
|
|||||
| Procedure | hard_delete_org_community | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_org_community(IN p_organization_id uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_apartment_id uuid;
6
BEGIN
7
-- Step 1: Loop over all "apartments" (companies) under this org
8
FOR v_apartment_id IN
9
SELECT id FROM companies WHERE organization_id = p_organization_id
10
LOOP
11
-- Delete data linked to apartment (i.e., company)
12
DELETE FROM visitors WHERE apartment_id = v_apartment_id;
13
DELETE FROM units WHERE apartment_id = v_apartment_id;
14
DELETE FROM tickets WHERE apartment_id = v_apartment_id;
15
DELETE FROM ticket_workflow WHERE apartment_id = v_apartment_id;
16
DELETE FROM service_providers WHERE apartment_id = v_apartment_id;
17
DELETE FROM residents WHERE apartment_id = v_apartment_id;
18
DELETE FROM resident_requests WHERE apartment_id = v_apartment_id;
19
DELETE FROM gates WHERE apartment_id = v_apartment_id;
20
DELETE FROM floors WHERE apartment_id = v_apartment_id;
21
DELETE FROM buildings WHERE apartment_id = v_apartment_id;
22
23
-- Apartment entity (i.e., company-level data)
24
DELETE FROM apartments WHERE id = v_apartment_id;
25
DELETE FROM events WHERE company_id = v_apartment_id;
26
DELETE FROM users WHERE company_id = v_apartment_id;
27
DELETE FROM companies WHERE id = v_apartment_id;
28
END LOOP;
29
30
-- Step 2: Delete the organization
31
DELETE FROM organizations WHERE id = p_organization_id;
32
33
RAISE NOTICE 'Deleted community data for organization_id: %', p_organization_id;
34
END;
35
$procedure$
|
|||||
| Procedure | hard_delete_org_inventory | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_org_inventory(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 orders WHERE company_id = v_company_id;
9
DELETE FROM company_products WHERE company_id = v_company_id;
10
DELETE FROM company_categories WHERE company_id = v_company_id;
11
DELETE FROM product_tax_categories WHERE company_id = v_company_id;
12
DELETE FROM units WHERE company_id = v_company_id;
13
DELETE FROM users WHERE company_id = v_company_id;
14
DELETE FROM companies WHERE id = v_company_id;
15
END LOOP;
16
17
DELETE FROM organizations WHERE id = p_organization_id;
18
19
RAISE NOTICE 'Deleted inventory data for organization_id: %', p_organization_id;
20
END;
21
$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
9
-- Bill Details
10
DELETE FROM bill_details
11
WHERE bill_header_id IN (
12
SELECT id FROM bill_headers WHERE company_id = v_company_id
13
);
14
15
-- Bill Payment Details
16
DELETE FROM bill_payment_details
17
WHERE bill_payment_header_id IN (
18
SELECT id FROM bill_payment_headers WHERE company_id = v_company_id
19
);
20
21
-- Draft Bill Details
22
DELETE FROM draft_bill_details
23
WHERE bill_header_id IN (
24
SELECT id FROM draft_bill_headers WHERE company_id = v_company_id
25
);
26
27
-- Vendor Note Details
28
DELETE FROM vendor_note_details
29
WHERE vendor_note_header_id IN (
30
SELECT id FROM vendor_note_headers WHERE company_id = v_company_id
31
);
32
33
-- Gate Pass Details
34
DELETE FROM gate_pass_details
35
WHERE gate_pass_header_id IN (
36
SELECT id FROM gate_pass_headers WHERE company_id = v_company_id
37
);
38
39
-- Remaining headers and master data
40
DELETE FROM bill_workflow WHERE company_id = v_company_id;
41
DELETE FROM bill_payment_header_ids WHERE company_id = v_company_id;
42
DELETE FROM bill_payment_headers WHERE company_id = v_company_id;
43
44
DELETE FROM bill_header_ids WHERE company_id = v_company_id;
45
DELETE FROM bill_headers WHERE company_id = v_company_id;
46
47
DELETE FROM draft_bill_header_ids WHERE company_id = v_company_id;
48
DELETE FROM draft_bill_headers WHERE company_id = v_company_id;
49
50
DELETE FROM recurring_bill_schedules WHERE company_id = v_company_id;
51
52
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
53
54
DELETE FROM vendor_note_work_flows WHERE company_id = v_company_id;
55
DELETE FROM vendor_note_statuses WHERE company_id = v_company_id;
56
DELETE FROM vendor_note_headers WHERE company_id = v_company_id;
57
58
DELETE FROM vendor_categories WHERE company_id = v_company_id;
59
DELETE FROM vendors WHERE company_id = v_company_id;
60
61
DELETE FROM users WHERE company_id = v_company_id;
62
63
DELETE FROM companies WHERE id = v_company_id;
64
END LOOP;
65
66
DELETE FROM organizations WHERE id = p_organization_id;
67
68
RAISE NOTICE 'Deleted purchase data for organization_id: %', p_organization_id;
69
END;
70
$procedure$
|
|||||
| Procedure | hard_delete_org_sales | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.hard_delete_org_sales(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
9
-- ========== DELETE CUSTOMER NOTE DETAILS ==========
10
DELETE FROM customer_note_details
11
WHERE customer_note_header_id IN (
12
SELECT id FROM customer_note_headers WHERE company_id = v_company_id
13
);
14
15
DELETE FROM customer_note_headers WHERE company_id = v_company_id;
16
DELETE FROM customer_note_work_flows WHERE company_id = v_company_id;
17
18
-- ========== DELETE CUSTOMERS ==========
19
DELETE FROM customers WHERE company_id = v_company_id;
20
21
-- ========== DELETE GROUP INVOICES ==========
22
DELETE FROM group_invoice_details WHERE company_id = v_company_id;
23
DELETE FROM group_invoice_header_ids WHERE company_id = v_company_id;
24
DELETE FROM group_invoice_headers WHERE company_id = v_company_id;
25
DELETE FROM group_invoice_templates WHERE company_id = v_company_id;
26
27
-- ========== DELETE INVOICE ENTRIES ==========
28
DELETE FROM invoice_details
29
WHERE invoice_header_id IN (
30
SELECT id FROM invoice_headers WHERE company_id = v_company_id
31
);
32
DELETE FROM invoice_headers WHERE company_id = v_company_id;
33
DELETE FROM invoice_header_ids WHERE company_id = v_company_id;
34
35
DELETE FROM invoice_payment_details
36
WHERE invoice_payment_header_id IN (
37
SELECT id FROM invoice_payment_headers WHERE company_id = v_company_id
38
);
39
DELETE FROM invoice_payment_headers WHERE company_id = v_company_id;
40
DELETE FROM invoice_payment_header_ids WHERE company_id = v_company_id;
41
42
DELETE FROM invoice_voucher_ids WHERE company_id = v_company_id;
43
DELETE FROM invoice_workflow WHERE company_id = v_company_id;
44
45
-- ========== DELETE DRAFT INVOICES ==========
46
DELETE FROM draft_invoice_details
47
WHERE invoice_header_id IN (
48
SELECT id FROM draft_invoice_headers WHERE company_id = v_company_id
49
);
50
DELETE FROM draft_invoice_headers WHERE company_id = v_company_id;
51
DELETE FROM draft_invoice_header_ids WHERE company_id = v_company_id;
52
53
-- ========== DELETE GATE PASS ==========
54
DELETE FROM gate_pass_details
55
WHERE gate_pass_header_id IN (
56
SELECT id FROM gate_pass_headers WHERE company_id = v_company_id
57
);
58
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
59
60
-- ========== DELETE CONFIG & USERS ==========
61
DELETE FROM penalty_configs WHERE company_id = v_company_id;
62
DELETE FROM recurring_sales_schedules WHERE company_id = v_company_id;
63
DELETE FROM users WHERE company_id = v_company_id;
64
DELETE FROM companies WHERE id = v_company_id;
65
66
RAISE NOTICE 'Deleted sales data for company_id %', v_company_id;
67
END LOOP;
68
69
DELETE FROM organizations WHERE id = p_organization_id;
70
71
RAISE NOTICE 'Deleted sales data for organization_id %', p_organization_id;
72
END;
73
$procedure$
|
|||||
| Procedure | insert_visitor_vehicle | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.insert_visitor_vehicle(IN p_visitor_log_id integer, IN p_vehicle_number text DEFAULT NULL::text, IN p_vehicle_type text DEFAULT NULL::text)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_created_by UUID; -- Declare variable to store created_by ID
6
BEGIN
7
-- Fetch created_by from the visitor_log table based on the visitor_log_id
8
SELECT created_by INTO v_created_by
9
FROM public.visitor_logs
10
WHERE id = p_visitor_log_id;
11
12
-- If no created_by found (i.e., visitor_log_id does not exist), set default value (e.g., 0 or NULL)
13
IF v_created_by IS NULL THEN
14
RAISE NOTICE 'No created_by found for visitor_log_id: %, using default value', p_visitor_log_id;
15
v_created_by := 'dd4f94f2-0f79-4748-b94b-bf935e3944c7'; -- System Id
16
END IF;
17
18
-- Insert the data into the VisitorVehicles table
19
INSERT INTO public.visitor_vehicles (visitor_log_id, vehicle_number, vehicle_type, created_on_utc, created_by)
20
VALUES (p_visitor_log_id, p_vehicle_number, P_vehicle_type, NOW(), v_created_by);
21
22
-- Optionally, raise a notice confirming the insertion
23
RAISE NOTICE 'Inserted Vehicle: %, % for VisitorLogId: %, CreatedBy: %',
24
COALESCE(p_vehicle_number, 'NULL'),
25
COALESCE(P_vehicle_type, 'NULL'),
26
p_visitor_log_id,
27
v_created_by;
28
END;
29
$procedure$
|
|||||
| Procedure | upsert_meeting_action_items | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_meeting_action_items(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_action_items jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_occurrence_id INT;
6
v_item jsonb;
7
v_existing_ids INT[];
8
BEGIN
9
-- Step 1: Get or create event occurrence
10
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
11
12
-- Step 2: Get current action item IDs
13
SELECT array_agg(id)
14
INTO v_existing_ids
15
FROM meeting_action_items
16
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
17
18
-- Step 3: Loop through each item
19
FOR v_item IN SELECT * FROM jsonb_array_elements(p_action_items)
20
LOOP
21
IF (v_item->>'id')::int < 0 THEN
22
-- Insert new item (negative ID indicates new)
23
INSERT INTO meeting_action_items (
24
occurrence_id,
25
action_description,
26
assigned_to_user_id,
27
due_date,
28
status,
29
created_on_utc,
30
created_by,
31
is_deleted
32
)
33
VALUES (
34
v_occurrence_id,
35
v_item->>'action_description',
36
(v_item->>'assigned_to_user_id')::uuid,
37
NULLIF(v_item->>'due_date', '')::timestamp,
38
v_item->>'status',
39
now(),
40
p_user_uuid,
41
false
42
);
43
ELSE
44
-- Update existing action item (positive ID)
45
UPDATE meeting_action_items
46
SET
47
action_description = v_item->>'action_description',
48
assigned_to_user_id = (v_item->>'assigned_to_user_id')::uuid,
49
due_date = NULLIF(v_item->>'due_date', '')::timestamp,
50
status = v_item->>'status',
51
modified_on_utc = now(),
52
modified_by = p_user_uuid
53
WHERE id = (v_item->>'id')::int
54
AND occurrence_id = v_occurrence_id;
55
56
-- Mark as handled
57
v_existing_ids := array_remove(v_existing_ids, (v_item->>'id')::int);
58
END IF;
59
END LOOP;
60
61
-- Step 4: Soft delete action items that were removed
62
IF v_existing_ids IS NOT NULL THEN
63
UPDATE meeting_action_items
64
SET is_deleted = true,
65
deleted_on_utc = now(),
66
modified_by = p_user_uuid
67
WHERE id = ANY(v_existing_ids);
68
END IF;
69
END;
70
$procedure$
|
|||||
| Procedure | upsert_meeting_agenda_items | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_meeting_agenda_items(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_agenda_items jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_occurrence_id INT;
6
v_item jsonb;
7
v_existing_ids INT[];
8
BEGIN
9
-- Step 1: Get or create occurrence
10
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
11
12
-- Step 2: Track existing agenda item IDs for this occurrence
13
SELECT array_agg(id)
14
INTO v_existing_ids
15
FROM meeting_agenda_items
16
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
17
18
-- Step 3: Loop through input items
19
FOR v_item IN SELECT * FROM jsonb_array_elements(p_agenda_items)
20
LOOP
21
IF (v_item->>'id')::int < 0 THEN
22
-- Insert new item (negative ID indicates new)
23
INSERT INTO meeting_agenda_items (
24
occurrence_id, item_text, order_no, created_on_utc, created_by, is_deleted
25
)
26
VALUES (
27
v_occurrence_id,
28
v_item->>'item_text',
29
(v_item->>'order_no')::int,
30
now(),
31
p_user_uuid,
32
false
33
);
34
ELSE
35
-- Update existing item (positive ID)
36
UPDATE meeting_agenda_items
37
SET item_text = v_item->>'item_text',
38
order_no = (v_item->>'order_no')::int,
39
modified_on_utc = now(),
40
modified_by = p_user_uuid
41
WHERE id = (v_item->>'id')::int AND occurrence_id = v_occurrence_id;
42
43
-- Remove updated ID from existing ID list (so itβs not soft-deleted later)
44
v_existing_ids := array_remove(v_existing_ids, (v_item->>'id')::int);
45
END IF;
46
END LOOP;
47
48
-- Step 4: Soft delete missing items (not sent in input)
49
UPDATE meeting_agenda_items
50
SET is_deleted = true,
51
deleted_on_utc = now(),
52
modified_by = p_user_uuid
53
WHERE id = ANY(v_existing_ids);
54
55
END;
56
$procedure$
|
|||||
| Procedure | upsert_meeting_participants | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_meeting_participants(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_participant_user_ids jsonb)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_occurrence_id INT;
6
v_user_id UUID;
7
v_existing_ids UUID[];
8
BEGIN
9
-- Get or create occurrence
10
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
11
RAISE NOTICE 'Occurrence ID: %', v_occurrence_id;
12
13
-- Existing participants
14
SELECT array_agg(user_id) INTO v_existing_ids
15
FROM meeting_participants
16
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
17
RAISE NOTICE 'Existing participants count: %', COALESCE(array_length(v_existing_ids, 1), 0);
18
19
-- Loop input participants
20
FOR v_user_id IN
21
SELECT (elem->>'user_id')::uuid
22
FROM jsonb_array_elements(p_participant_user_ids) AS elem
23
LOOP
24
RAISE NOTICE 'Processing user: %', v_user_id;
25
26
IF v_existing_ids IS NULL OR NOT v_user_id = ANY(v_existing_ids) THEN
27
INSERT INTO meeting_participants (
28
occurrence_id, user_id, created_on_utc, created_by, is_deleted
29
)
30
VALUES (v_occurrence_id, v_user_id, now(), p_user_uuid, false);
31
RAISE NOTICE 'Inserted user %', v_user_id;
32
ELSE
33
UPDATE meeting_participants
34
SET modified_on_utc = now(),
35
modified_by = p_user_uuid
36
WHERE occurrence_id = v_occurrence_id
37
AND user_id = v_user_id;
38
RAISE NOTICE 'Updated user %', v_user_id;
39
40
v_existing_ids := array_remove(v_existing_ids, v_user_id);
41
END IF;
42
END LOOP;
43
44
-- Soft delete removed participants
45
IF v_existing_ids IS NOT NULL THEN
46
UPDATE meeting_participants
47
SET is_deleted = true,
48
deleted_on_utc = now(),
49
modified_by = p_user_uuid
50
WHERE occurrence_id = v_occurrence_id
51
AND user_id = ANY(v_existing_ids);
52
RAISE NOTICE 'Soft deleted users: %', v_existing_ids;
53
END IF;
54
55
END;
56
$procedure$
|
|||||
| Procedure | create_service_provider | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.create_service_provider(IN p_first_name character varying, IN p_last_name character varying, IN p_email character varying, IN p_visiting_from character varying, IN p_contact_number character varying, IN p_permanent_address_id uuid, IN p_present_address_id uuid, IN p_service_provider_type_id integer, IN p_service_provider_sub_type_id integer, IN p_vehicle_number character varying, IN p_identity_type_id integer, IN p_identity_number character varying, IN p_validity_date date, IN p_police_verification_status boolean, IN p_is_hireable boolean, IN p_is_visible boolean, IN p_is_frequent_visitor boolean, IN p_apartment_id uuid, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
pin_code character varying;
6
BEGIN
7
-- Generate the random pin code
8
pin_code := public.generate_random_pin();
9
10
INSERT INTO public.service_providers (
11
first_name,
12
last_name,
13
email,
14
visiting_from,
15
contact_number,
16
permanent_address_id,
17
present_address_id,
18
service_provider_type_id,
19
service_provider_sub_type_id,
20
vehicle_number,
21
identity_type_id,
22
identity_number,
23
validity_date,
24
policeverification_status,
25
is_hireable,
26
is_visible,
27
is_frequent_visitor,
28
apartment_id,
29
pin,
30
created_on_utc,
31
created_by
32
)
33
VALUES (
34
p_first_name,
35
p_last_name,
36
p_email,
37
p_visiting_from,
38
p_contact_number,
39
p_permanent_address_id,
40
p_present_address_id,
41
p_service_provider_type_id,
42
p_service_provider_sub_type_id,
43
p_vehicle_number,
44
p_identity_type_id,
45
p_identity_number,
46
p_validity_date,
47
p_police_verification_status,
48
p_is_hireable,
49
p_is_visible,
50
p_is_frequent_visitor,
51
p_apartment_id,
52
pin_code,
53
current_timestamp,
54
p_created_by
55
);
56
END;
57
$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; -- Variable to hold company IDs associated with the organization
6
BEGIN
7
-- Get 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 users associated with the company
12
DELETE FROM public.users
13
WHERE users.company_id = p_company_id;
14
15
-- Delete from companies
16
DELETE FROM public.companies
17
WHERE public.companies.id = p_company_id;
18
END LOOP;
19
20
-- Delete the organization itself
21
DELETE FROM public.organizations
22
WHERE public.organizations.id = p_organization_id;
23
24
-- Log the operation
25
RAISE NOTICE 'Organization with ID % and all related data have been hard deleted.', p_organization_id;
26
27
END;
28
$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_guids 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 character varying, IN p_email character varying, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_company_id uuid;
6
v_company_name text;
7
v_company_ids uuid[];
8
v_company_names text[];
9
i integer;
10
v_organization_exists boolean;
11
v_user_exists boolean;
12
v_company_exists boolean;
13
v_existing_user_id uuid;
14
BEGIN
15
-- Check if organization already exists
16
SELECT EXISTS (
17
SELECT 1 FROM public.organizations
18
WHERE id = p_id OR ( id = p_id AND name = p_name)
19
) INTO v_organization_exists;
20
21
IF v_organization_exists THEN
22
RAISE NOTICE 'Organization with ID % or Name % already exists. Skipping organization creation.', p_id, p_name;
23
ELSE
24
-- Insert organization if it doesn't exist
25
INSERT INTO public.organizations (
26
id, name, created_on_utc, created_by
27
) VALUES (
28
p_id, p_name, NOW(), p_created_by
29
);
30
RAISE NOTICE 'Initialized organization: % with ID: %', p_name, p_id;
31
END IF;
32
33
-- Parse company IDs and names
34
v_company_ids := string_to_array(p_company_guids, ',');
35
v_company_names := string_to_array(p_company_names, ',');
36
37
-- Initialize companies with duplicate checks
38
FOR i IN 1..array_length(v_company_ids, 1) LOOP
39
v_company_id := v_company_ids[i];
40
v_company_name := v_company_names[i];
41
42
-- Check if company already exists
43
SELECT EXISTS (
44
SELECT 1 FROM public.companies
45
WHERE id = v_company_id
46
) INTO v_company_exists;
47
48
IF NOT v_company_exists THEN
49
CALL public.initialize_company(
50
v_company_id,
51
p_id,
52
true, -- Indicates it's an apartment
53
v_company_name,
54
p_created_by
55
);
56
RAISE NOTICE 'Initialized company: % with ID: %', v_company_name, v_company_id;
57
ELSE
58
RAISE NOTICE 'Company % (ID: %) already exists for organization %. Skipping initialization.',
59
v_company_name, v_company_id, p_id;
60
END IF;
61
END LOOP;
62
63
-- Assign the first company ID from the array
64
v_company_id := v_company_ids[1];
65
66
-- Check if user already exists and get the existing ID if found
67
SELECT id INTO v_existing_user_id FROM public.users
68
WHERE id = p_user_id OR email = p_email
69
LIMIT 1;
70
71
v_user_exists := (v_existing_user_id IS NOT NULL);
72
73
IF NOT v_user_exists THEN
74
-- Create user if they don't exist
75
INSERT INTO public.users (
76
id, email, contact_number, first_name, last_name,
77
password_hash, created_on_utc, created_by, company_id, is_owner
78
) VALUES (
79
p_user_id, p_email, p_phone_number, p_user_first_name, p_user_last_name,
80
'', NOW(), p_created_by, v_company_id, false
81
);
82
RAISE NOTICE 'Created user % (%) for organization %', p_email, p_user_id, p_id;
83
ELSE
84
-- Update existing user's company association
85
UPDATE public.users
86
SET company_id = v_company_id
87
WHERE id = v_existing_user_id;
88
RAISE NOTICE 'updated user % (%) for organization %', p_email, p_user_id, p_id;
89
END IF;
90
RAISE NOTICE 'Organization initialization process completed for % (ID: %)', p_name, p_id;
91
END;
92
$procedure$
|
|||||
| Procedure | update_ticket_status | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_ticket_status(IN p_apartment_id uuid, IN p_ticket_status_id integer, IN p_ticket_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_ticket RECORD;
6
BEGIN
7
-- Process each ticket in the given list
8
FOR v_ticket IN
9
SELECT id, ticket_status_id
10
FROM public.tickets
11
WHERE id = ANY(p_ticket_ids)
12
LOOP
13
-- Validate that the status transition is allowed
14
IF EXISTS (
15
SELECT 1 FROM public.ticket_workflow
16
WHERE status = v_ticket.ticket_status_id
17
AND next_status = p_ticket_status_id
18
AND apartment_id = p_apartment_id
19
) THEN
20
-- Update the ticket status
21
UPDATE public.tickets
22
SET ticket_status_id = p_ticket_status_id,
23
modified_by = p_modified_by,
24
modified_on_utc = now()
25
WHERE id = v_ticket.id;
26
27
RAISE NOTICE 'Ticket % updated to status %', v_ticket.id, p_ticket_status_id;
28
ELSE
29
RAISE WARNING 'Invalid status transition for Ticket ID: %', v_ticket.id;
30
END IF;
31
END LOOP;
32
END;
33
$procedure$
|
|||||
| Procedure | upsert_meeting_note | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.upsert_meeting_note(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_note_text text)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_occurrence_id INT;
6
v_note_id INT;
7
BEGIN
8
-- Get or create occurrence
9
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
10
11
-- Check if note exists for occurrence
12
SELECT id INTO v_note_id
13
FROM meeting_notes
14
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
15
16
IF v_note_id IS NOT NULL THEN
17
-- Update existing note
18
UPDATE meeting_notes
19
SET note_text = p_note_text,
20
modified_on_utc = now(),
21
modified_by = p_user_uuid
22
WHERE id = v_note_id;
23
ELSE
24
-- Insert new note
25
INSERT INTO meeting_notes (
26
occurrence_id, note_text, created_on_utc, created_by, is_deleted
27
) VALUES (
28
v_occurrence_id, p_note_text, now(), p_user_uuid, false
29
);
30
END IF;
31
END;
32
$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_organization_id uuid, IN p_is_apartment boolean, IN p_name text, IN p_created_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
5
6
BEGIN
7
8
-- Insert into companies table
9
INSERT INTO public.companies (
10
id,
11
organization_id,
12
is_apartment,
13
name,
14
created_on_utc,
15
created_by
16
) VALUES (
17
p_company_id,
18
p_organization_id,
19
p_is_apartment,
20
p_name,
21
NOW(),
22
p_created_by
23
);
24
25
-- If this company is an apartment, insert into apartments table as well
26
IF p_is_apartment THEN
27
INSERT INTO public.apartments (
28
id,
29
organization_id,
30
name,
31
apartment_type_id,
32
created_on_utc,
33
created_by
34
) VALUES (
35
p_company_id,
36
p_organization_id,
37
p_name,
38
1, -- default apartment_type_id
39
NOW(),
40
p_created_by
41
);
42
END IF;
43
44
END;
45
$procedure$
|
|||||
| Procedure | update_ticket_status_with_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.update_ticket_status_with_logs(IN p_apartment_id uuid, IN p_ticket_status_id integer, IN p_ticket_ids uuid[], IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_ticket RECORD;
6
v_old_status int;
7
v_is_on_hold bool;
8
v_new_log_id uuid;
9
BEGIN
10
-- Process each ticket in the given list
11
FOR v_ticket IN
12
SELECT id, ticket_status_id, is_on_hold
13
FROM public.tickets
14
WHERE id = ANY(p_ticket_ids)
15
LOOP
16
v_old_status := v_ticket.ticket_status_id;
17
v_is_on_hold := v_ticket.is_on_hold;
18
19
-- Validate status transition
20
IF EXISTS (
21
SELECT 1
22
FROM public.ticket_workflow
23
WHERE status = v_old_status
24
AND next_status = p_ticket_status_id
25
AND apartment_id = p_apartment_id
26
) THEN
27
28
-- Update the ticket
29
UPDATE public.tickets
30
SET ticket_status_id = p_ticket_status_id,
31
modified_by = p_modified_by,
32
modified_on_utc = now()
33
WHERE id = v_ticket.id;
34
35
-- Insert log entry
36
v_new_log_id := gen_random_uuid();
37
38
INSERT INTO public.ticket_logs (
39
id,
40
ticket_id,
41
old_status_id,
42
new_status_id,
43
is_on_hold,
44
created_by,
45
created_on_utc
46
)
47
VALUES (
48
v_new_log_id,
49
v_ticket.id,
50
v_old_status,
51
p_ticket_status_id,
52
v_is_on_hold,
53
p_modified_by,
54
now()
55
);
56
57
RAISE NOTICE 'Ticket % updated from % to %', v_ticket.id, v_old_status, p_ticket_status_id;
58
59
ELSE
60
RAISE WARNING 'Invalid status transition for Ticket ID: % (old=% new=%)',
61
v_ticket.id, v_old_status, p_ticket_status_id;
62
END IF;
63
64
END LOOP;
65
66
END;
67
$procedure$
|
|||||
| Procedure | toggle_ticket_hold_with_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.toggle_ticket_hold_with_logs(IN p_ticket_ids uuid[], IN p_hold boolean, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_ticket RECORD;
6
v_log_id uuid;
7
BEGIN
8
FOR v_ticket IN
9
SELECT id, ticket_status_id, is_on_hold
10
FROM public.tickets
11
WHERE id = ANY(p_ticket_ids)
12
LOOP
13
14
----------------------------------------------------------------
15
-- Skip if state is already same
16
----------------------------------------------------------------
17
IF v_ticket.is_on_hold = p_hold THEN
18
IF p_hold THEN
19
RAISE NOTICE 'Ticket % is already ON HOLD', v_ticket.id;
20
ELSE
21
RAISE NOTICE 'Ticket % is already NOT on hold', v_ticket.id;
22
END IF;
23
CONTINUE;
24
END IF;
25
26
----------------------------------------------------------------
27
-- Update ticket hold state
28
----------------------------------------------------------------
29
UPDATE public.tickets
30
SET is_on_hold = p_hold,
31
modified_by = p_modified_by,
32
modified_on_utc = now()
33
WHERE id = v_ticket.id;
34
35
----------------------------------------------------------------
36
-- Insert TicketLog
37
----------------------------------------------------------------
38
v_log_id := gen_random_uuid();
39
40
INSERT INTO public.ticket_logs (
41
id,
42
ticket_id,
43
old_status_id,
44
new_status_id,
45
is_on_hold,
46
created_by,
47
created_on_utc
48
)
49
VALUES (
50
v_log_id,
51
v_ticket.id,
52
v_ticket.ticket_status_id,
53
v_ticket.ticket_status_id, -- no status change
54
p_hold, -- NEW hold value
55
p_modified_by,
56
now()
57
);
58
59
----------------------------------------------------------------
60
-- Messages
61
----------------------------------------------------------------
62
IF p_hold THEN
63
RAISE NOTICE 'Ticket % set to ON HOLD', v_ticket.id;
64
ELSE
65
RAISE NOTICE 'Ticket % UN-HOLD done', v_ticket.id;
66
END IF;
67
68
END LOOP;
69
70
END;
71
$procedure$
|
|||||
| Procedure | mark_ticket_reopened_with_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.mark_ticket_reopened_with_logs(IN p_ticket_ids uuid[], IN p_reopen_status_id integer, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_ticket RECORD;
6
v_log_id uuid;
7
BEGIN
8
FOR v_ticket IN
9
SELECT id, ticket_status_id, is_re_opened
10
FROM public.tickets
11
WHERE id = ANY(p_ticket_ids)
12
LOOP
13
14
----------------------------------------------------------------
15
-- Skip if already reopened once (idempotent)
16
----------------------------------------------------------------
17
IF v_ticket.is_re_opened THEN
18
RAISE NOTICE 'Ticket % is already marked as reopened', v_ticket.id;
19
CONTINUE;
20
END IF;
21
22
----------------------------------------------------------------
23
-- Update ticket (set reopened flag + change status)
24
----------------------------------------------------------------
25
UPDATE public.tickets
26
SET
27
ticket_status_id = p_reopen_status_id, -- dynamic
28
is_re_opened = true, -- YOUR COLUMN NAME
29
modified_by = p_modified_by,
30
modified_on_utc = now()
31
WHERE id = v_ticket.id;
32
33
----------------------------------------------------------------
34
-- Insert Ticket Log (NO EXTRA BOOLEAN)
35
----------------------------------------------------------------
36
v_log_id := gen_random_uuid();
37
38
INSERT INTO public.ticket_logs (
39
id,
40
ticket_id,
41
old_status_id,
42
new_status_id,
43
is_on_hold,
44
created_by,
45
created_on_utc
46
)
47
VALUES (
48
v_log_id,
49
v_ticket.id,
50
v_ticket.ticket_status_id,
51
p_reopen_status_id, -- new status
52
false, -- hold unchanged
53
p_modified_by,
54
now()
55
);
56
57
RAISE NOTICE 'Ticket % marked as REOPENED', v_ticket.id;
58
59
END LOOP;
60
61
END;
62
$procedure$
|
|||||
| Procedure | assign_ticket_with_logs | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.assign_ticket_with_logs(IN p_ticket_id uuid, IN p_service_provider_id integer, IN p_new_status_id integer, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_ticket RECORD;
6
v_log_id uuid;
7
BEGIN
8
----------------------------------------------------------------
9
-- Fetch full ticket row
10
----------------------------------------------------------------
11
SELECT
12
id,
13
ticket_status_id,
14
ticket_assigned_to,
15
is_on_hold
16
INTO v_ticket
17
FROM public.tickets
18
WHERE id = p_ticket_id;
19
20
IF v_ticket.id IS NULL THEN
21
RAISE EXCEPTION 'Ticket % not found', p_ticket_id;
22
END IF;
23
24
----------------------------------------------------------------
25
-- Update assigned_to and status
26
----------------------------------------------------------------
27
UPDATE public.tickets
28
SET
29
ticket_assigned_to = p_service_provider_id,
30
ticket_status_id = p_new_status_id,
31
modified_by = p_modified_by,
32
modified_on_utc = now()
33
WHERE id = p_ticket_id;
34
35
----------------------------------------------------------------
36
-- Insert assignment log
37
----------------------------------------------------------------
38
v_log_id := gen_random_uuid();
39
40
INSERT INTO public.ticket_logs (
41
id,
42
ticket_id,
43
old_status_id,
44
new_status_id,
45
is_on_hold,
46
created_by,
47
created_on_utc
48
)
49
VALUES (
50
v_log_id,
51
p_ticket_id,
52
v_ticket.ticket_status_id, -- OLD STATUS
53
p_new_status_id, -- NEW STATUS
54
v_ticket.is_on_hold, -- Hold flag unchanged
55
p_modified_by,
56
now()
57
);
58
59
END;
60
$procedure$
|
|||||
| Procedure | assign_multiple_tickets_with_logs_json | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.assign_multiple_tickets_with_logs_json(IN p_assignments jsonb, IN p_new_status_id integer, IN p_modified_by uuid)
2
LANGUAGE plpgsql
3
AS $procedure$
4
DECLARE
5
v_item jsonb;
6
v_ticket_id uuid;
7
v_sp_id int;
8
v_ticket RECORD;
9
v_log_id uuid;
10
BEGIN
11
FOR v_item IN SELECT * FROM jsonb_array_elements(p_assignments)
12
LOOP
13
v_ticket_id := (v_item->>'ticketId')::uuid;
14
v_sp_id := (v_item->>'serviceProviderId')::int;
15
16
-- Fetch ticket
17
SELECT id, ticket_status_id, ticket_assigned_to, is_on_hold
18
INTO v_ticket
19
FROM public.tickets
20
WHERE id = v_ticket_id;
21
22
IF v_ticket.id IS NULL THEN
23
RAISE EXCEPTION 'Ticket % not found', v_ticket_id;
24
END IF;
25
26
-- Update ticket
27
UPDATE public.tickets
28
SET
29
ticket_assigned_to = v_sp_id,
30
ticket_status_id = p_new_status_id,
31
modified_by = p_modified_by,
32
modified_on_utc = now()
33
WHERE id = v_ticket_id;
34
35
-- Insert log
36
v_log_id := gen_random_uuid();
37
38
INSERT INTO public.ticket_logs (
39
id, ticket_id, old_status_id, new_status_id,
40
is_on_hold, created_by, created_on_utc
41
)
42
VALUES (
43
v_log_id,
44
v_ticket_id,
45
v_ticket.ticket_status_id,
46
p_new_status_id,
47
v_ticket.is_on_hold,
48
p_modified_by,
49
now()
50
);
51
END LOOP;
52
END;
53
$procedure$
|
|||||
| Procedure | add_existing_user_role_permissions | Missing in Target |
Source Script
Target Script
1
CREATE OR REPLACE PROCEDURE public.add_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_permission_ids uuid[] DEFAULT NULL::uuid[], IN p_role_ids integer[] DEFAULT NULL::integer[])
2
LANGUAGE plpgsql
3
AS $procedure$
4
BEGIN
5
-- Insert user to organization if organization_id is provided
6
IF p_organization_id IS NOT NULL THEN
7
CALL public.insert_organization_user(p_user_id, p_created_by, p_organization_id);
8
END IF;
9
10
-- Insert user to company if company_id is provided
11
IF p_company_id IS NOT NULL THEN
12
CALL public.insert_company_user(p_user_id, p_created_by, p_company_id);
13
END IF;
14
15
-- Insert permissions if both permission_ids array and organization_id are provided
16
IF p_permission_ids IS NOT NULL AND p_organization_id IS NOT NULL THEN
17
CALL public.insert_user_permission(p_user_id, p_organization_id, p_permission_ids, p_created_by);
18
END IF;
19
20
-- Insert roles if role_ids array is provided
21
IF p_role_ids IS NOT NULL THEN
22
CALL public.insert_user_roles(p_user_id, p_role_ids, p_organization_id, p_created_by);
23
END IF;
24
END;
25
$procedure$
|
-- Table: residents
Exists in source, missing in target
-- Table: countries
Exists in source, missing in target
-- Table: event_occurrence_status
Exists in source, missing in target
-- Table: guest_approvals
Exists in source, missing in target
-- Table: community_directory_verification_statuses
Exists in source, missing in target
-- Table: visitor_phones
Exists in source, missing in target
-- Table: service_provider_ticket_categories
Exists in source, missing in target
-- Table: visitors
Exists in source, missing in target
-- Table: poll_votes
Exists in source, missing in target
-- Table: __EFMigrationsHistory
Exists in source, missing in target
-- Table: occupant_types
Exists in source, missing in target
-- Table: organizations
Exists in source, missing in target
-- Table: community_calenders
Exists in source, missing in target
-- Table: buildings
Exists in source, missing in target
-- Table: committee_members
Exists in source, missing in target
-- Table: UserToRoleMapping
Exists in source, missing in target
-- Table: ticket_number_sequences
Exists in source, missing in target
-- Table: pre_approved_entries
Exists in source, missing in target
-- Table: meeting_agenda_items
Exists in source, missing in target
-- Table: ticket_logs
Exists in source, missing in target
-- Table: noc_approvals
Exists in source, missing in target
-- Table: vehicles
Exists in source, missing in target
-- Table: unit_statuses
Exists in source, missing in target
-- Table: states
Exists in source, missing in target
-- Table: service_provider_logs
Exists in source, missing in target
-- Table: noc_certificates
Exists in source, missing in target
-- Table: sub_categories
Exists in source, missing in target
-- Table: delivery_companies
Exists in source, missing in target
-- Table: noc_due_snapshots
Exists in source, missing in target
-- Table: noc_requests
Exists in source, missing in target
-- Table: tickets
Exists in source, missing in target
-- Table: unit_vehicle_limits
Exists in source, missing in target
-- Table: noc_types
Exists in source, missing in target
-- Table: visitor_statuses
Exists in source, missing in target
-- Table: service_provider_logs_2022_2023
Exists in source, missing in target
-- Table: banks
Exists in source, missing in target
-- Table: visitor_vehicles
Exists in source, missing in target
-- Table: occupancy_types
Exists in source, missing in target
-- Table: noc_approval_roles
Exists in source, missing in target
-- Table: water_tanker_deliveries
Exists in source, missing in target
-- Table: units
Exists in source, missing in target
-- Table: visitor_approvals
Exists in source, missing in target
-- Table: visitor_types
Exists in source, missing in target
-- Table: visitor_sp_company_visits
Exists in source, missing in target
-- Table: service_provider_verifications
Exists in source, missing in target
-- Table: resident_tokens
Exists in source, missing in target
-- Table: pre_approved_schedule_rules
Exists in source, missing in target
-- Table: visitor_details
Exists in source, missing in target
-- Table: visitor_apartments
Exists in source, missing in target
-- Table: visitor_emails
Exists in source, missing in target
-- Table: floors
Exists in source, missing in target
-- Table: gate_types
Exists in source, missing in target
-- Table: emergency_contacts
Exists in source, missing in target
-- Table: resident_types
Exists in source, missing in target
-- Table: visitor_identities
Exists in source, missing in target
-- Table: visitor_logs
Exists in source, missing in target
-- Table: member_additional_details
Exists in source, missing in target
-- Table: push_notifications
Exists in source, missing in target
-- Table: multi_unit_visits
Exists in source, missing in target
-- Table: meeting_action_items
Exists in source, missing in target
-- Table: meeting_notes
Exists in source, missing in target
-- Table: event_status
Exists in source, missing in target
-- Table: event_types
Exists in source, missing in target
-- Table: identity_types
Exists in source, missing in target
-- Table: ticket_comment_documents
Exists in source, missing in target
-- Table: gates
Exists in source, missing in target
-- Table: ticket_comments
Exists in source, missing in target
-- Table: users
Exists in source, missing in target
-- Table: event_occurrences
Exists in source, missing in target
-- Table: has_default
Exists in source, missing in target
-- Table: service_provider_apartments
Exists in source, missing in target
-- Table: community_directory_location_types
Exists in source, missing in target
-- Table: community_directory_source_types
Exists in source, missing in target
-- Table: community_directory_listing_types
Exists in source, missing in target
-- Table: community_directory_categories
Exists in source, missing in target
-- Table: community_directory_status_types
Exists in source, missing in target
-- Table: pins
Exists in source, missing in target
-- Table: building_types
Exists in source, missing in target
-- Table: facility_bookings
Exists in source, missing in target
-- Table: resident_units
Exists in source, missing in target
-- Table: resident_requests
Exists in source, missing in target
-- Table: ticket_priorities
Exists in source, missing in target
-- Table: cities
Exists in source, missing in target
-- Table: companies
Exists in source, missing in target
-- Table: delivery_personnel
Exists in source, missing in target
-- Table: events
Exists in source, missing in target
-- Table: verification_types
Exists in source, missing in target
-- Table: apartments
Exists in source, missing in target
-- Table: apartment_requests
Exists in source, missing in target
-- Table: ticket_statuses
Exists in source, missing in target
-- Table: polls
Exists in source, missing in target
-- Table: vehicle_types
Exists in source, missing in target
-- Table: UserToPaidModulesMapping
Exists in source, missing in target
-- Table: ticket_documents
Exists in source, missing in target
-- Table: calendars
Exists in source, missing in target
-- Table: apartment_types
Exists in source, missing in target
-- Table: addresses
Exists in source, missing in target
-- Table: service_provider_companies
Exists in source, missing in target
-- Table: service_provider_company_categories
Exists in source, missing in target
-- Table: service_provider_company_subtypes
Exists in source, missing in target
-- Table: service_providers
Exists in source, missing in target
-- Table: unit_service_providers
Exists in source, missing in target
-- Table: ticket_categories
Exists in source, missing in target
-- Table: possible_visitors
Exists in source, missing in target
-- Table: user_roles
Exists in source, missing in target
-- Table: community_documents
Exists in source, missing in target
-- Table: document_types
Exists in source, missing in target
-- Table: user_fcm_tokens
Exists in source, missing in target
-- Table: security_guard_apartments
Exists in source, missing in target
-- Table: service_provider_users
Exists in source, missing in target
-- Table: community_directory_listings
Exists in source, missing in target
-- Table: community_directory_contacts
Exists in source, missing in target
-- Table: community_directory_recommendations
Exists in source, missing in target
-- Table: decisions
Exists in source, missing in target
-- Table: poll_types
Exists in source, missing in target
-- Table: decision_outcomes
Exists in source, missing in target
-- Table: poll_statuses
Exists in source, missing in target
-- Table: poll_options
Exists in source, missing in target
-- Table: visitor_delivery_company
Exists in source, missing in target
-- Table: ticket_workflow
Exists in source, missing in target
-- Table: categories
Exists in source, missing in target
-- Table: complaint
Exists in source, missing in target
-- Table: delivery_company_categories
Exists in source, missing in target
-- Table: ticket_fors
Exists in source, missing in target
-- Table: notice_categories
Exists in source, missing in target
-- Table: notice_priorities
Exists in source, missing in target
-- Table: notices
Exists in source, missing in target
-- Table: schema_versions
Exists in source, missing in target
-- Table: service_provider_sub_types
Exists in source, missing in target
-- Table: service_provider_types
Exists in source, missing in target
-- Table: service_provider_addresses
Exists in source, missing in target
-- Table: meeting_occurrences
Exists in source, missing in target
-- Table: meeting_statuses
Exists in source, missing in target
-- Table: occurrence_participants
Exists in source, missing in target
-- Table: roles
Exists in source, missing in target
-- Table: portfolios
Exists in source, missing in target
-- Table: rsvp_statuses
Exists in source, missing in target
-- Table: attendance_statuses
Exists in source, missing in target
-- Table: user_device_tokens
Exists in source, missing in target
-- Table: visit_types
Exists in source, missing in target
-- Table: unit_types
Exists in source, missing in target
-- Table: service_provider_logs_2024_2025
Exists in source, missing in target
-- Table: meeting_modes
Exists in source, missing in target
-- Table: gate_passes
Exists in source, missing in target
-- Table: participant_roles
Exists in source, missing in target
-- Table: raise_tickets
Exists in source, missing in target
-- Table: resident_request_statuses
Exists in source, missing in target
-- Table: service_provider_logs_2023_2024
Exists in source, missing in target
-- Table: community_directory_locations
Exists in source, missing in target
-- Table: option_types
Exists in source, missing in target
-- Table: poll_decisions
Exists in source, missing in target
-- Table: poll_eligibility_types
Exists in source, missing in target
-- Table: ticket_service_provider_otps
Exists in source, missing in target
-- Table: service_provider_logs_2025_2026
Exists in source, missing in target
-- 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: generate_random_pin
CREATE OR REPLACE FUNCTION public.generate_random_pin()
RETURNS character varying
LANGUAGE plpgsql
AS $function$
DECLARE
pin_code VARCHAR(6);
BEGIN
-- Generate a random 6-digit number
pin_code := LPAD(CAST(FLOOR(RANDOM() * 1000000) AS VARCHAR), 6, '0');
RETURN pin_code;
END;
$function$
-- Function: get_active_committee_members_by_apartment
CREATE OR REPLACE FUNCTION public.get_active_committee_members_by_apartment(p_apartment_id uuid)
RETURNS TABLE(id integer, apartment_id uuid, role_id integer, user_id uuid, member_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
cm.id,
cm.apartment_id,
cm.role_id,
cm.user_id,
u.first_name || ' ' || u.last_name AS member_name
FROM
committee_members cm
inner join users u on u.id = cm.user_id
WHERE
cm.is_deleted = FALSE
AND cm.apartment_id = p_apartment_id
AND cm.effective_start_date <= NOW()
AND (cm.effective_end_date IS NULL OR cm.effective_end_date >= NOW());
END;
$function$
-- Function: get_current_visitors_last24h
CREATE OR REPLACE FUNCTION public.get_current_visitors_last24h(p_apartment_id uuid)
RETURNS TABLE(visitor_id integer, first_name text, last_name text, email text, visiting_from text, contact_number text, vehicle_number text, entry_time timestamp without time zone, visitor_type_id integer, identity_type_id integer, identity_number text)
LANGUAGE plpgsql
STABLE
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id AS visitor_id,
v.first_name,
v.last_name,
v.email,
v.visiting_from,
v.contact_number,
v.vehicle_number,
vl.entry_time,
v.visitor_type_id,
v.identity_type_id,
v.identity_number
FROM
public.visitors v
INNER JOIN public.visitor_logs vl ON vl.visitor_id = v.id
WHERE
v.apartment_id = p_apartment_id
AND vl.exit_time IS NULL
AND vl.entry_time >= (NOW() AT TIME ZONE 'UTC') - INTERVAL '24 hours'
AND vl.is_deleted = false
AND v.is_deleted = false;
END;
$function$
-- Function: get_resident_fcm_tokens_by_visitor_log_id
CREATE OR REPLACE FUNCTION public.get_resident_fcm_tokens_by_visitor_log_id(p_visitorlogid integer)
RETURNS TABLE(id integer, user_ids uuid[], visitor_id integer, first_name text, last_name text, fcm_tokens text[], device_ids text[])
LANGUAGE sql
AS $function$
SELECT
ROW_NUMBER() OVER ()::INT AS id, -- synthetic serial-like column
ARRAY_AGG(DISTINCT r.user_id) AS user_ids,
v.id AS visitor_id,
v.first_name AS first_name,
v.last_name AS last_name,
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.fcm_token), NULL) AS fcm_tokens,
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.device_id), NULL) AS device_ids
FROM multi_unit_visits muv
INNER JOIN resident_units ru ON muv.unit_id = ru.unit_id
INNER JOIN residents r ON ru.resident_id = r.id
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
INNER JOIN visitors v ON v.id = vl.visitor_id
LEFT JOIN user_fcm_tokens uft ON uft.user_id = r.user_id
WHERE muv.visitor_log_id = p_visitorlogid
AND muv.is_deleted = FALSE
AND ru.is_deleted = FALSE
AND r.is_deleted = FALSE
GROUP BY v.id, v.first_name, v.last_name;
$function$
-- Function: get_user_ids_by_visitor_log_id
CREATE OR REPLACE FUNCTION public.get_user_ids_by_visitor_log_id(p_visitorlogid integer)
RETURNS TABLE(id integer, user_ids uuid[], visitor_id integer, first_name text, last_name text)
LANGUAGE sql
AS $function$
SELECT
ROW_NUMBER() OVER ()::INT AS id, -- synthetic serial-like column
ARRAY_AGG(DISTINCT r.user_id) AS user_ids,
v.id AS visitor_id,
v.first_name As first_name,
v.last_name As last_name
FROM multi_unit_visits muv
INNER JOIN resident_units ru ON muv.unit_id = ru.unit_id
INNER JOIN residents r ON ru.resident_id = r.id
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
INNER JOIN visitors v ON v.id = vl.visitor_id
WHERE muv.visitor_log_id = p_VisitorLogId
AND muv.is_deleted = FALSE
AND ru.is_deleted = FALSE
AND r.is_deleted = FALSE
GROUP BY v.id, v.first_name, v.last_name;
$function$
-- Function: get_visitor_logs
CREATE OR REPLACE FUNCTION public.get_visitor_logs(p_apartment_id uuid, p_visitor_id integer, p_visitor_type_id integer)
RETURNS TABLE(visitor_log_id integer, visitor_id integer, visitor_first_name text, unit_id integer, unit_name text, visitor_type_id integer, visitor_type_name text, visiting_from text, current_status_id integer, entry_time timestamp without time zone, exit_time timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
vl.id AS visitor_log_id,
vl.visitor_id,
v.first_name AS visitor_first_name,
u.id AS unit_id,
u.name AS unit_name,
vl.visitor_type_id,
(SELECT vt.name::TEXT -- Explicitly cast visitor type name to TEXT
FROM visitor_types vt
WHERE vt.id = vl.visitor_type_id
LIMIT 1) AS visitor_type_name,
vl.visiting_from,
vl.current_status_id,
vl.entry_time,
vl.exit_time
FROM visitor_logs vl
INNER JOIN visitors v ON vl.visitor_id = v.id
INNER JOIN visitor_unit_logs vut ON vl.id = vut.visitor_log_id
INNER JOIN units u ON vut.unit_id = u.id
WHERE v.apartment_id = p_apartment_id
AND vl.visitor_id = p_visitor_id
AND vl.visitor_type_id = p_visitor_type_id
ORDER BY vl.id;
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: unit_resident_fcm_tokens
CREATE OR REPLACE FUNCTION public.unit_resident_fcm_tokens(p_unit_id integer)
RETURNS TABLE(resident_id integer, user_id uuid, device_id text, fcm_token text)
LANGUAGE sql
AS $function$
SELECT
ru.resident_id,
r.user_id,
uft.device_id,
uft.fcm_token
FROM resident_units ru
INNER JOIN residents r ON ru.resident_id = r.id
LEFT JOIN user_fcm_tokens uft ON uft.user_id = r.user_id
WHERE ru.unit_id = p_unit_id
AND ru.is_deleted = FALSE
AND r.is_deleted = FALSE
AND (uft.fcm_token IS NOT NULL OR uft.device_id IS NOT NULL);
$function$
-- Function: update_visitor_log
CREATE OR REPLACE FUNCTION public.update_visitor_log(p_visitor_log_id integer)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
UPDATE public.visitor_logs
SET
exit_time = NOW(),
visitor_status_id = 4,
modified_on_utc = NOW()
WHERE id = p_visitor_log_id;
END;
$function$
-- Function: update_visitor_approval
CREATE OR REPLACE FUNCTION public.update_visitor_approval(p_visitor_approval_id integer, p_visit_type_id integer, p_start_date date, p_end_date date, p_entry_time time without time zone, p_exit_time time without time zone, p_vehicle_number text, p_company_name text, p_created_by uuid)
RETURNS TABLE(visitor_approve_id integer, visitor_id integer, visit_type_id integer, start_date date, end_date date, entry_time time without time zone, exit_time time without time zone, vehicle_number text, company_name text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
-- Update the record
UPDATE public.visitor_approvals va
SET
visit_type_id = p_visit_type_id,
start_date = COALESCE(p_start_date, va.start_date),
end_date = COALESCE(p_end_date, va.end_date),
entry_time = COALESCE(p_entry_time, va.entry_time),
exit_time = COALESCE(p_exit_time, va.exit_time),
vehicle_number = COALESCE(p_vehicle_number, va.vehicle_number),
company_name = COALESCE(p_company_name, va.company_name),
modified_on_utc = NOW(),
modified_by = p_created_by
WHERE va.id = p_visitor_approval_id;
-- Check if update happened
IF NOT FOUND THEN
RAISE EXCEPTION 'Visitor approval not found for id: %', p_visitor_approval_id;
END IF;
-- Return the updated row
RETURN QUERY
SELECT
va.id AS visitor_approve_id,
va.visitor_id,
va.visit_type_id,
va.start_date,
va.end_date,
va.entry_time,
va.exit_time,
va.vehicle_number,
va.company_name,
va.created_on_utc,
va.modified_on_utc,
va.created_by,
va.modified_by
FROM public.visitor_approvals va
WHERE va.id = p_visitor_approval_id;
END;
$function$
-- Function: upsert_user_fcm_token
CREATE OR REPLACE FUNCTION public.upsert_user_fcm_token(p_user_id uuid, p_device_id text, p_fcm_token text, p_platform text)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_id int4;
BEGIN
INSERT INTO public.user_fcm_tokens (user_id, device_id, fcm_token, platform, upsert_on_utc)
VALUES (p_user_id, p_device_id, p_fcm_token, p_platform, now())
ON CONFLICT (user_id, device_id)
DO UPDATE SET
fcm_token = EXCLUDED.fcm_token,
platform = EXCLUDED.platform,
upsert_on_utc = now()
RETURNING id INTO v_id;
RETURN v_id;
END;
$function$
-- Function: get_all_committee_members_by_apartment
CREATE OR REPLACE FUNCTION public.get_all_committee_members_by_apartment(p_apartment_id uuid)
RETURNS TABLE(id integer, user_id uuid, member_name text, effective_start_date timestamp without time zone, effective_end_date timestamp without time zone, role_id integer, role_name character varying, portfolio_id integer, portfolio_name text)
LANGUAGE sql
AS $function$
SELECT
cm.id,
cm.user_id,
CONCAT(res.first_name, ' ', res.last_name) AS member_name,
cm.effective_start_date,
cm.effective_end_date,
cm.role_id,
r.name AS role_name,
cm.portfolio_id,
p.name AS portfolio_name
FROM
committee_members cm
LEFT JOIN roles r ON r.id = cm.role_id
LEFT JOIN portfolios p ON p.id = cm.portfolio_id
LEFT JOIN (
SELECT DISTINCT ON (user_id) user_id, first_name, last_name
FROM residents
WHERE is_deleted = false
ORDER BY user_id, id
) res ON res.user_id = cm.user_id
WHERE
cm.apartment_id = p_apartment_id
AND cm.is_deleted = false;
$function$
-- Function: get_all_roles
CREATE OR REPLACE FUNCTION public.get_all_roles()
RETURNS TABLE(id integer, name character varying, description character varying)
LANGUAGE sql
AS $function$
SELECT id, name, description
FROM public.roles
WHERE true
ORDER BY id;
$function$
-- Function: get_water_tanker_summary
CREATE OR REPLACE FUNCTION public.get_water_tanker_summary(p_company_id uuid, p_start_date date, p_end_date date)
RETURNS TABLE(start_date date, end_date date, total_tankers integer, total_liters numeric)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
p_start_date,
p_end_date,
COUNT(*)::INT AS total_tankers, -- β
cast to INT
COALESCE(SUM(actual_received_liters)::NUMERIC(14,2), 0) AS total_liters
FROM public.water_tanker_deliveries
WHERE company_id = p_company_id
AND delivery_date BETWEEN p_start_date AND p_end_date
AND NOT is_deleted;
END;
$function$
-- Function: get_unit_info_by_user_apartment
CREATE OR REPLACE FUNCTION public.get_unit_info_by_user_apartment(p_user_id uuid, p_apartment_id uuid)
RETURNS TABLE(id integer, unit_id integer, customer_id uuid, unit_name text, user_name text, apartment_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ru.id, -- added resident_units primary key
ru.unit_id,
u.customer_id,
u.name,
usr.first_name || ' ' || usr.last_name AS user_name,
r.apartment_id
FROM resident_units ru
INNER JOIN residents r ON ru.resident_id = r.id
INNER JOIN units u ON ru.unit_id = u.id
INNER JOIN users usr ON r.user_id = usr.id
WHERE r.user_id = p_user_id
AND r.apartment_id = p_apartment_id;
END;
$function$
-- Function: get_portfolios_by_apartment
CREATE OR REPLACE FUNCTION public.get_portfolios_by_apartment(p_apartment_id uuid)
RETURNS TABLE(id integer, apartment_id uuid, name text, description text)
LANGUAGE sql
AS $function$
SELECT id, apartment_id, name, description
FROM public.portfolios
WHERE apartment_id = p_apartment_id
ORDER BY id;
$function$
-- Function: get_committee_members_by_apartment
CREATE OR REPLACE FUNCTION public.get_committee_members_by_apartment(p_apartment_id uuid)
RETURNS TABLE(id integer, apartment_id uuid, role_id integer, user_id uuid, member_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
cm.id,
cm.apartment_id,
cm.role_id,
cm.user_id,
u.first_name || ' ' || u.last_name AS member_name
FROM
committee_members cm
inner join users u on u.id = cm.user_id
WHERE
cm.is_deleted = FALSE
AND cm.apartment_id = p_apartment_id
AND cm.effective_start_date <= NOW()
AND (cm.effective_end_date IS NULL OR cm.effective_end_date >= NOW());
END;
$function$
-- Function: get_units_with_primary_owner_name
CREATE OR REPLACE FUNCTION public.get_units_with_primary_owner_name(p_apartment_id uuid)
RETURNS TABLE(id integer, customer_id uuid, unit_id integer, unit_name text, owner_first_name text, owner_last_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ROW_NUMBER() OVER (ORDER BY u.name)::int AS id, -- 1,2,3...
u.customer_id AS customer_id,
u.id AS unit_id,
u.name AS unit_name,
r.first_name AS owner_first_name,
r.last_name AS owner_last_name
FROM public.units u
JOIN public.resident_units ru
ON ru.unit_id = u.id
AND (ru.is_deleted = false OR ru.is_deleted IS NULL)
AND ru.is_primary_owner = true
JOIN public.residents r
ON r.id = ru.resident_id
AND (r.is_deleted = false OR r.is_deleted IS NULL)
WHERE (u.is_deleted = false OR u.is_deleted IS NULL)
AND u.apartment_id = p_apartment_id
ORDER BY u.name;
END;
$function$
-- Function: get_visitors_of_unit
CREATE OR REPLACE FUNCTION public.get_visitors_of_unit(p_unit_id integer, p_datetime timestamp without time zone)
RETURNS TABLE(id integer, visitor_log_id integer, name text, visiting_from text, checked_in_at timestamp without time zone, checked_out_at timestamp without time zone, status text, status_id integer, visitor_type text, visitor_type_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_visit_date DATE := p_datetime::date;
BEGIN
RETURN QUERY
SELECT
ROW_NUMBER() OVER ()::integer AS id, -- Cast ROW_NUMBER() result to integer
vl.id AS visitor_log_id,
TRIM(v.first_name || ' ' || COALESCE(v.last_name, '')) AS name,
vl.visiting_from,
vl.entry_time AS checked_in_at,
vl.exit_time AS checked_out_at,
vs.name::text AS status,
vs.id AS status_id,
vt.name::text AS visitor_type,
vt.id AS visitor_type_id
FROM
multi_unit_visits muv
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
INNER JOIN visitors v ON v.id = vl.visitor_id
INNER JOIN visitor_statuses vs ON vs.id = muv.visitor_statuses
INNER JOIN visitor_types vt ON vt.id = vl.visitor_type_id
WHERE
muv.unit_id = p_unit_id
AND DATE(vl.entry_time) = v_visit_date
AND muv.is_deleted = FALSE
AND vl.is_deleted = FALSE
AND v.is_deleted = FALSE;
END;
$function$
-- Function: get_resident_details_by_user
CREATE OR REPLACE FUNCTION public.get_resident_details_by_user(p_company_id uuid, p_user_id uuid)
RETURNS TABLE(id integer, resident_id integer, first_name text, last_name text, email text, resident_primary_owner boolean, unit_id integer, unit_name text, customer_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
r.id AS id,
r.id AS resident_id,
r.first_name,
r.last_name,
r.email,
r.is_primary_owner AS resident_primary_owner,
ru.unit_id,
u.name AS unit_name,
u.customer_id
FROM residents r
LEFT JOIN resident_units ru
ON ru.resident_id = r.id
AND ru.is_deleted = false
LEFT JOIN units u
ON u.id = ru.unit_id
AND u.apartment_id = p_company_id
WHERE r.user_id = p_user_id
AND r.is_deleted = false;
END;
$function$
-- Function: get_vehicles_by_unit
CREATE OR REPLACE FUNCTION public.get_vehicles_by_unit(p_unit_id integer)
RETURNS TABLE(id integer, vehicle_number text, vehicle_type_id integer, unit_id integer, vehicle_rf_id text, vehicle_rf_id_secretcode text, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, deleted_on_utc timestamp without time zone, is_deleted boolean, created_by uuid, modified_by uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id, v.vehicle_number, v.vehicle_type_id, v.unit_id,
v.vehicle_rf_id, v.vehicle_rf_id_secretcode,
v.created_on_utc, v.modified_on_utc, v.deleted_on_utc,
v.is_deleted, v.created_by, v.modified_by
FROM public.vehicles v
WHERE v.is_deleted = false
AND v.unit_id = p_unit_id;
END;
$function$
-- Function: set_sequence_for_table
CREATE OR REPLACE FUNCTION public.set_sequence_for_table(p_table_name text, p_sequence_name text)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
max_id bigint;
is_identity boolean;
sql_alter text;
sql_setval text;
BEGIN
-- Get max id from the table
EXECUTE format('SELECT COALESCE(MAX(id), 0) FROM %I', p_table_name) INTO max_id;
-- Check if the id column is identity
SELECT EXISTS (
SELECT 1 FROM information_schema.columns c
WHERE c.table_name = p_table_name
AND c.column_name = 'id'
AND c.is_identity = 'YES'
) INTO is_identity;
IF NOT is_identity THEN
-- ALTER TABLE to set default nextval only if not identity
sql_alter := format('ALTER TABLE %I ALTER COLUMN id SET DEFAULT nextval(''%I'')', p_table_name, p_sequence_name);
EXECUTE sql_alter;
END IF;
-- Debug: raise notice of max id
RAISE NOTICE 'Setting sequence % to max id % for table %', p_sequence_name, max_id, p_table_name;
-- Set the sequence value to max_id with is_called = true
sql_setval := format('SELECT setval(''%I'', %s, true)', p_sequence_name, max_id);
EXECUTE sql_setval;
END;
$function$
-- Function: get_user_details
CREATE OR REPLACE FUNCTION public.get_user_details(p_user_id uuid, p_apartment_id uuid)
RETURNS TABLE(user_id uuid, user_email text, resident_id integer, first_name text, last_name text, contact_number text, is_primary_owner boolean, unit_id integer, unit_name text, building_id integer, building_name text, floor_id integer, floor_name text, area numeric, bhk_type numeric, customer_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
u.id AS user_id,
u.email::text AS user_email, -- Fix: varchar β text
r.id AS resident_id,
r.first_name,
r.last_name,
r.contact_number,
r.is_primary_owner,
ru.unit_id,
un.name AS unit_name,
b.id AS building_id,
b.name AS building_name,
f.id AS floor_id,
f.name AS floor_name,
un.area,
un.bhk_type,
un.customer_id
FROM residents r
JOIN users u
ON u.id = r.user_id
LEFT JOIN resident_units ru
ON ru.resident_id = r.id
AND ru.is_deleted = false
LEFT JOIN units un
ON un.id = ru.unit_id
AND un.is_deleted = false
LEFT JOIN floors f
ON f.id = un.floor_id
AND f.is_deleted = false
LEFT JOIN buildings b
ON b.id = f.building_id
AND b.is_deleted = false
WHERE r.is_deleted = false
AND r.user_id = p_user_id
AND r.apartment_id = p_apartment_id;
END;
$function$
-- Function: get_units_by_user
CREATE OR REPLACE FUNCTION public.get_units_by_user(p_company_id uuid, p_user_id uuid)
RETURNS TABLE(id integer, resident_unit_id integer, unit_id integer, unit_name text, customer_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ru.id AS id, -- β
NEW COLUMN
ru.id AS resident_unit_id, -- existing
u.id AS unit_id,
u.name AS unit_name,
u.customer_id
FROM residents r
INNER JOIN resident_units ru
ON ru.resident_id = r.id
AND ru.is_deleted = FALSE
INNER JOIN units u
ON u.id = ru.unit_id
INNER JOIN apartments a
ON a.id = u.apartment_id
WHERE a.id = p_company_id -- β
company filter
AND r.user_id = p_user_id -- β
user filter
AND r.is_deleted = FALSE;
END;
$function$
-- Function: get_inhouse_service_providers
CREATE OR REPLACE FUNCTION public.get_inhouse_service_providers(p_apartment_id uuid, p_types integer[])
RETURNS TABLE(id integer, service_provider_id integer, first_name text, last_name text, service_provider_type_id integer, service_provider_type_name character varying)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
CAST(ROW_NUMBER() OVER () AS int) AS id, -- index
sp.id AS service_provider_id, -- actual service provider ID
sp.first_name,
sp.last_name,
sp.service_provider_type_id,
spt.name AS service_provider_type_name
FROM public.service_providers sp
INNER JOIN public.service_provider_types spt
ON sp.service_provider_type_id = spt.id
WHERE sp.apartment_id = p_apartment_id
AND sp.service_provider_type_id = ANY(p_types)
AND sp.is_deleted = false
AND COALESCE(spt.is_deleted, false) = false;
END;
$function$
-- Function: get_visitor_by_contact_or_email
CREATE OR REPLACE FUNCTION public.get_visitor_by_contact_or_email(p_apartment_id uuid DEFAULT NULL::uuid, p_contact_number text DEFAULT NULL::text, p_email text DEFAULT NULL::text)
RETURNS TABLE(id integer, first_name text, last_name text, email text, contact_number text, visitor_type_id integer, vehicle_number text, identity_type_id integer, identity_number text, apartment_id uuid)
LANGUAGE sql
AS $function$
SELECT
v.id,
v.first_name,
v.last_name,
v.email,
v.contact_number,
v.visitor_type_id,
v.vehicle_number,
v.identity_type_id,
v.identity_number,
v.apartment_id
FROM public.visitors v
WHERE v.is_deleted = false
AND (p_apartment_id IS NULL OR v.apartment_id = p_apartment_id)
AND (
(p_contact_number IS NOT NULL AND trim(v.contact_number) = trim(p_contact_number))
OR
(p_email IS NOT NULL AND lower(trim(v.email)) = lower(trim(p_email)))
);
$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);
END LOOP;
-- Grant on sequences
FOR obj IN
SELECT sequence_name
FROM information_schema.sequences
WHERE sequence_schema = p_schema
LOOP
EXECUTE format('GRANT USAGE, SELECT ON SEQUENCE %I.%I TO %I;', p_schema, obj.sequence_name, p_user);
END LOOP;
-- Grant on functions
FOR obj IN
SELECT routine_name, specific_name
FROM information_schema.routines
WHERE routine_schema = p_schema
AND routine_type = 'FUNCTION'
LOOP
EXECUTE format('GRANT EXECUTE ON FUNCTION %I.%I() TO %I;', p_schema, obj.routine_name, p_user);
-- Note: This only grants for functions without parameters. For overloaded functions, you may need to manually grant.
END LOOP;
-- Grant on procedures (Postgres 11+)
FOR obj IN
SELECT routine_name, specific_name
FROM information_schema.routines
WHERE routine_schema = p_schema
AND routine_type = 'PROCEDURE'
LOOP
EXECUTE format('GRANT EXECUTE ON PROCEDURE %I.%I() TO %I;', p_schema, obj.routine_name, p_user);
-- Note: As above, only for procedures without params.
END LOOP;
END;
$function$
-- Function: get_apartment_visitors_in_timespan
CREATE OR REPLACE FUNCTION public.get_apartment_visitors_in_timespan(p_apartment_id uuid, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
RETURNS TABLE(id integer, visitor_id integer, visitor_type_id integer, visitor_type text, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_person_type text, visitor_person_sub_type text, visitor_status_id integer, visitor_status text, building_id integer, building_name text, floor_id integer, floor_name text, unit_id integer, unit_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
CAST(ROW_NUMBER() OVER (ORDER BY vl.entry_time, v.id) AS int) AS serial_id,
v.id as visitor_id,
vt.id AS visitor_type_id,
'visitor'::text AS visitor_type,
v.first_name::text,
v.last_name::text,
vl.entry_time,
vl.exit_time,
vt.name::text AS visitor_person_type,
NULL::text AS visitor_person_sub_type,
CASE
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 3
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 4
ELSE vl.visitor_status_id
END AS visitor_status_id,
(
CASE
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 'Checked In'
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 'Checked Out'
ELSE COALESCE(vs.name::text, 'Unknown')
END
)::text AS visitor_status,
b.id AS building_id,
b.name::text AS building_name,
f.id AS floor_id,
f.name::text AS floor_name,
u.id AS unit_id,
u.name::text AS unit_name
FROM public.visitor_logs vl
JOIN public.visitors v ON vl.visitor_id = v.id
JOIN public.visitor_types vt ON v.visitor_type_id = vt.id
LEFT JOIN public.visitor_statuses vs ON vl.visitor_status_id = vs.id
-- π Join chain for hierarchy
LEFT JOIN public.multi_unit_visits muv ON muv.visitor_log_id = vl.id AND COALESCE(muv.is_deleted, FALSE) = FALSE
LEFT JOIN public.units u ON u.id = muv.unit_id AND COALESCE(u.is_deleted, FALSE) = FALSE
LEFT JOIN public.floors f ON f.id = u.floor_id AND COALESCE(f.is_deleted, FALSE) = FALSE
LEFT JOIN public.buildings b ON b.id = f.building_id AND COALESCE(b.is_deleted, FALSE) = FALSE
--LEFT JOIN public.apartments a ON a.id = COALESCE(v.apartment_id, b.apartment_id) AND COALESCE(a.is_deleted, FALSE) = FALSE
WHERE
vl.entry_time BETWEEN p_start_date AND p_end_date
AND COALESCE(vl.is_deleted, FALSE) = FALSE
AND COALESCE(v.is_deleted, FALSE) = FALSE
ORDER BY vl.entry_time, v.id;
END;
$function$
-- Function: get_unit_related_details_by_user
CREATE OR REPLACE FUNCTION public.get_unit_related_details_by_user(p_user_id uuid)
RETURNS jsonb
LANGUAGE sql
AS $function$
SELECT COALESCE(
/* 1οΈβ£ Unit-based JSON (resident users) */
(
SELECT jsonb_build_object(
'id', u.id,
'name', u.name,
'unit_type_id', u.unit_type_id,
'phone_extension', u.phone_extention,
'area', u.area,
'bhk_type', u.bhk_type,
'occupant_type_id', u.occupant_type_id,
'occupancy_type_id', u.occupancy_type_id,
'residents', COALESCE(
(
SELECT jsonb_agg(
DISTINCT jsonb_build_object(
'id', r2.id,
'first_name', r2.first_name,
'last_name', r2.last_name,
'email', r2.email,
'contact_number', r2.contact_number,
'resident_type', rt.name
)
)
FROM resident_units ru2
JOIN residents r2
ON r2.id = ru2.resident_id
AND r2.is_deleted = false
JOIN resident_types rt
ON rt.id = r2.resident_type_id
AND rt.is_deleted = false
WHERE ru2.unit_id = u.id
AND ru2.is_deleted = false
),
'[]'::jsonb
),
'vehicles', COALESCE(
(
SELECT jsonb_agg(
DISTINCT jsonb_build_object(
'id', v.id,
'vehicle_number', v.vehicle_number,
'vehicle_type', vt.name
)
)
FROM vehicles v
JOIN vehicle_types vt
ON vt.id = v.vehicle_type_id
AND vt.is_deleted = false
WHERE v.unit_id = u.id
AND v.is_deleted = false
),
'[]'::jsonb
)
)
FROM residents r
JOIN resident_units ru
ON ru.resident_id = r.id
AND ru.is_deleted = false
JOIN units u
ON u.id = ru.unit_id
AND u.is_deleted = false
WHERE r.user_id = p_user_id
AND r.is_deleted = false
ORDER BY r.is_primary_owner DESC NULLS LAST
LIMIT 1
),
/* 2οΈβ£ Fallback: user-only JSON (non-resident users) */
(
SELECT jsonb_build_object(
'user_id', u.id,
'first_name', u.first_name,
'last_name', u.last_name,
'email', u.email,
'contact_number', u.contact_number
)
FROM users u
WHERE u.id = p_user_id
AND u.is_deleted = false
)
);
$function$
-- Function: get_pre_approved_entries_by_unit_date_range
CREATE OR REPLACE FUNCTION public.get_pre_approved_entries_by_unit_date_range(p_apartment_id uuid, p_unit_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
RETURNS TABLE(id integer, apartment_id uuid, unit_id integer, unit_name text, visitor_id bigint, visitor_name text, possible_visitor_id bigint, possible_visitor_name text, visitor_type_id integer, visitor_type_name text, notes text, is_once boolean, valid_from timestamp without time zone, valid_until timestamp without time zone, pin text, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
LANGUAGE sql
AS $function$
SELECT
pae.id,
pae.apartment_id,
pae.unit_id,
u.name AS unit_name,
pae.visitor_id,
CASE
WHEN v.id IS NOT NULL
THEN concat_ws(' ', v.first_name, v.last_name)
ELSE NULL
END AS visitor_name,
pae.possible_visitor_id,
CASE
WHEN pv.id IS NOT NULL
THEN concat_ws(' ', pv.first_name, pv.last_name)
ELSE NULL
END AS possible_visitor_name,
v.visitor_type_id,
vt.name AS visitor_type_name,
pae.notes,
pae.is_once,
pae.valid_from,
pae.valid_until,
pae.pin,
pae.created_by,
concat_ws(' ',
COALESCE(cu.first_name, null),
COALESCE(cu.last_name, null)
) AS created_by_name,
pae.created_on_utc,
pae.modified_by,
concat_ws(' ',
COALESCE(mu.first_name, null),
COALESCE(mu.last_name, null)
) AS modified_by_name,
pae.modified_on_utc
FROM pre_approved_entries pae
LEFT JOIN units u
ON u.id = pae.unit_id
AND u.is_deleted = false
LEFT JOIN visitors v
ON v.id = pae.visitor_id
AND v.is_deleted = false
LEFT JOIN possible_visitors pv
ON pv.id = pae.possible_visitor_id
AND pv.is_deleted = false
LEFT JOIN visitor_types vt
ON vt.id = v.visitor_type_id
AND vt.is_deleted = false
LEFT JOIN users cu
ON cu.id = pae.created_by
AND cu.is_deleted = false
LEFT JOIN users mu
ON mu.id = pae.modified_by
AND mu.is_deleted = false
WHERE
pae.apartment_id = p_apartment_id
AND pae.unit_id = p_unit_id
AND pae.is_deleted = false
-- OVERLAPPING DATE LOGIC
AND pae.valid_from <= p_end_date
AND COALESCE(pae.valid_until, pae.valid_from) >= p_start_date
ORDER BY
pae.valid_from,
pae.id;
$function$
-- Function: get_fcm_tokens_by_roles
CREATE OR REPLACE FUNCTION public.get_fcm_tokens_by_roles(p_apartment_id uuid, p_role_id integer)
RETURNS TABLE(user_id uuid, device_id text, fcm_token text)
LANGUAGE sql
STABLE
AS $function$
SELECT DISTINCT ON (uft.user_id, uft.device_id)
u.id AS user_id,
uft.device_id,
uft.fcm_token
FROM users u
INNER JOIN fdw_common.user_roles ur
ON ur.user_id = u.id
INNER JOIN user_fcm_tokens uft
ON uft.user_id = u.id
WHERE ur.role_id = p_role_id
AND u.company_id = p_apartment_id
AND u.is_deleted = FALSE
AND uft.fcm_token IS NOT NULL
AND uft.device_id IS NOT NULL;
$function$
-- Function: checkin_service_provider
CREATE OR REPLACE FUNCTION public.checkin_service_provider(p_apartment_id uuid, p_pin text, p_created_by uuid, p_entry_time timestamp without time zone)
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
p_entry_time,
p_entry_time,
p_created_by
)
RETURNING id INTO v_log_id;
RETURN v_log_id;
END;
$function$
-- Function: get_community_home_memory_snapshot
CREATE OR REPLACE FUNCTION public.get_community_home_memory_snapshot(p_apartment_id uuid)
RETURNS TABLE(announcement jsonb, meeting jsonb, decision jsonb, poll jsonb)
LANGUAGE sql
STABLE
AS $function$
WITH params AS (
SELECT
now() AS now_ts,
now() - interval '3 months' AS last_3_months
)
SELECT
/* ------------------------------------------------------------------
1) Recent Announcement (last 3 months)
------------------------------------------------------------------ */
(
SELECT to_jsonb(a)
FROM (
SELECT
n.id,
n.title,
n.created_on_utc AS "created_on_utc"
FROM notices n
CROSS JOIN params p
WHERE n.apartment_id = p_apartment_id
AND n.is_deleted = false
AND n.created_on_utc >= p.last_3_months
ORDER BY n.created_on_utc DESC
LIMIT 1
) a
) AS announcement,
/* ------------------------------------------------------------------
2) Upcoming Meeting (nearest future occurrence)
------------------------------------------------------------------ */
(
SELECT to_jsonb(m)
FROM (
SELECT
e.id,
e.title,
make_timestamp(
EXTRACT(YEAR FROM eo.occurrence_date)::int,
EXTRACT(MONTH FROM eo.occurrence_date)::int,
EXTRACT(DAY FROM eo.occurrence_date)::int,
EXTRACT(HOUR FROM eo.start_time)::int,
EXTRACT(MINUTE FROM eo.start_time)::int,
0
) AS "meeting_date"
FROM events e
JOIN event_occurrences eo
ON eo.event_id = e.id
CROSS JOIN params p
WHERE e.company_id = p_apartment_id
AND e.event_type_id IN (2, 3, 4) -- Meeting, Committee Meeting, AGM
AND e.is_deleted = false
AND eo.is_deleted = false
AND eo.occurrence_date >= CURRENT_DATE
ORDER BY eo.occurrence_date ASC
LIMIT 1
) m
) AS meeting,
/* ------------------------------------------------------------------
3) Latest Decision (most recent)
------------------------------------------------------------------ */
(
SELECT to_jsonb(d)
FROM (
SELECT
d.id,
d.title,
d.created_on_utc AS "created_on_utc"
FROM decisions d
WHERE d.apartment_id = p_apartment_id
AND d.is_deleted = false
ORDER BY d.created_on_utc DESC
LIMIT 1
) d
) AS decision,
/* ------------------------------------------------------------------
4) Active Poll (currently running)
------------------------------------------------------------------ */
(
SELECT to_jsonb(p)
FROM (
SELECT
p.id,
p.title,
p.start_date AS "starts_on_utc",
p.end_date AS "ends_on_utc"
FROM polls p
WHERE p.apartment_id = p_apartment_id
AND p.is_deleted = false
AND p.end_date >= now()
ORDER BY p.end_date ASC
LIMIT 1
) p
) AS poll;
$function$
-- Function: save_visitor_and_approval
CREATE OR REPLACE FUNCTION public.save_visitor_and_approval(p_apartment_id uuid, p_first_name text, p_contact_number text, p_visitor_type_id integer, p_visit_type_id integer, p_start_date date, p_end_date date, p_entry_time time without time zone, p_exit_time time without time zone, p_vehicle_number text, p_company_name text, p_created_by uuid)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id INTEGER;
BEGIN
-- Check if the visitor already exists based on first_name and contact_number
SELECT id INTO v_visitor_id
FROM public.visitors
WHERE first_name = p_first_name
AND contact_number = p_contact_number
AND is_deleted = false;
-- If visitor exists, use the existing visitor_id, else insert a new visitor
IF NOT FOUND THEN
-- Insert into visitors table (auto-generated id will be used)
INSERT INTO public.visitors (
first_name,
contact_number,
visitor_type_id,
created_on_utc,
created_by,
apartment_id
)
VALUES (
p_first_name,
p_contact_number,
p_visitor_type_id,
NOW(),
p_created_by,
p_apartment_id
)
RETURNING id INTO v_visitor_id; -- Capture the auto-generated id
END IF;
-- Insert into visitor_approvals table using the visitor_id
INSERT INTO public.visitor_approvals (
visitor_id,
visit_type_id,
start_date,
end_date,
entry_time,
exit_time,
vehicle_number,
company_name,
created_on_utc,
created_by
)
VALUES (
v_visitor_id,
p_visit_type_id,
COALESCE(p_start_date, NULL), -- Handle NULL for start_date
COALESCE(p_end_date, NULL), -- Handle NULL for end_date
COALESCE(p_entry_time, NULL), -- Handle NULL for entry_time
COALESCE(p_exit_time, NULL), -- Handle NULL for exit_time
COALESCE(p_vehicle_number, NULL), -- Handle NULL for vehicle_number
COALESCE(p_company_name, NULL), -- Handle NULL for company_name
NOW(),
p_created_by
);
END;
$function$
-- Function: take_visitor_action
CREATE OR REPLACE FUNCTION public.take_visitor_action(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_action_status integer)
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, approver_first_name text, approver_last_name text, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits INT;
local_approved_count INT;
local_rejected_count INT;
local_new_visitor_status INT;
v_pending CONSTANT INT := 1;
v_partial_approved CONSTANT INT := 2;
v_approved CONSTANT INT := 3;
v_rejected CONSTANT INT := 7;
BEGIN
/*
* 1. Update unit-level status (approve / reject)
*/
UPDATE public.multi_unit_visits AS muv
SET visitor_statuses = p_action_status,
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
modified_by = p_resident_user
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.unit_id = p_unit_id
AND muv.is_deleted = FALSE;
IF NOT FOUND THEN
RETURN;
END IF;
/*
* 2. Recalculate unit status counts
*/
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_approved),
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_rejected)
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits AS muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = FALSE;
/*
* 3. Decide parent visitor_logs status
*/
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := v_approved; -- 3 Approved
ELSIF local_approved_count > 0 THEN
local_new_visitor_status := v_partial_approved; -- 2 Partial Approved
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := v_rejected; -- 7 Rejected
ELSE
local_new_visitor_status := v_pending; -- 1 Pending
END IF;
/*
* 4. Update visitor_logs parent status
*/
UPDATE public.visitor_logs AS vl
SET
visitor_status_id = local_new_visitor_status,
entry_time = CASE
WHEN local_new_visitor_status = v_approved
AND vl.entry_time IS NULL
--THEN (NOW() AT TIME ZONE '+05:30')::time
THEN (NOW() AT TIME ZONE 'Asia/Kolkata')
ELSE vl.entry_time
END,
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
modified_by = p_resident_user
WHERE vl.id = p_visitor_log_id;
/*
* 5. Return response with approver details
*/
RETURN QUERY
SELECT
p_visitor_log_id,
p_unit_id,
r.id,
r.first_name,
r.last_name,
local_new_visitor_status
FROM public.residents r
WHERE r.user_id = p_resident_user
AND r.is_deleted = FALSE;
END;
$function$
-- Function: create_pre_approved_entry_generic
CREATE OR REPLACE FUNCTION public.create_pre_approved_entry_generic(p_apartment_id uuid, p_unit_id integer, p_visitor_type_id integer, p_visitor_id bigint DEFAULT NULL::bigint, p_delivery_company_id integer DEFAULT NULL::integer, p_possible_first_name text DEFAULT NULL::text, p_possible_last_name text DEFAULT NULL::text, p_possible_phone text DEFAULT NULL::text, p_possible_email text DEFAULT NULL::text, p_possible_vehicle text DEFAULT NULL::text, p_possible_notes text DEFAULT NULL::text, p_valid_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_valid_to timestamp without time zone DEFAULT NULL::timestamp without time zone, p_is_once boolean DEFAULT true, p_notes text DEFAULT NULL::text, p_created_by uuid DEFAULT NULL::uuid, p_schedule_rule jsonb DEFAULT NULL::jsonb)
RETURNS TABLE(pre_approved_entry_id integer, first_name text, last_name text, pin text, valid_from timestamp without time zone, valid_to timestamp without time zone, possible_visitor_id bigint, visitor_id bigint, schedule_days_of_week integer[], schedule_start_time text, schedule_end_time text, schedule_max_entries_per_day integer, schedule_validity_in_months integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_delivery_type_id constant int := 2; -- Delivery from visitor_types table
v_now timestamp := (NOW() AT TIME ZONE 'UTC') AT TIME ZONE 'Asia/Kolkata';
v_valid_from timestamp := COALESCE(p_valid_from, NOW());
v_valid_to timestamp := p_valid_to;
v_visitor_id bigint := NULL;
v_possible_visitor_id bigint := NULL;
v_pin text := NULL;
v_attempt int := 0;
v_entry_id int := NULL;
v_days int[] := NULL;
v_start_time time := NULL;
v_end_time time := NULL;
v_max_entries int := NULL;
v_validity_months int := NULL;
v_first_name text := NULL;
v_last_name text := NULL;
BEGIN
-- =======================
-- VALIDATIONS
-- =======================
IF p_apartment_id IS NULL THEN
RAISE EXCEPTION 'ApartmentId is required';
END IF;
IF p_unit_id IS NULL OR p_unit_id <= 0 THEN
RAISE EXCEPTION 'UnitId must be > 0';
END IF;
IF p_created_by IS NULL THEN
RAISE EXCEPTION 'CreatedBy is required';
END IF;
-- normalize visitor id (EF code treats 0 as null)
IF p_visitor_id = 0 THEN
p_visitor_id := NULL;
END IF;
v_visitor_id := p_visitor_id;
-- One-at-a-time per apartment for PIN generation & inserts
PERFORM pg_advisory_xact_lock(hashtext(p_apartment_id::text));
-- =======================
-- STEP 1: Resolve VisitorId via phone
-- =======================
IF v_visitor_id IS NULL AND COALESCE(trim(p_possible_phone), '') <> '' THEN
SELECT vp.visitor_id::bigint
INTO v_visitor_id
FROM public.visitor_phones vp
WHERE vp.phone_number = p_possible_phone
AND vp.is_active = true
ORDER BY vp.id DESC
LIMIT 1;
END IF;
-- =======================
-- STEP 2: Resolve PossibleVisitor via phone
-- =======================
IF v_visitor_id IS NULL AND COALESCE(trim(p_possible_phone), '') <> '' THEN
SELECT pv.id
INTO v_possible_visitor_id
FROM public.possible_visitors pv
WHERE pv.phone_number = p_possible_phone
AND pv.is_deleted = false
ORDER BY pv.id DESC
LIMIT 1;
END IF;
-- =======================
-- STEP 3: Create PossibleVisitor if needed
-- =======================
IF v_visitor_id IS NULL AND v_possible_visitor_id IS NULL THEN
INSERT INTO public.possible_visitors (
first_name,
last_name,
phone_number,
email,
vehicle_number,
notes,
created_on_utc,
created_by,
is_deleted
)
VALUES (
COALESCE(p_possible_first_name, ''),
p_possible_last_name,
p_possible_phone,
p_possible_email,
p_possible_vehicle,
p_possible_notes,
v_now,
p_created_by,
false
)
RETURNING id INTO v_possible_visitor_id;
END IF;
-- =======================
-- STEP 4: Generate Unique PIN
-- Must be unique across:
-- 1) pre_approved_entries
-- 2) service_provider_apartments
-- for the same apartment and overlapping time window
-- =======================
WHILE v_attempt < 10 LOOP
v_attempt := v_attempt + 1;
v_pin := (floor(random() * 900000) + 100000)::int::text;
IF NOT EXISTS (
-- Check visitor pre-approved entries
SELECT 1
FROM public.pre_approved_entries pae
WHERE pae.apartment_id = p_apartment_id
AND pae.is_deleted = false
AND pae.pin = v_pin
AND pae.valid_from <= COALESCE(v_valid_to, 'infinity'::timestamp)
AND COALESCE(pae.valid_until, 'infinity'::timestamp) >= v_valid_from
UNION ALL
-- Check service provider apartment pins
SELECT 1
FROM public.service_provider_apartments spa
WHERE spa.apartment_id = p_apartment_id
AND spa.is_deleted = false
AND spa.is_active = true
AND spa.pin = v_pin
AND spa.valid_from <= COALESCE(v_valid_to, 'infinity'::timestamp)
AND COALESCE(spa.valid_to, 'infinity'::timestamp) >= v_valid_from
) THEN
EXIT;
END IF;
END LOOP;
IF v_pin IS NULL OR v_attempt >= 10 THEN
RAISE EXCEPTION
'Unable to generate a unique PIN for apartment % after % attempts',
p_apartment_id, v_attempt;
END IF;
-- =======================
-- STEP 5: Insert pre_approved_entries
-- =======================
INSERT INTO public.pre_approved_entries (
apartment_id,
unit_id,
notes,
is_once,
valid_from,
valid_until,
created_by,
created_on_utc,
is_deleted,
possible_visitor_id,
visitor_id,
pin
)
VALUES (
p_apartment_id,
p_unit_id,
p_notes,
COALESCE(p_is_once, true),
v_valid_from,
v_valid_to,
p_created_by,
v_now,
false,
v_possible_visitor_id,
v_visitor_id,
v_pin
)
RETURNING id INTO v_entry_id;
-- =======================
-- STEP 6: Delivery company mapping (Delivery = 2)
-- Now supports BOTH visitor_id and possible_visitor_id
-- =======================
IF p_visitor_type_id = v_delivery_type_id
AND p_delivery_company_id IS NOT NULL
AND (v_visitor_id IS NOT NULL OR v_possible_visitor_id IS NOT NULL) THEN
INSERT INTO public.visitor_delivery_company (
visitor_id,
possible_visitor_id,
delivery_company_id,
is_active,
valid_from,
valid_to
)
VALUES (
v_visitor_id,
v_possible_visitor_id,
p_delivery_company_id,
true,
NOW(),
NULL
);
END IF;
-- =======================
-- STEP 7: Schedule rule insert (optional)
-- =======================
IF p_schedule_rule IS NOT NULL THEN
-- Parse JSON safely
-- daysOfWeek (int array)
IF (p_schedule_rule ? 'daysOfWeek') THEN
SELECT COALESCE(array_agg(value::int), NULL)
INTO v_days
FROM jsonb_array_elements_text(p_schedule_rule->'daysOfWeek') AS t(value);
END IF;
-- startTime / endTime as time
IF (p_schedule_rule ? 'startTime') THEN
v_start_time := NULLIF(p_schedule_rule->>'startTime', '')::time;
END IF;
IF (p_schedule_rule ? 'endTime') THEN
v_end_time := NULLIF(p_schedule_rule->>'endTime', '')::time;
END IF;
-- maxEntriesPerDay default 1
v_max_entries :=
COALESCE(NULLIF(p_schedule_rule->>'maxEntriesPerDay', '')::int, 1);
-- validityInMonths nullable
IF (p_schedule_rule ? 'validityInMonths') THEN
v_validity_months := NULLIF(p_schedule_rule->>'validityInMonths', '')::int;
END IF;
INSERT INTO public.pre_approved_schedule_rules (
pre_approved_entry_id,
days_of_week,
start_time,
end_time,
max_entries_per_day,
validity_in_months
)
VALUES (
v_entry_id,
v_days,
v_start_time,
v_end_time,
v_max_entries,
v_validity_months
);
END IF;
-- =======================
-- STEP 8: Resolve names
-- =======================
IF v_visitor_id IS NOT NULL THEN
SELECT v.first_name, v.last_name
INTO v_first_name, v_last_name
FROM public.visitors v
WHERE v.id = v_visitor_id
AND v.is_deleted = false
LIMIT 1;
ELSIF v_possible_visitor_id IS NOT NULL THEN
SELECT pv.first_name, pv.last_name
INTO v_first_name, v_last_name
FROM public.possible_visitors pv
WHERE pv.id = v_possible_visitor_id
AND pv.is_deleted = false
LIMIT 1;
END IF;
-- =======================
-- RETURN
-- =======================
pre_approved_entry_id := v_entry_id;
first_name := v_first_name;
last_name := v_last_name;
pin := v_pin;
valid_from := v_valid_from;
valid_to := v_valid_to;
possible_visitor_id := v_possible_visitor_id;
visitor_id := v_visitor_id;
schedule_days_of_week := v_days;
schedule_start_time := CASE WHEN v_start_time IS NULL THEN NULL ELSE to_char(v_start_time, 'HH24:MI') END;
schedule_end_time := CASE WHEN v_end_time IS NULL THEN NULL ELSE to_char(v_end_time, 'HH24:MI') END;
schedule_max_entries_per_day := v_max_entries;
schedule_validity_in_months := v_validity_months;
RETURN NEXT;
END;
$function$
-- Function: visitor_bulk_check_out
CREATE OR REPLACE FUNCTION public.visitor_bulk_check_out(p_visitor_log_ids bigint[])
RETURNS SETOF visitor_logs
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
UPDATE public.visitor_logs
SET
exit_time = (NOW() AT TIME ZONE 'Asia/Kolkata'),
visitor_status_id = 5, -- Checked Out
created_on_utc = created_on_utc -- no-op, keeps row shape stable
WHERE id = ANY(p_visitor_log_ids)
RETURNING *;
END;
$function$
-- Function: verify_user_password
CREATE OR REPLACE FUNCTION public.verify_user_password(p_user_name text, p_password text)
RETURNS uuid
LANGUAGE plpgsql
AS $function$
DECLARE
v_user_id UUID;
BEGIN
SELECT id
INTO v_user_id
FROM users
WHERE user_name = p_user_name
AND is_deleted = false
AND password_hash = crypt(p_password, password_hash);
RETURN v_user_id;
END;
$function$
-- Function: get_community_memory_snapshot
CREATE OR REPLACE FUNCTION public.get_community_memory_snapshot(p_apartment_id uuid)
RETURNS TABLE(announcement jsonb, meeting jsonb, decision jsonb, poll jsonb)
LANGUAGE plpgsql
STABLE
AS $function$
BEGIN
RETURN QUERY
SELECT
(
SELECT to_jsonb(a)
FROM notices a
WHERE a.apartment_id = p_apartment_id
AND a.created_on_utc >= now() - interval '3 months'
AND a.is_deleted = false
ORDER BY a.created_on_utc DESC
LIMIT 1
) AS announcement,
(
SELECT to_jsonb(m)
FROM meetings m
WHERE m.apartment_id = p_apartment_id
AND m.meeting_date >= now()
AND m.is_cancelled = false
ORDER BY m.meeting_date ASC
LIMIT 1
) AS meeting,
(
SELECT to_jsonb(d)
FROM decisions d
WHERE d.apartment_id = p_apartment_id
AND d.decision_date >= now() - interval '3 months'
AND d.is_deleted = false
ORDER BY d.decision_date DESC
LIMIT 1
) AS decision,
(
SELECT to_jsonb(p)
FROM polls p
WHERE p.apartment_id = p_apartment_id
AND p.start_date <= now()
AND p.end_date >= now()
AND p.is_deleted = false
ORDER BY p.end_date ASC
LIMIT 1
) AS poll;
END;
$function$
-- Function: get_service_provider_app_context
CREATE OR REPLACE FUNCTION public.get_service_provider_app_context(p_user_id uuid, p_apartment_id uuid)
RETURNS TABLE(service_provider_id integer, role text, category jsonb, verified boolean, onboarded_on timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
sp.id AS service_provider_id,
'Service Provider'::text AS role,
jsonb_build_object(
'id', sp.service_provider_sub_type_id,
'name', sps.name
) AS category,
sp.police_verification_status AS verified,
sp.created_on_utc AS onboarded_on
FROM service_provider_users spu
JOIN service_providers sp
ON sp.id = spu.service_provider_id
AND sp.is_deleted = false
JOIN service_provider_sub_types sps
ON sps.id = sp.service_provider_sub_type_id
JOIN service_provider_apartments spa
ON spa.service_provider_id = sp.id
AND spa.apartment_id = p_apartment_id
AND spa.is_deleted = false
AND spa.is_active = true
WHERE spu.user_id = p_user_id;
END;
$function$
-- Function: get_facility_manager_app_context
CREATE OR REPLACE FUNCTION public.get_facility_manager_app_context(p_user_id uuid, p_apartment_id uuid)
RETURNS TABLE(user_id uuid, role text, apartment_id uuid, apartment_name text, assigned_since timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ur.user_id,
r.name::text AS role,
a.id AS apartment_id,
a.name::text AS apartment_name,
-- Assigned since = role start date (fallback to created_on)
CASE
WHEN ur.start_date > '0001-01-01' THEN ur.start_date
ELSE ur.created_on_utc
END AS assigned_since
FROM fdw_common.user_roles ur
JOIN roles r
ON r.id = ur.role_id
AND r.id = 11 -- Facility Manager
JOIN organizations o
ON o.id = ur.organization_id
JOIN apartments a
ON a.organization_id = o.id
AND a.id = p_apartment_id
WHERE ur.user_id = p_user_id
AND ur.is_deleted = false
AND (ur.end_date = '0001-01-01' OR ur.end_date > now());
END;
$function$
-- Function: get_security_guard_app_context
CREATE OR REPLACE FUNCTION public.get_security_guard_app_context(p_user_id uuid, p_apartment_id uuid)
RETURNS TABLE(user_id uuid, role text, apartment_id uuid, apartment_name text, assigned_gate_ids integer[], active_since timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ur.user_id,
r.name::text AS role,
a.id AS apartment_id,
a.name AS apartment_name,
-- FUTURE: security_guard_gates
-- CURRENT: return empty array safely
ARRAY[]::int[] AS assigned_gate_ids,
sga.valid_from
FROM fdw_common.user_roles ur
JOIN roles r
ON r.id = ur.role_id
AND r.name = 'Security Guard'
JOIN public.security_guard_apartments sga
ON sga.user_id = ur.user_id
AND sga.apartment_id = p_apartment_id
AND sga.is_active = true
AND sga.is_deleted = false
JOIN apartments a
ON a.id = p_apartment_id
WHERE ur.user_id = p_user_id
AND ur.is_deleted = false;
END;
$function$
-- Function: postgres_fdw_handler
CREATE OR REPLACE FUNCTION public.postgres_fdw_handler()
RETURNS fdw_handler
LANGUAGE c
STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_handler$function$
-- Function: postgres_fdw_validator
CREATE OR REPLACE FUNCTION public.postgres_fdw_validator(text[], oid)
RETURNS void
LANGUAGE c
STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_validator$function$
-- Function: postgres_fdw_disconnect
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect(text)
RETURNS boolean
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect$function$
-- Function: postgres_fdw_disconnect_all
CREATE OR REPLACE FUNCTION public.postgres_fdw_disconnect_all()
RETURNS boolean
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_disconnect_all$function$
-- Function: postgres_fdw_get_connections
CREATE OR REPLACE FUNCTION public.postgres_fdw_get_connections(check_conn boolean DEFAULT false, OUT server_name text, OUT user_name text, OUT valid boolean, OUT used_in_xact boolean, OUT closed boolean, OUT remote_backend_pid integer)
RETURNS SETOF record
LANGUAGE c
PARALLEL RESTRICTED STRICT
AS '$libdir/postgres_fdw', $function$postgres_fdw_get_connections_1_2$function$
-- Function: get_service_provider_categories
CREATE OR REPLACE FUNCTION public.get_service_provider_categories()
RETURNS TABLE(id integer, name text)
LANGUAGE sql
AS $function$
SELECT spc.id, spc.name
FROM public.service_provider_company_categories spc;
$function$
-- Function: get_security_guard_fcm_tokens
CREATE OR REPLACE FUNCTION public.get_security_guard_fcm_tokens(p_apartment_id uuid)
RETURNS TABLE(user_id uuid, device_id text, fcm_token text)
LANGUAGE sql
STABLE
AS $function$
SELECT DISTINCT ON (uft.user_id, uft.device_id)
u.id AS user_id,
uft.device_id,
uft.fcm_token
FROM users u
INNER JOIN fdw_common.user_roles ur
ON ur.user_id = u.id
INNER JOIN user_fcm_tokens uft
ON uft.user_id = u.id
WHERE ur.role_id = 7
AND u.company_id = p_apartment_id
AND u.is_deleted = FALSE
AND uft.fcm_token IS NOT NULL
AND uft.device_id IS NOT NULL;
$function$
-- Function: get_community_feed
CREATE OR REPLACE FUNCTION public.get_community_feed(p_apartment_id uuid, p_unit_id integer, p_resident_id integer)
RETURNS TABLE(type text, priority text, occurred_on timestamp without time zone, payload jsonb)
LANGUAGE sql
STABLE
AS $function$
/* ------------------------------------------------------------------
π’ Announcements
------------------------------------------------------------------ */
(
SELECT
'Announcement',
'High',
n.created_on_utc,
jsonb_build_object(
'id', n.id,
'title', n.title,
'summary', n.short_description,
'createdBy', u.first_name || ' ' || u.last_name
)
FROM notices n
JOIN users u ON u.id = n.created_by
WHERE n.apartment_id = p_apartment_id
AND n.is_deleted = false
AND u.is_deleted = false
ORDER BY n.created_on_utc DESC
LIMIT 1
)
/* ------------------------------------------------------------------
π³ Decisions
------------------------------------------------------------------ */
UNION ALL
(
SELECT
'Decision',
'Medium',
d.created_on_utc,
jsonb_build_object(
'id', d.id,
'title', d.title,
'description', d.description,
'decisionOutcome', deo.name,
'createdBy', u.first_name || ' ' || u.last_name
)
FROM decisions d
JOIN decision_outcomes deo ON deo.id = d.decision_outcome_id
JOIN users u ON u.id = d.created_by
WHERE d.apartment_id = p_apartment_id
AND d.is_deleted = false
AND u.is_deleted = false
ORDER BY d.created_on_utc DESC
LIMIT 2
)
/* ------------------------------------------------------------------
π Polls (CORRECT & DERIVED)
------------------------------------------------------------------ */
UNION ALL
(
WITH vote_stats AS (
SELECT
pv.poll_id,
COUNT(*) AS total_votes
FROM poll_votes pv
WHERE pv.is_deleted = false
GROUP BY pv.poll_id
),
option_stats AS (
SELECT
pv.poll_option_id,
COUNT(*) AS votes
FROM poll_votes pv
WHERE pv.is_deleted = false
GROUP BY pv.poll_option_id
),
possible_voters AS (
SELECT
p.id AS poll_id,
CASE
WHEN p.poll_eligibility_type_id = 1 THEN (
SELECT COUNT(*)
FROM units u
WHERE u.apartment_id = p_apartment_id
AND u.unit_type_id = 1
AND u.is_deleted = false
)
WHEN p.poll_eligibility_type_id = 2 THEN (
SELECT COUNT(*)
FROM residents r
WHERE r.apartment_id = p_apartment_id
AND r.is_deleted = false
)
ELSE 0
END AS total_possible
FROM polls p
WHERE p.apartment_id = p_apartment_id
)
SELECT
'Poll',
'Medium',
p.start_date,
jsonb_build_object(
'id', p.id,
'title', p.title,
'status', CASE
WHEN p.start_date > now() THEN 'Not Started'
WHEN p.end_date < now() THEN 'Closed'
ELSE 'Ongoing'
END,
'endsAt', p.end_date,
'hasVoted',
EXISTS (
SELECT 1
FROM poll_votes pv
WHERE pv.poll_id = p.id
AND pv.is_deleted = false
AND (
(p.poll_eligibility_type_id = 1 AND pv.unit_id = p_unit_id)
OR (p.poll_eligibility_type_id = 2 AND pv.resident_id = p_resident_id)
)
),
'selectedOptionId',
(
SELECT pv.poll_option_id
FROM poll_votes pv
WHERE pv.poll_id = p.id
AND pv.is_deleted = false
AND (
(p.poll_eligibility_type_id = 1 AND pv.unit_id = p_unit_id)
OR (p.poll_eligibility_type_id = 2 AND pv.resident_id = p_resident_id)
)
LIMIT 1
),
'votesCast', COALESCE(vs.total_votes, 0),
'possibleVoters', pv.total_possible,
'participationPercent',
CASE
WHEN pv.total_possible = 0 THEN 0
ELSE ROUND((COALESCE(vs.total_votes, 0) * 100.0) / pv.total_possible, 1)
END,
'options',
COALESCE(
(
SELECT jsonb_agg(
jsonb_build_object(
'id', po.id,
'text', po.option_text,
'voteCount', COALESCE(os.votes, 0),
'percentage',
CASE
WHEN COALESCE(vs.total_votes, 0) = 0 THEN 0
ELSE ROUND(
(COALESCE(os.votes, 0) * 100.0)
/ COALESCE(vs.total_votes, 0),
1
)
END
)
ORDER BY po.id
)
FROM poll_options po
LEFT JOIN option_stats os ON os.poll_option_id = po.id
WHERE po.poll_id = p.id
),
'[]'::jsonb
)
)
FROM polls p
LEFT JOIN vote_stats vs ON vs.poll_id = p.id
LEFT JOIN possible_voters pv ON pv.poll_id = p.id
WHERE p.apartment_id = p_apartment_id
AND p.is_deleted = false
ORDER BY p.created_on_utc DESC
LIMIT 1
)
/* ------------------------------------------------------------------
π
Meetings
------------------------------------------------------------------ */
UNION ALL
(
SELECT
'Meeting',
'High',
e.start_time,
jsonb_build_object(
'id', e.id,
'title', e.title,
'description', e.description,
'startsAt', e.start_time,
'endsAt', e.end_time,
'isLive', (e.start_time <= now() AND now() <= e.end_time)
)
FROM events e
WHERE e.company_id = p_apartment_id
AND e.is_deleted = false
ORDER BY e.created_on_utc DESC
LIMIT 1
);
$function$
-- Function: get_meeting_data
CREATE OR REPLACE FUNCTION public.get_meeting_data(p_event_uuid uuid, p_occ_date date)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
v_agenda jsonb;
v_participants jsonb;
v_actions jsonb;
v_notes jsonb;
BEGIN
-- Get agenda items, or empty array if no data
SELECT COALESCE(
jsonb_agg(
jsonb_build_object(
'id', id,
'item_text', item_text,
'order_no', order_no)
), '[]'::jsonb
)
INTO v_agenda
FROM meeting_agenda_items
WHERE occurrence_id = (
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
) AND is_deleted = false;
-- Get participants, or empty array if no data
SELECT COALESCE(
jsonb_agg(
jsonb_build_object(
'id', mp.id,
'user_id', user_id,
'user_name', CONCAT(u.first_name, ' ', u.last_name) -- Concatenate first and last name
)
), '[]'::jsonb
)
INTO v_participants
FROM meeting_participants mp
JOIN users u ON mp.user_id = u.id -- Join with the users table to get first_name and last_name
WHERE occurrence_id = (
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
) AND mp.is_deleted = false;
-- Get actions, or empty array if no data
SELECT COALESCE(
jsonb_agg(
jsonb_build_object(
'id', mat.id,
'action_description', action_description,
'assigned_to_user_id', assigned_to_user_id,
'assigned_to_user_name', CONCAT(u.first_name, ' ', u.last_name),
'due_date', due_date,
'status', status
)
), '[]'::jsonb
)
INTO v_actions
FROM meeting_action_items mat
JOIN users u ON mat.assigned_to_user_id = u.id -- Join with the users table to get first_name and last_name
WHERE occurrence_id = (
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
) AND mat.is_deleted = false;
-- Get notes
SELECT jsonb_agg(
jsonb_build_object(
'note_text', note_text
)
)
INTO v_notes
FROM meeting_notes
WHERE occurrence_id = (
SELECT id FROM event_occurrences WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date
) AND is_deleted = false;
-- Return all data in a single JSON object
RETURN jsonb_build_object(
'agenda', v_agenda,
'participants', v_participants,
'actions', v_actions,
'notes', v_notes
);
END;
$function$
-- Function: create_poll_vote
CREATE OR REPLACE FUNCTION public.create_poll_vote(p_poll_id integer, p_unit_id integer, p_resident_id integer, p_poll_option_id integer, p_vote_comment text)
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
-- 1οΈβ£ Validate required identifiers
IF p_unit_id IS NULL OR p_resident_id IS NULL THEN
RAISE EXCEPTION 'Both unitId and residentId are required';
END IF;
-- 2οΈβ£ Validate poll active
IF NOT EXISTS (
SELECT 1
FROM polls
WHERE id = p_poll_id
AND is_deleted = false
AND start_date <= now()
AND end_date >= now()
) THEN
RAISE EXCEPTION 'Poll is not active';
END IF;
-- 3οΈβ£ Validate option
IF NOT EXISTS (
SELECT 1
FROM poll_options
WHERE id = p_poll_option_id
AND poll_id = p_poll_id
) THEN
RAISE EXCEPTION 'Invalid poll option';
END IF;
-- 4οΈβ£ Insert vote (unit-based)
INSERT INTO poll_votes (
poll_id,
unit_id,
resident_id,
poll_option_id,
vote_comment,
vote_date,
created_on_utc,
is_deleted
)
VALUES (
p_poll_id,
p_unit_id,
p_resident_id,
p_poll_option_id,
p_vote_comment,
now(),
now(),
false
)
ON CONFLICT ON CONSTRAINT uq_poll_votes_unit_per_poll
DO NOTHING;
END;
$function$
-- Function: create_poll_with_options
CREATE OR REPLACE FUNCTION public.create_poll_with_options(p_apartment_id uuid, p_title character varying, p_description text, p_poll_type_id integer, p_poll_eligibility_type_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone, p_created_by uuid, p_options jsonb)
RETURNS TABLE(poll_id integer, options_count integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_poll_id int;
v_option jsonb;
BEGIN
-- 1οΈβ£ Validate date range
IF p_end_date <= p_start_date THEN
RAISE EXCEPTION 'Poll end date must be greater than start date';
END IF;
-- 2οΈβ£ Validate poll type
IF NOT EXISTS (
SELECT 1 FROM poll_types WHERE id = p_poll_type_id
) THEN
RAISE EXCEPTION 'Invalid poll_type_id: %', p_poll_type_id;
END IF;
-- 3οΈβ£ Validate eligibility type
IF NOT EXISTS (
SELECT 1 FROM poll_eligibility_types WHERE id = p_poll_eligibility_type_id
) THEN
RAISE EXCEPTION 'Invalid poll_eligibility_type_id: %', p_poll_eligibility_type_id;
END IF;
-- 4οΈβ£ Create poll
INSERT INTO polls (
apartment_id,
title,
description,
poll_type_id,
poll_eligibility_type_id,
status_id,
start_date,
end_date,
created_on_utc,
created_by,
is_deleted
)
VALUES (
p_apartment_id,
p_title,
p_description,
p_poll_type_id,
p_poll_eligibility_type_id,
1, -- Draft / Active (adjust if needed)
p_start_date,
p_end_date,
now(),
p_created_by,
false
)
RETURNING id INTO v_poll_id;
-- 5οΈβ£ Insert options
FOR v_option IN
SELECT * FROM jsonb_array_elements(p_options)
LOOP
INSERT INTO poll_options (
poll_id,
option_text,
option_type_id
)
VALUES (
v_poll_id,
v_option ->> 'text',
1
);
END LOOP;
-- 6οΈβ£ Return
RETURN QUERY
SELECT
v_poll_id,
jsonb_array_length(p_options);
END;
$function$
-- Function: update_visitor_status
CREATE OR REPLACE FUNCTION public.update_visitor_status(p_visitor_log_id integer, p_unit_id integer, p_visitor_status_id integer, p_modified_by uuid, p_is_manual_approval boolean DEFAULT false, p_approval_source integer DEFAULT 1, p_approval_reason integer DEFAULT 0, p_approved_by_resident_id integer DEFAULT NULL::integer)
RETURNS TABLE(visitor_id integer, total_visits_count integer, approved_visits_count integer, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits int;
local_approved_count int;
local_rejected_count int; -- Added to track rejections
local_new_visitor_status int;
BEGIN
-- Step 1: Update the status for the specific unit
UPDATE public.multi_unit_visits
SET visitor_statuses = p_visitor_status_id,
is_manual_approval = COALESCE(p_is_manual_approval, false),
approval_source = COALESCE(p_approval_source, 1),
approval_reason = COALESCE(p_approval_reason, 0),
approved_by_resident_id = p_approved_by_resident_id,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE visitor_log_id = p_visitor_log_id
AND unit_id = p_unit_id
AND is_deleted = false;
-- Step 2: Check if the update was successful.
IF NOT FOUND THEN
RETURN;
END IF;
-- Step 3: Recalculate the overall visitor log status.
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits AS muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = false;
-- Step 4: Determine the new overall status with corrected logic.
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := 2; -- Rule 1: All units have approved.
ELSIF local_approved_count > 0 THEN
-- FIX: If at least one unit approves, the status is "Partial Approved",
-- even if another unit rejects.
local_new_visitor_status := 7; -- Rule 2: At least one approval, but not all.
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := 6; -- Rule 3: No approvals yet, but at least one rejection.
ELSE
local_new_visitor_status := 1; -- Rule 4: No approvals and no rejections yet.
END IF;
-- Step 5: Update the parent visitor_logs table.
UPDATE public.visitor_logs
SET visitor_status_id = local_new_visitor_status,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = p_visitor_log_id;
-- Step 6: On success, return the calculated values.
RETURN QUERY SELECT p_visitor_log_id, local_total_visits, local_approved_count, local_new_visitor_status;
END;
$function$
-- Function: get_unit_visitors
CREATE OR REPLACE FUNCTION public.get_unit_visitors(p_unit_id integer, p_for_date date DEFAULT CURRENT_DATE)
RETURNS TABLE(id integer, source text, visitor_id bigint, possible_visitor_id bigint, pre_approved_entry_id integer, unit_id integer, visitor_type_id integer, visitor_type text, visitor_name text, phone_number text, vehicle_number text, entry_time timestamp without time zone, exit_time timestamp without time zone, expected_from timestamp without time zone, expected_until timestamp without time zone, visitor_status_id integer, visitor_status_name text)
LANGUAGE sql
STABLE
AS $function$
WITH unit_ctx AS (
SELECT
u.id AS unit_id,
u.apartment_id AS apartment_id
FROM units u
WHERE u.id = p_unit_id
AND u.is_deleted = false
)
SELECT
ROW_NUMBER() OVER (
ORDER BY
entry_time DESC NULLS LAST,
expected_from DESC NULLS LAST
)::int AS id,
q.*
FROM (
/* ===============================
1οΈβ£ ACTUAL VISITS (LOG)
=============================== */
SELECT
'LOG' AS source,
v.id AS visitor_id,
NULL::bigint AS possible_visitor_id,
vl.pre_approved_entry_id,
muv.unit_id,
v.visitor_type_id,
vt.name AS visitor_type,
concat_ws(' ', v.first_name, v.last_name) AS visitor_name,
NULL::text AS phone_number,
vl.vehicle_number,
vl.entry_time,
vl.exit_time,
NULL::timestamp AS expected_from,
NULL::timestamp AS expected_until,
muv.visitor_statuses AS visitor_status_id,
vs.name AS visitor_status_name
FROM unit_ctx uc
JOIN visitor_logs vl
ON vl.apartment_id = uc.apartment_id
Join visitor_types vt
On vl.visitor_type_id = vt.id
JOIN visitors v
ON v.id = vl.visitor_id
AND v.is_deleted = false
JOIN multi_unit_visits muv
ON muv.visitor_log_id = vl.id
AND muv.is_deleted = false
AND muv.unit_id = uc.unit_id
JOIN visitor_statuses vs
ON vs.id = muv.visitor_statuses
AND vs.is_deleted = false
WHERE
DATE(vl.created_on_utc) = p_for_date
UNION ALL
/* ===============================
2οΈβ£ EXPECTED VISITORS (PRE_APPROVED)
=============================== */
SELECT
'PRE_APPROVED' AS source,
pv2.id AS visitor_id, -- optional (known visitor)
pv.id AS possible_visitor_id,
pae.id AS pre_approved_entry_id,
pae.unit_id,
pv.visitor_type_id,
vt.name AS visitor_type,
concat_ws(' ', pv.first_name, pv.last_name) AS visitor_name,
pv.phone_number,
pv.vehicle_number,
NULL::timestamp AS entry_time,
NULL::timestamp AS exit_time,
pae.valid_from AS expected_from,
pae.valid_until AS expected_until,
8 AS visitor_status_id, -- virtual EXPECTED
'Expected' AS visitor_status_name
FROM unit_ctx uc
JOIN pre_approved_entries pae
ON pae.unit_id = uc.unit_id
AND pae.apartment_id = uc.apartment_id
AND pae.is_deleted = false
JOIN possible_visitors pv
ON pv.id = pae.possible_visitor_id
AND pv.is_deleted = false
Join visitor_types vt
On pv.visitor_type_id = vt.id
LEFT JOIN visitors pv2
ON pv2.id = pae.visitor_id
AND pv2.is_deleted = false
WHERE
pae.valid_from::date <= p_for_date
AND (pae.valid_until IS NULL OR pae.valid_until::date >= p_for_date)
) q;
$function$
-- Function: get_visitors_inside_with_units
CREATE OR REPLACE FUNCTION public.get_visitors_inside_with_units(p_apartment_id uuid, p_today_only boolean DEFAULT false)
RETURNS TABLE(id integer, visitor_log_id bigint, visitor_id bigint, first_name text, last_name text, visitor_type_id integer, visitor_type_name text, visiting_units jsonb, entry_time timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
CAST(row_number() OVER (ORDER BY vl.entry_time DESC) AS integer) AS id,
vl.id AS visitor_log_id,
v.id AS visitor_id,
v.first_name,
v.last_name,
v.visitor_type_id,
vt.name::text AS visitor_type_name,
COALESCE(
jsonb_agg(
jsonb_build_object(
'unitId', u.id,
'unitName', u.name
)
ORDER BY u.id
) FILTER (WHERE u.id IS NOT NULL),
'[]'::jsonb
) AS visiting_units,
vl.entry_time
FROM
visitor_logs vl
JOIN
visitors v ON v.id = vl.visitor_id
JOIN
visitor_types vt ON vt.id = vl.visitor_type_id
LEFT JOIN
multi_unit_visits muv
ON muv.visitor_log_id = vl.id
AND muv.is_deleted = FALSE
LEFT JOIN
units u
ON u.id = muv.unit_id
AND u.is_deleted = FALSE
And u.apartment_id = p_apartment_id
WHERE
vl.apartment_id = p_apartment_id
AND vl.exit_time IS NULL
AND v.is_deleted = FALSE
AND vt.is_deleted = FALSE
--AND (p_today_only = FALSE OR vl.entry_time::date = CURRENT_DATE)
AND (NOT p_today_only OR vl.entry_time::date = CURRENT_DATE)
GROUP BY
vl.id,
v.id,
v.first_name,
v.last_name,
v.visitor_type_id,
vt.name,
vl.entry_time
ORDER BY
vl.entry_time DESC;
END;
$function$
-- Function: get_visitors_inside_counts_by_type
CREATE OR REPLACE FUNCTION public.get_visitors_inside_counts_by_type(p_apartment_id uuid, p_today_only boolean)
RETURNS TABLE(id integer, visitor_type_id integer, visitor_type_name text, count integer)
LANGUAGE sql
AS $function$
SELECT
ROW_NUMBER() OVER (ORDER BY vt.id)::integer AS id,
vt.id AS visitor_type_id,
vt.name::text AS visitor_type_name,
COUNT(*) AS count
FROM visitor_logs vl
JOIN visitor_types vt ON vt.id = vl.visitor_type_id
JOIN visitors v ON v.id = vl.visitor_id
WHERE
vl.apartment_id = p_apartment_id
AND vl.exit_time IS NULL
AND (NOT p_today_only OR vl.entry_time::date = CURRENT_DATE)
AND v.is_deleted = FALSE
AND vt.is_deleted = FALSE
GROUP BY
vt.id,
vt.name
ORDER BY
vt.id;
$function$
-- Function: get_unit_related_details
CREATE OR REPLACE FUNCTION public.get_unit_related_details(unit_id integer)
RETURNS jsonb
LANGUAGE sql
AS $function$
SELECT jsonb_build_object(
'id', u.id,
'name', u.name,
'unit_type_id', u.unit_type_id,
'phone_extension', u.phone_extention,
'area', u.area,
'bhk_type', u.bhk_type,
'occupant_type_id', u.occupant_type_id,
'occupancy_type_id', u.occupancy_type_id,
'residents', COALESCE(
(SELECT jsonb_agg(DISTINCT jsonb_build_object(
'id', r.id,
'first_name', r.first_name,
'last_name', r.last_name,
'email', r.email,
'contact_number', r.contact_number,
'resident_type', rt.name
))
FROM resident_units ru
JOIN residents r ON r.id = ru.resident_id AND r.is_deleted = false
JOIN resident_types rt ON rt.id = r.resident_type_id AND rt.is_deleted = false
WHERE ru.unit_id = u.id AND ru.is_deleted = false),
'[]'::jsonb),
'vehicles', COALESCE(
(SELECT jsonb_agg(DISTINCT jsonb_build_object(
'id', v.id,
'vehicle_number', v.vehicle_number,
'vehicle_type', vt.name
))
FROM vehicles v
JOIN vehicle_types vt ON vt.id = v.vehicle_type_id AND vt.is_deleted = false
WHERE v.unit_id = u.id AND v.is_deleted = false),
'[]'::jsonb)
)
FROM units u
WHERE u.id = unit_id AND u.is_deleted = false;
$function$
-- Function: approve_visitor_for_unit
CREATE OR REPLACE FUNCTION public.approve_visitor_for_unit(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_resident_id integer)
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits INT;
local_approved_count INT;
local_rejected_count INT;
local_new_visitor_status INT;
BEGIN
-- Step 1: Approve for this unit only
UPDATE public.multi_unit_visits
SET visitor_statuses = 2, -- Approved
modified_on_utc = NOW(),
modified_by = p_resident_user
WHERE visitor_log_id = p_visitor_log_id
AND unit_id = p_unit_id
AND is_deleted = FALSE;
IF NOT FOUND THEN
-- No update happened (invalid visitor_log_id or unit_id)
RETURN;
END IF;
-- Step 2: Recalculate parent visitor log status
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = FALSE;
-- Step 3: Decide new parent status
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := 2; -- All Approved
ELSIF local_approved_count > 0 THEN
local_new_visitor_status := 7; -- Partial Approved
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := 6; -- Rejected
ELSE
local_new_visitor_status := 1; -- Pending
END IF;
UPDATE public.visitor_logs
SET visitor_status_id = local_new_visitor_status,
modified_on_utc = NOW(),
modified_by = p_resident_user
WHERE id = p_visitor_log_id;
-- Step 4: Return minimal information for the service layer
RETURN QUERY
SELECT
p_visitor_log_id,
p_unit_id,
p_resident_id, -- approver resident
local_new_visitor_status;
END;
$function$
-- Function: get_notice_list_by_date_range
CREATE OR REPLACE FUNCTION public.get_notice_list_by_date_range(p_apartment_id uuid, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
RETURNS TABLE(id bigint, title character varying, short_description character varying, category_id integer, category_name character varying, priority_id integer, priority_name character varying, expires_on timestamp without time zone, created_by uuid, created_by_name character varying, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name character varying, modified_on_utc timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
n.id::bigint AS id,
n.title::varchar AS title,
n.short_description::varchar AS short_description,
n.category_id AS category_id,
nc.name::varchar AS category_name,
n.priority_id AS priority_id,
np.name::varchar AS priority_name,
n.expires_on AS expires_on,
n.created_by AS created_by,
(cu.first_name || ' ' || cu.last_name)::varchar AS created_by_name,
n.created_on_utc AS created_on_utc,
n.modified_by AS modified_by,
COALESCE(
(mu.first_name || ' ' || mu.last_name)::varchar,
''
) AS modified_by_name,
n.modified_on_utc AS modified_on_utc
FROM notices n
JOIN notice_categories nc ON n.category_id = nc.id
JOIN notice_priorities np ON n.priority_id = np.id
JOIN users cu ON n.created_by = cu.id
LEFT JOIN users mu ON n.modified_by = mu.id
WHERE
n.is_deleted = false
AND n.apartment_id = p_apartment_id
AND n.expires_on >= p_start_date
AND n.expires_on < p_end_date
ORDER BY n.expires_on DESC, n.id DESC;
END;
$function$
-- Function: get_visitor_multiple_unit_logs
CREATE OR REPLACE FUNCTION public.get_visitor_multiple_unit_logs(v_apartment_id uuid, log_date timestamp without time zone)
RETURNS TABLE(id bigint, visitor_id bigint, first_name text, last_name text, unit_details jsonb, latest_entry_time timestamp without time zone, latest_exit_time timestamp without time zone, contact_number text, visitor_type_id integer, visitor_type_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
vl.id,
vl.visitor_id::BIGINT AS visitor_id,
v.first_name,
v.last_name,
/* units aggregation */
COALESCE(
jsonb_agg(
jsonb_build_object(
'unit_id', muv.unit_id,
'unit_name', u.name
)
) FILTER (WHERE muv.unit_id IS NOT NULL),
'[]'::jsonb
) AS unit_details,
/* entry / exit from log */
vl.entry_time AS latest_entry_time,
vl.exit_time AS latest_exit_time,
/* single phone per visitor (no fan-out, no ambiguity) */
vp.phone_number AS contact_number,
/* visitor type from log */
vl.visitor_type_id,
vt.name::TEXT AS visitor_type_name
FROM visitor_logs vl
/* visitor metadata */
LEFT JOIN visitors v
ON v.id = vl.visitor_id
AND v.is_deleted = FALSE
/* SAFE phone join (fixes ambiguity + fan-out) */
LEFT JOIN LATERAL (
SELECT vp2.phone_number
FROM visitor_phones vp2
WHERE vp2.visitor_id = vl.visitor_id
ORDER BY vp2.id
LIMIT 1
) vp ON true
/* unit mapping */
LEFT JOIN public.multi_unit_visits muv
ON muv.visitor_log_id = vl.id
AND muv.is_deleted = FALSE
LEFT JOIN units u
ON u.id = muv.unit_id
/* type lookup from LOG */
LEFT JOIN visitor_types vt
ON vt.id = vl.visitor_type_id
WHERE
vl.apartment_id = v_apartment_id
AND vl.created_on_utc >= date_trunc('day', log_date)
AND vl.created_on_utc < date_trunc('day', log_date) + interval '1 day'
GROUP BY
vl.id,
vl.visitor_id,
v.first_name,
v.last_name,
vl.entry_time,
vl.exit_time,
vp.phone_number,
vl.visitor_type_id,
vt.name
ORDER BY MAX(vl.created_on_utc) DESC;
END;
$function$
-- Function: get_apartment_visitors
CREATE OR REPLACE FUNCTION public.get_apartment_visitors(p_apartment_id uuid, p_unit_id integer DEFAULT NULL::integer, p_for_date date DEFAULT CURRENT_DATE)
RETURNS TABLE(row_id integer, source text, visitor_id bigint, possible_visitor_id bigint, pre_approved_entry_id integer, unit_id integer, visitor_type_id integer, visitor_name text, phone_number text, vehicle_number text, entry_time timestamp without time zone, exit_time timestamp without time zone, expected_from timestamp without time zone, expected_until timestamp without time zone, visitor_status_id integer, visitor_status_name text)
LANGUAGE sql
STABLE
AS $function$
SELECT
ROW_NUMBER() OVER (ORDER BY
entry_time DESC NULLS LAST,
expected_from DESC NULLS LAST
)::int AS row_id,
q.*
FROM (
/* ===============================
1οΈβ£ ACTUAL VISITS (LOG)
=============================== */
SELECT
'LOG' AS source,
v.id AS visitor_id,
NULL::bigint AS possible_visitor_id,
vl.pre_approved_entry_id,
muv.unit_id,
v.visitor_type_id,
concat_ws(' ', v.first_name, v.last_name) AS visitor_name,
NULL::text AS phone_number,
vl.vehicle_number,
vl.entry_time,
vl.exit_time,
NULL::timestamp AS expected_from,
NULL::timestamp AS expected_until,
muv.visitor_statuses AS visitor_status_id,
vs.name AS visitor_status_name
FROM visitor_logs vl
JOIN visitors v
ON v.id = vl.visitor_id
AND v.is_deleted = false
JOIN multi_unit_visits muv
ON muv.visitor_log_id = vl.id
AND muv.is_deleted = false
JOIN visitor_statuses vs
ON vs.id = muv.visitor_statuses
AND vs.is_deleted = false
WHERE
vl.apartment_id = p_apartment_id
AND DATE(vl.created_on_utc) = p_for_date
AND (p_unit_id IS NULL OR muv.unit_id = p_unit_id)
UNION ALL
/* ===============================
2οΈβ£ EXPECTED VISITORS
=============================== */
SELECT
'PRE_APPROVED' AS source,
pv2.id AS visitor_id,
pv.id AS possible_visitor_id,
pae.id AS pre_approved_entry_id,
pae.unit_id,
pv.visitor_type_id,
concat_ws(' ', pv.first_name, pv.last_name) AS visitor_name,
pv.phone_number,
pv.vehicle_number,
NULL::timestamp AS entry_time,
NULL::timestamp AS exit_time,
pae.valid_from AS expected_from,
pae.valid_until AS expected_until,
8 AS visitor_status_id,
'Expected' AS visitor_status_name
FROM pre_approved_entries pae
JOIN possible_visitors pv
ON pv.id = pae.possible_visitor_id
AND pv.is_deleted = false
LEFT JOIN visitors pv2
ON pv2.id = pae.visitor_id
AND pv2.is_deleted = false
WHERE
pae.apartment_id = p_apartment_id
AND pae.is_deleted = false
AND pae.valid_from::date <= p_for_date
AND (pae.valid_until IS NULL OR pae.valid_until::date >= p_for_date)
AND (p_unit_id IS NULL OR pae.unit_id = p_unit_id)
) q;
$function$
-- Function: take_visitor_action
CREATE OR REPLACE FUNCTION public.take_visitor_action(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid, p_action_status integer, p_approval_reason integer, p_approval_source integer, p_approved_by_resident_id integer, p_is_manual_approval boolean)
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, approver_first_name text, approver_last_name text, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits INT;
local_approved_count INT;
local_rejected_count INT;
local_new_visitor_status INT;
v_pending CONSTANT INT := 1;
v_partial_approved CONSTANT INT := 2;
v_approved CONSTANT INT := 3;
v_rejected CONSTANT INT := 7;
v_checked_in CONSTANT INT := 4;
BEGIN
/*
* 1. Update unit-level status (approve / reject)
*/
UPDATE public.multi_unit_visits AS muv
SET visitor_statuses = p_action_status,
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
modified_by = p_resident_user,
approval_reason = p_approval_reason,
approval_source = p_approval_source,
approved_by_resident_id = p_approved_by_resident_id,
is_manual_approval = p_is_manual_approval
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.unit_id = p_unit_id
AND muv.is_deleted = FALSE;
IF NOT FOUND THEN
RETURN;
END IF;
/*
* 2. Recalculate unit status counts
*/
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_approved),
COUNT(*) FILTER (WHERE muv.visitor_statuses = v_rejected)
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits AS muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = FALSE;
/*
* 3. Decide parent visitor_logs status
*/
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := v_approved; -- 3 Approved
ELSIF local_approved_count > 0 THEN
local_new_visitor_status := v_partial_approved; -- 2 Partial Approved
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := v_rejected; -- 7 Rejected
ELSE
local_new_visitor_status := v_pending; -- 1 Pending
END IF;
/*
* 4. Update visitor_logs parent status
*/
UPDATE public.visitor_logs AS vl
SET
--visitor_status_id = local_new_visitor_status,
visitor_status_id = CASE
WHEN (
(local_new_visitor_status IN (v_approved, v_partial_approved)
AND vl.entry_time IS NULL)
OR COALESCE(p_is_manual_approval, false) = true
)
THEN v_checked_in
ELSE visitor_status_id
END,
entry_time = CASE
WHEN (
(local_new_visitor_status IN (v_approved, v_partial_approved)
AND vl.entry_time IS NULL)
OR COALESCE(p_is_manual_approval, false) = true
)
AND vl.entry_time IS NULL
THEN (NOW() AT TIME ZONE 'Asia/Kolkata')
ELSE vl.entry_time
END,
modified_on_utc = (NOW() AT TIME ZONE 'UTC'),
modified_by = p_resident_user
WHERE vl.id = p_visitor_log_id;
/*
* 5. Return response with approver details
*/
RETURN QUERY
SELECT
p_visitor_log_id,
p_unit_id,
r.id,
r.first_name,
r.last_name,
local_new_visitor_status
FROM public.residents r
WHERE r.user_id = p_resident_user
AND r.is_deleted = FALSE;
END;
$function$
-- Function: create_poll
CREATE OR REPLACE FUNCTION public.create_poll(p_apartment_id uuid, p_title character varying, p_description text, p_poll_type_id integer, p_poll_eligibility_type integer, p_status_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone, p_created_by uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_poll_id int;
BEGIN
/* -----------------------------------------
1οΈβ£ Validate eligibility type
----------------------------------------- */
IF p_poll_eligibility_type NOT IN (1, 2) THEN
RAISE EXCEPTION
'Invalid poll eligibility type %',
p_poll_eligibility_type;
END IF;
/* -----------------------------------------
2οΈβ£ Create poll (NO SNAPSHOT DATA)
----------------------------------------- */
INSERT INTO polls (
apartment_id,
title,
description,
poll_type_id,
poll_eligibility_type_id,
status_id,
start_date,
end_date,
created_on_utc,
created_by,
is_deleted
)
VALUES (
p_apartment_id,
p_title,
p_description,
p_poll_type_id,
p_poll_eligibility_type,
p_status_id,
p_start_date,
p_end_date,
(NOW() AT TIME ZONE 'UTC'),
p_created_by,
false
)
RETURNING id INTO v_poll_id;
RETURN v_poll_id;
END;
$function$
-- Function: get_visitors_by_unit_and_date_range
CREATE OR REPLACE FUNCTION public.get_visitors_by_unit_and_date_range(p_apartment_id uuid, p_unit_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
RETURNS TABLE(id bigint, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_status_id integer, visitor_status text, first_name text, last_name text, image_url text, email text, visiting_from text, contact_number text, visitor_type_id integer, visitor_type text, vehicle_number text, identity_type_id integer, identity_type text, identity_number text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
vl.id AS id,
vl.entry_time AS entry_time,
vl.exit_time AS exit_time,
vl.visitor_status_id AS visitor_status_id,
vs.name::text AS visitor_status,
v.first_name::text,
v.last_name::text,
COALESCE(v.image_url, '')::text AS image_url,
ve.email::text,
vd.visiting_from::text,
vp.phone_number::text AS contact_number,
vl.visitor_type_id AS visitor_type_id,
vt.name::text AS visitor_type, -- β
FIX
COALESCE(vd.vehicle_number, vl.vehicle_number)::text AS vehicle_number,
vi.identity_type_id,
it.name::text AS identity_type, -- β
FIX
vi.identity_number::text,
v.created_by,
v.created_on_utc,
v.modified_by,
v.modified_on_utc
FROM multi_unit_visits muv
JOIN visitor_logs vl
ON vl.id = muv.visitor_log_id
JOIN visitors v
ON v.id = vl.visitor_id
AND v.is_deleted = false
JOIN visitor_types vt
ON vt.id = vl.visitor_type_id
AND vt.is_deleted = false
JOIN visitor_statuses vs
ON vs.id = vl.visitor_status_id
AND vs.is_deleted = false
JOIN units u
ON u.id = muv.unit_id
AND u.apartment_id = p_apartment_id
AND u.is_deleted = false
-- OPTIONAL DATA (SAFE LEFT JOINS)
LEFT JOIN visitor_details vd
ON vd.visitor_id = v.id
LEFT JOIN visitor_emails ve
ON ve.visitor_id = v.id
AND ve.is_active = true
LEFT JOIN visitor_phones vp
ON vp.visitor_id = v.id
AND vp.is_active = true
LEFT JOIN visitor_identities vi
ON vi.visitor_id = v.id
AND vi.is_active = true
LEFT JOIN identity_types it
ON it.id = vi.identity_type_id
AND it.is_deleted = false
WHERE
muv.unit_id = p_unit_id
AND muv.is_deleted = false
AND (
(vl.entry_time IS NOT NULL AND vl.entry_time >= p_start_date AND vl.entry_time < p_end_date)
OR
(vl.entry_time IS NULL AND vl.created_on_utc BETWEEN p_start_date AND p_end_date)
)
ORDER BY vl.entry_time DESC;
END;
$function$
-- Function: get_unit_by_id
CREATE OR REPLACE FUNCTION public.get_unit_by_id(p_unit_id integer)
RETURNS TABLE(unit_id integer, unit_name text, floor_id integer, building_id integer, unit_type_id integer, customer_id uuid, occupant_type_id integer, occupancy_type_id integer, area numeric, bhk_type text, phone_extension integer, e_intercom integer, created_on_utc timestamp without time zone, modified_on_utc timestamp without time zone, created_by uuid, modified_by uuid, created_by_user_name text, modified_by_user_name text, customer_name text, customer_contact_number text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
unit.id AS unit_id,
unit.name AS unit_name,
unit.floor_id,
unit.building_id,
unit.unit_type_id,
unit.customer_id, -- UUID
unit.occupant_type_id,
unit.occupancy_type_id,
unit.area::NUMERIC, -- Explicit casting to NUMERIC
unit.bhk_type,
unit.phone_extention::INT AS phone_extension, -- Explicit casting to INT
unit.e_intercom::INT AS e_intercom, -- Explicit casting to INT
unit.created_on_utc,
unit.modified_on_utc,
unit.created_by, -- UUID
unit.modified_by, -- UUID
COALESCE(created_by_user.first_name, '') || ' ' || COALESCE(created_by_user.last_name, '') AS created_by_user_name,
COALESCE(modified_by_user.first_name, '') || ' ' || COALESCE(modified_by_user.last_name, '') AS modified_by_user_name,
u.first_name || ' ' || u.last_name AS customer_name,
u.contact_number AS customer_contact_number
FROM units unit
LEFT JOIN users created_by_user
ON unit.created_by = created_by_user.id
LEFT JOIN users modified_by_user
ON unit.modified_by = modified_by_user.id
JOIN resident_units ru
ON unit.id = ru.unit_id
JOIN residents r
ON ru.resident_id = r.id
JOIN users u
ON r.user_id = u.id
WHERE unit.id = p_unit_id
AND ru.is_deleted = false;
END;
$function$
-- Function: get_visitor_approval_info_by_approval_id
CREATE OR REPLACE FUNCTION public.get_visitor_approval_info_by_approval_id(approval_id integer)
RETURNS TABLE(visitor_id integer, first_name text, last_name text, contact_number text, start_date date, end_date date, entry_time time without time zone, exit_time time without time zone, created_by_first_name text, created_by_last_name text, unit_name text, apartment_name text, city_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
-- Correct syntax for returning the query result
RETURN QUERY
SELECT
v.id AS visitor_id,
v.first_name,
v.last_name,
v.contact_number,
av.start_date,
av.end_date,
av.entry_time,
av.exit_time,
u.first_name::text AS created_by_first_name,
u.last_name::text AS created_by_last_name,
ut.name, -- Fetching unit_name from resident-units table,
ap.name::text as apartment_name,
ct.name::text as apartment_name
FROM
public.visitors v
JOIN
public.visitor_approvals av ON v.id = av.visitor_id
LEFT JOIN
public.users u ON av.created_by = u.id
LEFT JOIN
public.residents r ON u.id = r.user_id
LEFT JOIN
public.resident_units ru ON r.id = ru.resident_id
LEFT JOIN
public.units ut ON ut.id = ru.unit_id
LEFT JOIN
public.apartments ap ON ap.id = ut.apartment_id
LEFT JOIN
public.addresses ad ON ad.id = ap.address_id
LEFT JOIN
public.cities ct ON ct.id = ad.city_id
WHERE
av.id = approval_id AND av.is_deleted = false;
END;
$function$
-- Function: get_water_tanker_deliveries
CREATE OR REPLACE FUNCTION public.get_water_tanker_deliveries(p_company_id uuid, p_start_date date, p_end_date date, p_vendor_id uuid DEFAULT NULL::uuid)
RETURNS TABLE(id integer, delivery_date date, delivery_time time without time zone, vendor_id uuid, vendor_name text, tanker_capacity_liters integer, actual_received_liters integer, created_by uuid, created_by_name text, created_on_utc timestamp with time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp with time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
d.id,
d.delivery_date,
d.delivery_time,
d.vendor_id,
vu.first_name || ' ' || vu.last_name AS vendor_name,
d.tanker_capacity_liters,
d.actual_received_liters,
d.created_by,
cu.first_name || ' ' || cu.last_name AS created_by_name,
d.created_on_utc,
d.modified_by,
mu.first_name || ' ' || mu.last_name AS modified_by_name,
d.modified_on_utc
FROM public.water_tanker_deliveries d
LEFT JOIN public.users vu ON vu.id = d.vendor_id AND NOT vu.is_deleted
LEFT JOIN public.users cu ON cu.id = d.created_by AND NOT cu.is_deleted
LEFT JOIN public.users mu ON mu.id = d.modified_by AND NOT mu.is_deleted
WHERE d.company_id = p_company_id
AND (p_vendor_id IS NULL OR d.vendor_id = p_vendor_id)
AND d.delivery_date BETWEEN p_start_date AND p_end_date
AND NOT d.is_deleted
ORDER BY d.delivery_date, d.delivery_time;
END;
$function$
-- Function: insert_bulk_water_tanker_deliveries
CREATE OR REPLACE FUNCTION public.insert_bulk_water_tanker_deliveries(p_deliveries jsonb, p_created_by uuid, p_created_on_utc timestamp with time zone)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
inserted_count INTEGER := 0;
delivery_record RECORD;
BEGIN
FOR delivery_record IN
SELECT *
FROM jsonb_to_recordset(p_deliveries)
AS (
company_id UUID,
vendor_id UUID,
delivery_date DATE,
delivery_time TIME,
tanker_capacity_liters INTEGER,
actual_received_liters INTEGER
)
LOOP
INSERT INTO public.water_tanker_deliveries (
company_id,
vendor_id,
delivery_date,
delivery_time,
tanker_capacity_liters,
actual_received_liters,
created_by,
created_on_utc
)
VALUES (
delivery_record.company_id,
delivery_record.vendor_id,
delivery_record.delivery_date,
delivery_record.delivery_time,
delivery_record.tanker_capacity_liters,
delivery_record.actual_received_liters,
p_created_by,
p_created_on_utc
);
inserted_count := inserted_count + 1;
END LOOP;
RETURN inserted_count;
END;
$function$
-- Function: approve_visitor_for_unit
CREATE OR REPLACE FUNCTION public.approve_visitor_for_unit(p_visitor_log_id integer, p_unit_id integer, p_resident_user uuid)
RETURNS TABLE(visitor_log_id integer, unit_id integer, approver_resident_id integer, approver_first_name text, approver_last_name text, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits INT;
local_approved_count INT;
local_rejected_count INT;
local_new_visitor_status INT;
local_resident_id INT;
BEGIN
-- Step 0: Resolve user_id β resident_id
SELECT r.id
INTO local_resident_id
FROM public.residents r
INNER JOIN public.resident_units ru ON ru.resident_id = r.id
WHERE r.user_id = p_resident_user
AND ru.unit_id = p_unit_id
AND r.is_deleted = FALSE
AND ru.is_deleted = FALSE
LIMIT 1;
IF local_resident_id IS NULL THEN
-- invalid user or unit
RETURN;
END IF;
-- Step 1: Approve for this unit only
UPDATE public.multi_unit_visits
SET visitor_statuses = 2, -- Approved
modified_on_utc = NOW(),
modified_by = p_resident_user
WHERE visitor_log_id = p_visitor_log_id
AND unit_id = p_unit_id
AND is_deleted = FALSE;
IF NOT FOUND THEN
RETURN;
END IF;
-- Step 2: Recalculate parent visitor log status
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6)
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = FALSE;
-- Step 3: Decide new parent status
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := 2; -- All Approved
ELSIF local_approved_count > 0 THEN
local_new_visitor_status := 7; -- Partial Approved
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := 6; -- Rejected
ELSE
local_new_visitor_status := 1; -- Pending
END IF;
UPDATE public.visitor_logs
SET visitor_status_id = local_new_visitor_status,
modified_on_utc = NOW(),
modified_by = p_resident_user
WHERE id = p_visitor_log_id;
-- Step 4: Return info + approver's name
RETURN QUERY
SELECT
p_visitor_log_id,
p_unit_id,
r.id,
r.first_name,
r.last_name,
local_new_visitor_status
FROM public.residents r
WHERE r.id = local_resident_id
AND r.is_deleted = FALSE;
END;
$function$
-- Function: get_current_visitors_and_service_providers
CREATE OR REPLACE FUNCTION public.get_current_visitors_and_service_providers(p_unit_id integer)
RETURNS TABLE(id integer, visitor_type_id integer, visitor_type text, serial_id integer, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_person_type text, visitor_person_sub_type text, visitor_status_id integer, visitor_status text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
CAST(ROW_NUMBER() OVER (ORDER BY combined.entry_time, combined.id) AS INT) AS serial_id,
combined.visitor_type_id,
combined.visitor_type,
combined.id,
combined.first_name,
combined.last_name,
combined.entry_time,
combined.exit_time,
combined.visitor_person_type,
combined.visitor_person_sub_type,
combined.visitor_status_id,
combined.visitor_status_name
FROM (
-- Visitors
SELECT
vt.id AS visitor_type_id,
'visitor'::TEXT AS visitor_type,
v.id,
v.first_name::TEXT,
v.last_name::TEXT,
vl.entry_time,
vl.exit_time,
vt."name"::TEXT AS visitor_person_type,
NULL::TEXT AS visitor_person_sub_type,
CASE
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 3
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 4
ELSE vl.visitor_status_id
END AS visitor_status_id,
CASE
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 'Checked In'
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 'Checked Out'
ELSE vs."name"::TEXT
END AS visitor_status_name
FROM
public.visitor_logs vl
JOIN public.visitors v ON vl.visitor_id = v.id
JOIN public.multi_unit_visits muv ON muv.visitor_log_id = vl.id
JOIN public.visitor_types vt ON v.visitor_type_id = vt.id
LEFT JOIN public.visitor_statuses vs ON vl.visitor_status_id = vs.id
WHERE
muv.unit_id = p_unit_id
AND vl.entry_time >= NOW() - INTERVAL '120 hour'
AND vl.is_deleted = FALSE
AND v.is_deleted = FALSE
AND muv.is_deleted = FALSE
UNION ALL
-- Service Providers
SELECT
vt_sp.id AS visitor_type_id,
'service_provider'::TEXT AS visitor_type,
sp.id,
sp.first_name::TEXT,
sp.last_name::TEXT,
spl.entry_time,
spl.exit_time,
spt."name"::TEXT AS visitor_person_type,
spst."name"::TEXT AS visitor_person_sub_type,
CASE
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 3
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 4
ELSE NULL
END AS visitor_status_id,
CASE
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 'Checked In'
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 'Checked Out'
ELSE NULL::TEXT
END AS visitor_status_name
FROM
public.service_provider_logs spl
JOIN public.service_providers sp ON spl.service_provider_id = sp.id
JOIN public.service_provider_types spt ON sp.service_provider_type_id = spt.id
JOIN public.service_provider_sub_types spst ON sp.service_provider_sub_type_id = spst.id
JOIN public.multi_unit_visits muv ON muv.unit_id = p_unit_id
LEFT JOIN public.visitor_types vt_sp ON vt_sp."name" = 'Service Provider' AND vt_sp.is_deleted = FALSE
WHERE
spl.entry_time >= NOW() - INTERVAL '120 hour'
AND spl.is_deleted = FALSE
AND sp.is_deleted = FALSE
) AS combined;
END;
$function$
-- Function: insert_pin_record
CREATE OR REPLACE FUNCTION public.insert_pin_record(p_effective_start_date_time timestamp without time zone, p_service_provider_id integer DEFAULT NULL::integer, p_visitor_id integer DEFAULT NULL::integer, p_delivery_id integer DEFAULT NULL::integer, p_effective_end_date_time timestamp without time zone DEFAULT NULL::timestamp without time zone)
RETURNS void
LANGUAGE plpgsql
AS $function$
DECLARE
v_pin_code VARCHAR(6);
is_unique BOOLEAN := FALSE;
max_attempts INT := 1000; -- Limit the number of attempts to avoid infinite loop
attempts INT := 0;
BEGIN
-- Loop until a unique PIN is found or max attempts are reached
WHILE NOT is_unique AND attempts < max_attempts LOOP
v_pin_code := generate_random_pin();
-- Check if the PIN is unique
PERFORM 1 FROM pins WHERE pins.pin_code = v_pin_code;
IF NOT FOUND THEN
is_unique := TRUE;
ELSE
attempts := attempts + 1;
END IF;
END LOOP;
IF NOT is_unique THEN
RAISE EXCEPTION 'Could not generate a unique PIN after % attempts', max_attempts;
END IF;
-- Insert the new record
INSERT INTO pins (service_provider_id, visitor_id, delivery_id, pin_code, effective_start_date_time, effective_end_date_time)
VALUES (p_service_provider_id, p_visitor_id, p_delivery_id, v_pin_code, p_effective_start_date_time, p_effective_end_date_time);
END;
$function$
-- Function: update_visitor_status
CREATE OR REPLACE FUNCTION public.update_visitor_status(p_visitor_log_id integer, p_unit_id integer, p_visitor_status_id integer, p_modified_by uuid)
RETURNS TABLE(visitor_id integer, total_visits_count integer, approved_visits_count integer, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits int;
local_approved_count int;
local_rejected_count int; -- Added to track rejections
local_new_visitor_status int;
BEGIN
-- Step 1: Update the status for the specific unit.
UPDATE public.multi_unit_visits
SET visitor_statuses = p_visitor_status_id,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE public.multi_unit_visits.visitor_log_id = p_visitor_log_id
AND public.multi_unit_visits.unit_id = p_unit_id
AND public.multi_unit_visits.is_deleted = false;
-- Step 2: Check if the update was successful.
IF NOT FOUND THEN
RETURN;
END IF;
-- Step 3: Recalculate the overall visitor log status.
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2), -- Approved
COUNT(*) FILTER (WHERE muv.visitor_statuses = 6) -- Rejected
INTO
local_total_visits,
local_approved_count,
local_rejected_count
FROM public.multi_unit_visits AS muv
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = false;
-- Step 4: Determine the new overall status with corrected logic.
IF local_approved_count = local_total_visits AND local_total_visits > 0 THEN
local_new_visitor_status := 2; -- Rule 1: All units have approved.
ELSIF local_approved_count > 0 THEN
-- FIX: If at least one unit approves, the status is "Partial Approved",
-- even if another unit rejects.
local_new_visitor_status := 7; -- Rule 2: At least one approval, but not all.
ELSIF local_rejected_count > 0 THEN
local_new_visitor_status := 6; -- Rule 3: No approvals yet, but at least one rejection.
ELSE
local_new_visitor_status := 1; -- Rule 4: No approvals and no rejections yet.
END IF;
-- Step 5: Update the parent visitor_logs table.
UPDATE public.visitor_logs
SET visitor_status_id = local_new_visitor_status,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = p_visitor_log_id;
-- Step 6: On success, return the calculated values.
RETURN QUERY SELECT p_visitor_log_id, local_total_visits, local_approved_count, local_new_visitor_status;
END;
$function$
-- Function: check_or_create_event_occurrence
CREATE OR REPLACE FUNCTION public.check_or_create_event_occurrence(p_event_uuid uuid, p_occ_date date, p_user_uuid uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_occurrence_id INT;
v_start_time TIME;
v_end_time TIME;
BEGIN
SELECT id INTO v_occurrence_id
FROM event_occurrences
WHERE event_id = p_event_uuid AND occurrence_date = p_occ_date AND is_deleted = false;
IF v_occurrence_id IS NOT NULL THEN
RETURN v_occurrence_id;
END IF;
-- Extract only the TIME part from the event's start and end time
SELECT start_time::time, end_time::time
INTO v_start_time, v_end_time
FROM events
WHERE id = p_event_uuid;
-- Insert new occurrence with time applied to p_occ_date
INSERT INTO event_occurrences (
event_id, occurrence_date, start_time, end_time,
generated_from_recurrence, created_on_utc, created_by, is_deleted
)
VALUES (
p_event_uuid,
p_occ_date,
(p_occ_date + v_start_time)::timestamp,
(p_occ_date + v_end_time)::timestamp,
(SELECT is_recurring FROM events WHERE id = p_event_uuid),
now(),
p_user_uuid,
false
)
RETURNING id INTO v_occurrence_id;
RETURN v_occurrence_id;
END;
$function$
-- Function: debug_visitor_logs
CREATE OR REPLACE FUNCTION public.debug_visitor_logs(v_apartment_id uuid, log_date timestamp without time zone)
RETURNS TABLE(id integer, visitor_id integer, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visiting_from text, contact_number text, visitor_type_id integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
vl.id, v.id, v.first_name, v.last_name, vl.entry_time, vl.exit_time,
vl.visiting_from, v.contact_number, v.visitor_type_id
FROM
visitors v
JOIN
visitor_logs vl ON v.id = vl.visitor_id
WHERE
v.apartment_id = v_apartment_id
AND DATE(vl.entry_time) = DATE(log_date)
AND v.is_deleted = FALSE
AND vl.is_deleted = FALSE;
END;
$function$
-- Function: get_all_apartment_or_org_residents
CREATE OR REPLACE FUNCTION public.get_all_apartment_or_org_residents(apartmentid uuid, isgetall boolean)
RETURNS TABLE(id integer, resident_name text, user_id uuid)
LANGUAGE plpgsql
AS $function$
DECLARE
org_id uuid;
BEGIN
IF isGetAll THEN
SELECT a.organization_id INTO org_id
FROM public.apartments a
WHERE a.id = apartmentid
AND a.is_deleted = false;
RETURN QUERY
SELECT
row_number() OVER ()::int AS id,
r.first_name || ' ' || r.last_name AS resident_name,
r.user_id
FROM public.apartments a
INNER JOIN public.residents r ON r.apartment_id = a.id
WHERE a.organization_id = org_id
AND r.is_deleted = false
AND a.is_deleted = false;
ELSE
RETURN QUERY
SELECT
row_number() OVER ()::int AS id,
r.first_name || ' ' || r.last_name AS resident_name,
r.user_id
FROM public.apartments a
INNER JOIN public.residents r ON r.apartment_id = a.id
WHERE a.id = apartmentid
AND r.is_deleted = false
AND a.is_deleted = false;
END IF;
END;
$function$
-- Function: get_all_visitor_logs
CREATE OR REPLACE FUNCTION public.get_all_visitor_logs(v_apartment_id uuid, log_date timestamp without time zone)
RETURNS TABLE(id integer, visitor_id integer, first_name text, last_name text, unit_id integer, unit_name text, latest_entry_time timestamp without time zone, latest_exit_time timestamp without time zone, visiting_from text, contact_number text, visitor_type_id integer, visitor_type_name text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
vl.id as id,
v.id AS visitor_id,
v.first_name,
v.last_name,
vul.unit_id,
u.name AS unit_name,
MAX(vl.entry_time) AS latest_entry_time,
MAX(vl.exit_time) AS latest_exit_time,
vl.visiting_from,
v.contact_number,
v.visitor_type_id,
vt.name :: text
FROM
visitors v
JOIN
visitor_logs vl ON v.id = vl.visitor_id
LEFT JOIN
visitor_unit_logs vul ON vul.visitor_log_id = vl.id -- Join visitor_logs to visitor_unit_logs
LEFT JOIN
units u ON u.id = vul.unit_id -- Join visitor_unit_logs to units to get unit_name
LEFT JOIN
visitor_types vt ON vt.id = v.visitor_type_id
WHERE
v.apartment_id = v_apartment_id -- Use the renamed parameter 'v_apartment_id'
AND DATE(vl.entry_time) = DATE(log_date) -- Match the date part of timestamp
AND v.is_deleted = FALSE
AND vl.is_deleted = FALSE
AND vul.is_deleted = FALSE -- Ensure visitor_unit_logs is not deleted
GROUP BY
vl.Id, v.id, v.first_name, v.last_name, vul.unit_id, u.name, v.identity_number,
vl.visiting_from, v.contact_number, v.visitor_type_id, vt.name;
END;
$function$
-- Function: process_visitor_unit_response
CREATE OR REPLACE FUNCTION public.process_visitor_unit_response(p_visitor_log_id integer, p_unit_id integer, p_visitor_status_id integer, p_modified_by uuid)
RETURNS TABLE(visitor_log_id integer, total_visits_count integer, approved_visits_count integer, final_status_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
local_total_visits int;
local_approved_count int;
local_new_visitor_status int;
BEGIN
-- Step 1: Update the status for the specific unit.
UPDATE public.multi_unit_visits
SET visitor_statuses = p_visitor_status_id,
modified_on_utc = NOW(),
modified_by = p_modified_by
-- FIX: Explicitly reference the table's column to avoid ambiguity
WHERE public.multi_unit_visits.visitor_log_id = p_visitor_log_id
AND public.multi_unit_visits.unit_id = p_unit_id
AND public.multi_unit_visits.is_deleted = false;
-- Step 2: Check if the update was successful.
IF NOT FOUND THEN
RETURN;
END IF;
-- Step 3: Recalculate the overall visitor log status.
SELECT
COUNT(*),
COUNT(*) FILTER (WHERE muv.visitor_statuses = 2) -- Approved
INTO
local_total_visits,
local_approved_count
FROM public.multi_unit_visits AS muv
-- FIX: Use an alias for clarity and to prevent ambiguity
WHERE muv.visitor_log_id = p_visitor_log_id
AND muv.is_deleted = false;
-- Step 4: Determine the new overall status.
IF EXISTS (
SELECT 1
FROM public.multi_unit_visits
-- FIX: Explicitly reference the table's column
WHERE public.multi_unit_visits.visitor_log_id = p_visitor_log_id
AND public.multi_unit_visits.visitor_statuses = 6 -- Rejected
AND public.multi_unit_visits.is_deleted = false
) THEN
local_new_visitor_status := 6; -- Rejected
ELSIF local_approved_count > 0 AND local_approved_count = local_total_visits THEN
local_new_visitor_status := 2; -- Approved
ELSE
local_new_visitor_status := 1; -- Pending
END IF;
-- Step 5: Update the parent visitor_logs table.
UPDATE public.visitor_logs
SET visitor_status_id = local_new_visitor_status,
modified_on_utc = NOW(),
modified_by = p_modified_by
WHERE id = p_visitor_log_id;
-- Step 6: On success, return the calculated values.
RETURN QUERY SELECT p_visitor_log_id, local_total_visits, local_approved_count, local_new_visitor_status;
END;
$function$
-- Function: save_visitor_and_pending
CREATE OR REPLACE FUNCTION public.save_visitor_and_pending(p_apartment_id uuid, p_first_name text, p_last_name text, p_email text, p_contact_number text, p_vehicle_number text, p_identity_type_id integer, p_identity_number text, p_visitor_type_id integer, p_visiting_from text, p_unit_ids integer[], p_created_by uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id INTEGER;
v_visitor_log_id INTEGER;
v_unit_id INTEGER;
BEGIN
-- Check if the visitor already exists based on first_name and contact_number
SELECT id INTO v_visitor_id
FROM public.visitors
WHERE first_name = p_first_name
AND contact_number = p_contact_number
AND is_deleted = false;
IF FOUND THEN
RAISE NOTICE 'Visitor already exists with ID: %, using existing ID.', v_visitor_id;
ELSE
RAISE NOTICE 'Visitor does not exist. Inserting new visitor.';
END IF;
-- If visitor exists, use the existing visitor_id, else insert a new visitor
IF NOT FOUND THEN
-- Insert into visitors table (auto-generated id will be used)
INSERT INTO public.visitors (
first_name,
last_name,
email,
contact_number,
visiting_from,
visitor_type_id,
vehicle_number,
identity_type_id,
identity_number,
created_on_utc,
created_by,
apartment_id
)
VALUES (
p_first_name,
p_last_name,
p_email,
p_contact_number,
p_visiting_from,
p_visitor_type_id,
p_vehicle_number,
p_identity_type_id,
p_identity_number,
NOW(),
p_created_by,
p_apartment_id
)
RETURNING id INTO v_visitor_id; -- Capture the auto-generated id
RAISE NOTICE 'New visitor inserted with ID: %', v_visitor_id;
END IF;
-- Insert into visitor_logs table using the visitor_id
INSERT INTO public.visitor_logs (
visitor_id,
visitor_type_id,
visiting_from,
visitor_status_id,
entry_time,
exit_time,
created_on_utc,
created_by
)
VALUES (
v_visitor_id,
p_visitor_type_id,
p_visiting_from,
1, -- Assuming 1 is the default visitor status
NULL,
NULL,
NOW(),
p_created_by
)
RETURNING id INTO v_visitor_log_id;
RAISE NOTICE 'Visitor log created for visitor ID: %', v_visitor_id;
-- Loop through each unit_id in the array and insert a record into multi_unit_visits table
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
INSERT INTO public.multi_unit_visits (
visitor_log_id,
unit_id,
visitor_statuses,
created_on_utc,
is_deleted,
created_by
)
VALUES (
v_visitor_log_id,
v_unit_id,
1, -- Assuming 1 is the default visitor status
NOW(),
FALSE,
p_created_by
);
RAISE NOTICE 'Multi-unit visit created for visitor_log_id: % and unit_id: %', v_visitor_log_id, v_unit_id;
END LOOP;
-- Return the visitor_log_id
RETURN v_visitor_log_id;
END;
$function$
-- Function: get_visitors_and_service_providers_in_timespan
CREATE OR REPLACE FUNCTION public.get_visitors_and_service_providers_in_timespan(p_unit_id integer, p_start_date timestamp without time zone, p_end_date timestamp without time zone)
RETURNS TABLE(id integer, visitor_type_id integer, visitor_type text, serial_id integer, first_name text, last_name text, entry_time timestamp without time zone, exit_time timestamp without time zone, visitor_person_type text, visitor_person_sub_type text, visitor_status_id integer, visitor_status text)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
CAST(ROW_NUMBER() OVER (ORDER BY combined.entry_time, combined.id) AS INT) AS serial_id,
combined.visitor_type_id,
combined.visitor_type,
combined.id,
combined.first_name,
combined.last_name,
combined.entry_time,
combined.exit_time,
combined.visitor_person_type,
combined.visitor_person_sub_type,
combined.visitor_status_id,
combined.visitor_status_name
FROM (
-- Visitors
SELECT
vt.id AS visitor_type_id,
'visitor'::TEXT AS visitor_type,
v.id,
v.first_name::TEXT,
v.last_name::TEXT,
vl.entry_time,
vl.exit_time,
vt."name"::TEXT AS visitor_person_type,
NULL::TEXT AS visitor_person_sub_type,
CASE
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 3
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 4
ELSE vl.visitor_status_id
END AS visitor_status_id,
CASE
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NULL THEN 'Checked In'
WHEN vl.entry_time IS NOT NULL AND vl.exit_time IS NOT NULL THEN 'Checked Out'
ELSE vs."name"::TEXT
END AS visitor_status_name
FROM
public.visitor_logs vl
JOIN public.visitors v ON vl.visitor_id = v.id
JOIN public.multi_unit_visits muv ON muv.visitor_log_id = vl.id
JOIN public.visitor_types vt ON v.visitor_type_id = vt.id
LEFT JOIN public.visitor_statuses vs ON vl.visitor_status_id = vs.id
WHERE
muv.unit_id = p_unit_id
AND vl.entry_time BETWEEN p_start_date AND p_end_date
AND vl.is_deleted = FALSE
AND v.is_deleted = FALSE
AND muv.is_deleted = FALSE
UNION ALL
-- Service Providers
SELECT
vt_sp.id AS visitor_type_id,
'service_provider'::TEXT AS visitor_type,
sp.id,
sp.first_name::TEXT,
sp.last_name::TEXT,
spl.entry_time,
spl.exit_time,
spt."name"::TEXT AS visitor_person_type,
spst."name"::TEXT AS visitor_person_sub_type,
CASE
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 3
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 4
ELSE NULL
END AS visitor_status_id,
CASE
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NULL THEN 'Checked In'
WHEN spl.entry_time IS NOT NULL AND spl.exit_time IS NOT NULL THEN 'Checked Out'
ELSE NULL::TEXT
END AS visitor_status_name
FROM
public.service_provider_logs spl
JOIN public.service_providers sp ON spl.service_provider_id = sp.id
JOIN public.service_provider_types spt ON sp.service_provider_type_id = spt.id
JOIN public.service_provider_sub_types spst ON sp.service_provider_sub_type_id = spst.id
JOIN public.multi_unit_visits muv ON muv.unit_id = p_unit_id
LEFT JOIN public.visitor_types vt_sp ON vt_sp."name" = 'Service Provider' AND vt_sp.is_deleted = FALSE
WHERE
spl.entry_time BETWEEN p_start_date AND p_end_date
AND spl.is_deleted = FALSE
AND sp.is_deleted = FALSE
) AS combined;
END;
$function$
-- Function: get_user_full_details
CREATE OR REPLACE FUNCTION public.get_user_full_details(p_user_id uuid, p_apartment_id uuid)
RETURNS TABLE(user_id uuid, full_name character varying, email character varying, contact_number character varying, unit_name character varying, vehicles text[], address_line1 character varying, address_line2 character varying, zip_code character varying, country_name character varying, state_name character varying, city_name character varying, apartment_id uuid)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
u.id AS user_id,
CONCAT(u.first_name, ' ', u.last_name)::VARCHAR AS full_name,
u.email::VARCHAR,
u.contact_number::VARCHAR,
un.name::VARCHAR AS unit_name,
ARRAY_AGG(DISTINCT CONCAT(vt.name, ' - ', v.vehicle_number))::TEXT[] AS vehicles,
a.address_line1::VARCHAR,
a.address_line2::VARCHAR,
a.zip_code::VARCHAR,
co.name::VARCHAR AS country_name,
s.name::VARCHAR AS state_name,
ci.name::VARCHAR AS city_name,
un.apartment_id
FROM
users u
INNER JOIN
residents r
ON u.id = r.user_id
LEFT JOIN
resident_units ru
ON r.id = ru.resident_id
AND ru.is_deleted = FALSE
LEFT JOIN
units un
ON ru.unit_id = un.id
AND un.apartment_id = p_apartment_id -- β
Filter by company
LEFT JOIN
vehicles v
ON un.id = v.unit_id
AND v.is_deleted = FALSE
LEFT JOIN
vehicle_types vt
ON v.vehicle_type_id = vt.id
LEFT JOIN
addresses a
ON r.permanent_address_id = a.id
LEFT JOIN
countries co
ON a.country_id = co.id
LEFT JOIN
states s
ON a.state_id = s.id
LEFT JOIN
cities ci
ON a.city_id = ci.id
WHERE
u.id = p_user_id
AND u.is_deleted = FALSE
AND r.is_deleted = FALSE
AND r.apartment_id = p_apartment_id -- β
Restrict resident scope to the company
GROUP BY
u.id, u.first_name, u.last_name, u.email, u.contact_number,
un.name, a.address_line1, a.address_line2, a.zip_code,
co.name, s.name, ci.name, un.apartment_id
ORDER BY
un.name;
END;
$function$
-- Function: get_all_visitors_by_unit
CREATE OR REPLACE FUNCTION public.get_all_visitors_by_unit(p_apartment_id uuid, p_unit_id integer)
RETURNS TABLE(id integer, first_name text, last_name text, email text, visiting_from text, contact_number text, visitor_type_id integer, visitor_type_name character varying, vehicle_number text, identity_type_id integer, identity_number text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id,
v.first_name,
v.last_name,
v.email,
v.visiting_from,
v.contact_number,
v.visitor_type_id,
vt.name AS visitor_type_name,
v.vehicle_number,
v.identity_type_id,
v.identity_number,
v.created_by,
v.created_on_utc,
v.modified_by,
v.modified_on_utc
FROM
public.visitors v
INNER JOIN
public.visitor_types vt ON v.visitor_type_id = vt.id
WHERE
v.apartment_id = p_apartment_id
AND v.unit_id = p_unit_id
AND v.is_deleted = false
AND (vt.is_deleted = false OR vt.is_deleted IS NULL);
END;
$function$
-- Function: generate_ticket_number
CREATE OR REPLACE FUNCTION public.generate_ticket_number(p_apartment_id uuid, p_category_id integer)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
v_today date := CURRENT_DATE;
v_short_code varchar(10);
v_last_number int;
v_formatted_date text;
v_formatted_sequence text;
v_ticket_number text;
BEGIN
-- 1οΈβ£ Fetch short code for the category
SELECT short_code INTO v_short_code
FROM public.ticket_categories
WHERE id = p_category_id AND is_deleted = false;
IF v_short_code IS NULL OR v_short_code = '' THEN
RAISE EXCEPTION 'ShortCode not found for category %', p_category_id;
END IF;
-- 2οΈβ£ Insert or update sequence (handles concurrency)
INSERT INTO public.ticket_number_sequences (apartment_id, ticket_date, category_id, last_number)
VALUES (p_apartment_id, v_today, p_category_id, 1)
ON CONFLICT (apartment_id, ticket_date, category_id)
DO UPDATE
SET last_number = ticket_number_sequences.last_number + 1
RETURNING last_number
INTO v_last_number;
-- 3οΈβ£ Format date as DDMMYY
v_formatted_date := to_char(v_today, 'DDMMYY');
-- 4οΈβ£ Format sequence as 2-digit (01, 02, 03β¦)
v_formatted_sequence := LPAD(v_last_number::text, 2, '0');
-- 5οΈβ£ Build final ticket number
v_ticket_number := v_formatted_date || '-' || v_short_code || '-' || v_formatted_sequence;
RETURN v_ticket_number;
END;
$function$
-- Function: get_ticket_analytics
CREATE OR REPLACE FUNCTION public.get_ticket_analytics(p_apartment_id uuid, p_from timestamp without time zone DEFAULT NULL::timestamp without time zone, p_to timestamp without time zone DEFAULT NULL::timestamp without time zone, p_recent_limit integer DEFAULT 5)
RETURNS jsonb
LANGUAGE plpgsql
AS $function$
DECLARE
result jsonb;
BEGIN
WITH base AS (
SELECT *
FROM public.tickets t
WHERE t.apartment_id = p_apartment_id
AND NOT t.is_deleted
AND (p_from IS NULL OR t.created_on_utc >= p_from)
AND (p_to IS NULL OR t.created_on_utc <= p_to)
),
by_status AS (
SELECT
coalesce(t.ticket_status_id, 0) AS status_id,
count(*) AS cnt
FROM base t
GROUP BY coalesce(t.ticket_status_id, 0)
ORDER BY cnt DESC
),
by_priority AS (
SELECT
coalesce(t.ticket_priority_id, 0) AS priority_id,
count(*) AS cnt
FROM base t
GROUP BY coalesce(t.ticket_priority_id, 0)
ORDER BY cnt DESC
),
by_category AS (
SELECT
coalesce(t.ticket_category_id, 0) AS category_id,
count(*) AS cnt
FROM base t
GROUP BY coalesce(t.ticket_category_id, 0)
ORDER BY cnt DESC
),
recent AS (
SELECT
t.id,
t.ticket_number,
t.title,
t.ticket_status_id,
t.ticket_priority_id,
t.ticket_category_id,
t.is_on_hold,
t.created_on_utc
FROM base t
ORDER BY t.created_on_utc DESC
LIMIT p_recent_limit
)
SELECT jsonb_build_object(
'totalTickets', (SELECT count(*) FROM base),
'onHoldTickets', (SELECT count(*) FROM base WHERE is_on_hold),
'ticketsByStatus', COALESCE((
SELECT jsonb_agg(jsonb_build_object(
'id', s.status_id,
'name', ts.name,
'count', s.cnt
) ORDER BY s.cnt DESC)
FROM by_status s
LEFT JOIN public.ticket_statuses ts ON ts.id = s.status_id
), '[]'::jsonb),
'ticketsByPriority', COALESCE((
SELECT jsonb_agg(jsonb_build_object(
'id', p.priority_id,
'name', tp.name,
'count', p.cnt
) ORDER BY p.cnt DESC)
FROM by_priority p
LEFT JOIN public.ticket_priorities tp ON tp.id = p.priority_id
), '[]'::jsonb),
'ticketsByCategory', COALESCE((
SELECT jsonb_agg(jsonb_build_object(
'id', c.category_id,
'name', tc.name,
'count', c.cnt
) ORDER BY c.cnt DESC)
FROM by_category c
LEFT JOIN public.ticket_categories tc ON tc.id = c.category_id
), '[]'::jsonb),
'recentTickets', COALESCE((
SELECT jsonb_agg(row_to_json(r))
FROM (
SELECT
r.id,
r.ticket_number,
r.title,
COALESCE(ts.name, '') AS status,
COALESCE(tp.name, '') AS priority,
COALESCE(tc.name, '') AS category,
r.is_on_hold,
r.created_on_utc
FROM recent r
LEFT JOIN public.ticket_statuses ts ON ts.id = r.ticket_status_id
LEFT JOIN public.ticket_priorities tp ON tp.id = r.ticket_priority_id
LEFT JOIN public.ticket_categories tc ON tc.id = r.ticket_category_id
ORDER BY r.created_on_utc DESC
) r
), '[]'::jsonb)
) INTO result;
RETURN result;
END;
$function$
-- Function: create_service_provider_api
CREATE OR REPLACE FUNCTION public.create_service_provider_api(p_first_name text, p_last_name text, p_contact_number text, p_visitor_type_id integer, p_email text, p_visiting_from text, p_service_provider_type_id integer, p_service_provider_sub_type_id integer, p_vehicle_number text, p_identity_type_id integer, p_identity_number text, p_validity_date date, p_police_verification_status boolean, p_is_hireable boolean, p_is_visible boolean, p_is_frequent_visitor boolean, p_apartment_id uuid, p_pin text, p_created_by uuid, p_permanent_address_id uuid, p_present_address_id uuid)
RETURNS integer
LANGUAGE sql
AS $function$
SELECT service_provider_id
FROM public.create_service_provider(
p_first_name,
p_last_name,
p_contact_number,
p_visitor_type_id,
p_email,
p_visiting_from,
p_service_provider_type_id,
p_service_provider_sub_type_id,
p_vehicle_number,
p_identity_type_id,
p_identity_number,
p_validity_date,
p_police_verification_status,
p_is_hireable,
p_is_visible,
p_is_frequent_visitor,
p_apartment_id,
p_pin,
p_created_by,
p_permanent_address_id,
p_present_address_id
);
$function$
-- Function: create_service_provider
CREATE OR REPLACE FUNCTION public.create_service_provider(p_first_name character varying, p_last_name character varying, p_contact_number character varying, p_visitor_type_id integer, p_email character varying, p_visiting_from character varying, p_service_provider_type_id integer, p_service_provider_sub_type_id integer, p_vehicle_number character varying, p_identity_type_id integer, p_identity_number character varying, p_validity_date timestamp without time zone, p_police_verification_status boolean, p_is_hireable boolean, p_is_visible boolean, p_is_frequent_visitor boolean, p_apartment_id uuid, p_pin character varying, p_created_by uuid, p_permanent_address_id uuid, p_present_address_id uuid)
RETURNS TABLE(service_provider_id integer)
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id integer;
v_service_provider_id integer;
BEGIN
/* ----------------------------------------------------
1. Create Visitor
---------------------------------------------------- */
INSERT INTO visitors (
first_name,
last_name,
visitor_type_id,
created_by,
created_on_utc
)
VALUES (
p_first_name,
p_last_name,
p_visitor_type_id,
p_created_by,
now()
)
RETURNING id INTO v_visitor_id;
/* ----------------------------------------------------
2. Visitor Phone
---------------------------------------------------- */
INSERT INTO visitor_phones (
visitor_id,
phone_number,
is_active,
valid_from
)
VALUES (
v_visitor_id,
p_contact_number,
true,
now()
);
/* ----------------------------------------------------
3. Visitor Email (optional)
---------------------------------------------------- */
IF p_email IS NOT NULL THEN
INSERT INTO visitor_emails (
visitor_id,
email,
is_active,
valid_from
)
VALUES (
v_visitor_id,
p_email,
true,
now()
);
END IF;
/* ----------------------------------------------------
4. Visitor Identity (optional)
---------------------------------------------------- */
IF p_identity_type_id IS NOT NULL AND p_identity_number IS NOT NULL THEN
INSERT INTO visitor_identities (
visitor_id,
identity_type_id,
identity_number,
is_active,
created_by,
created_on_utc
)
VALUES (
v_visitor_id,
p_identity_type_id,
p_identity_number,
true,
p_created_by,
now()
);
END IF;
/* ----------------------------------------------------
5. Visitor Details (optional)
---------------------------------------------------- */
IF p_visiting_from IS NOT NULL OR p_vehicle_number IS NOT NULL THEN
INSERT INTO visitor_details (
visitor_id,
visiting_from,
vehicle_number,
created_by,
created_on_utc
)
VALUES (
v_visitor_id,
p_visiting_from,
p_vehicle_number,
p_created_by,
now()
);
END IF;
/* ----------------------------------------------------
6. Visitor β Apartment mapping
---------------------------------------------------- */
INSERT INTO visitor_apartments (
visitor_id,
visitor_apartment_id,
first_seen,
is_blocked
)
VALUES (
v_visitor_id,
p_apartment_id,
now(),
false
);
/* ----------------------------------------------------
7. Create Service Provider
---------------------------------------------------- */
INSERT INTO service_providers (
service_provider_type_id,
service_provider_sub_type_id,
visitor_id,
is_frequent_visitor,
is_hireable,
is_visible,
police_verification_status,
pin,
valid_from,
created_by,
created_on_utc
)
VALUES (
p_service_provider_type_id,
p_service_provider_sub_type_id,
v_visitor_id,
p_is_frequent_visitor,
p_is_hireable,
p_is_visible,
p_police_verification_status,
p_pin,
p_validity_date,
p_created_by,
now()
)
RETURNING id INTO v_service_provider_id;
/* ----------------------------------------------------
8. Addresses (optional)
---------------------------------------------------- */
IF p_permanent_address_id IS NOT NULL THEN
INSERT INTO service_provider_addresses (
service_provider_id,
address_id,
is_permanent_address,
is_present_address
)
VALUES (
v_service_provider_id,
p_permanent_address_id,
true,
false
);
END IF;
IF p_present_address_id IS NOT NULL THEN
INSERT INTO service_provider_addresses (
service_provider_id,
address_id,
is_permanent_address,
is_present_address
)
VALUES (
v_service_provider_id,
p_present_address_id,
false,
true
);
END IF;
/* ----------------------------------------------------
9. Return
---------------------------------------------------- */
service_provider_id := v_service_provider_id;
RETURN;
END;
$function$
-- Function: get_visitor_inside_counts
CREATE OR REPLACE FUNCTION public.get_visitor_inside_counts(p_apartment_id uuid, p_today_only boolean)
RETURNS TABLE(id bigint, visitor_log_id bigint, visitor_id bigint, first_name text, last_name text, visitor_type_id integer, visitor_type_name text, entry_time timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
ROW_NUMBER() OVER (ORDER BY vl.entry_time ASC)::bigint AS id, -- Incremental ID
vl.id AS visitor_log_id,
v.id AS visitor_id,
v.first_name,
v.last_name,
vl.visitor_type_id,
vt.name::text AS visitor_type_name,
vl.entry_time
FROM
visitor_logs vl
INNER JOIN
visitors v ON v.id = vl.visitor_id
INNER JOIN
visitor_types vt ON vt.id = vl.visitor_type_id
WHERE
vl.apartment_id = p_apartment_id
AND vl.exit_time IS NULL
AND (NOT p_today_only OR vl.entry_time::date = CURRENT_DATE)
AND v.is_deleted = FALSE
AND vt.is_deleted = FALSE
ORDER BY
vl.entry_time ASC;
END;
$function$
-- Function: get_all_visitors_by_apartment
CREATE OR REPLACE FUNCTION public.get_all_visitors_by_apartment(p_apartment_id uuid)
RETURNS TABLE(id bigint, first_name text, last_name text, email text, visiting_from text, contact_number text, visitor_type_id integer, visitor_type_name character varying, vehicle_number text, identity_type_id integer, identity_number text, created_by uuid, created_on_utc timestamp without time zone, modified_by uuid, modified_on_utc timestamp without time zone)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT
v.id, -- this is bigint, matching the table definition
v.first_name,
v.last_name,
ve.email,
vd.visiting_from,
vp.phone_number AS contact_number,
v.visitor_type_id,
vt.name AS visitor_type_name,
vd.vehicle_number,
vi.identity_type_id,
vi.identity_number,
v.created_by,
v.created_on_utc,
v.modified_by,
v.modified_on_utc
FROM
public.visitors v
INNER JOIN
public.visitor_types vt ON v.visitor_type_id = vt.id
LEFT JOIN
public.visitor_emails ve ON v.id = ve.visitor_id AND ve.is_active = TRUE
LEFT JOIN
public.visitor_details vd ON v.id = vd.visitor_id
LEFT JOIN
public.visitor_phones vp ON v.id = vp.visitor_id AND vp.is_active = TRUE
LEFT JOIN
public.visitor_identities vi ON v.id = vi.visitor_id AND vi.is_active = TRUE
INNER JOIN
public.visitor_apartments va ON v.id = va.visitor_id
WHERE
va.visitor_apartment_id = p_apartment_id
AND v.is_deleted = FALSE
AND (vt.is_deleted = FALSE OR vt.is_deleted IS NULL);
END;
$function$
-- Function: get_service_provider_companies
CREATE OR REPLACE FUNCTION public.get_service_provider_companies(p_category_id integer DEFAULT NULL::integer, p_subtype_id integer DEFAULT NULL::integer)
RETURNS TABLE(id integer, name text, description text)
LANGUAGE sql
AS $function$
SELECT spc.id, spc.name, spc.description
FROM public.service_provider_companies spc
WHERE
(p_category_id IS NULL OR spc.category_id = p_category_id)
AND (p_subtype_id IS NULL OR spc.subtype_id = p_subtype_id);
$function$
-- Function: create_visitor
CREATE OR REPLACE FUNCTION public.create_visitor(p_first_name character varying, p_last_name character varying, p_contact_number character varying, p_visitor_type_id integer, p_email character varying, p_visiting_from character varying, p_vehicle_number character varying, p_identity_type_id integer, p_identity_number character varying, p_apartment_id uuid, p_created_by uuid, p_delivery_company_id integer, p_image_url character varying)
RETURNS bigint
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id BIGINT;
BEGIN
-- Insert into the visitors table
INSERT INTO public.visitors (
first_name,
last_name,
created_on_utc,
created_by,
is_active,
visitor_type_id,
image_url
)
VALUES (
p_first_name,
p_last_name,
current_timestamp,
p_created_by,
TRUE,
p_visitor_type_id,
p_image_url
)
RETURNING id INTO v_visitor_id; -- Store the newly created visitor's ID
-- Insert into visitor_apartments table
INSERT INTO public.visitor_apartments (
visitor_id,
visitor_apartment_id,
first_seen,
is_blocked
)
VALUES (
v_visitor_id,
p_apartment_id,
current_timestamp, -- Set first_seen as the current timestamp
FALSE -- Default is_blocked to FALSE
);
-- Insert into visitor_details table (if visiting_from or vehicle_number is provided)
IF p_visiting_from IS NOT NULL AND TRIM(p_visiting_from) <> '' OR p_vehicle_number IS NOT NULL AND TRIM(p_vehicle_number) <> '' THEN
INSERT INTO public.visitor_details (
visitor_id,
visiting_from,
vehicle_number,
created_by,
created_on_utc
) VALUES (
v_visitor_id,
p_visiting_from,
p_vehicle_number,
p_created_by,
current_timestamp
);
END IF;
-- Insert into visitor_phones table
INSERT INTO public.visitor_phones (
visitor_id,
phone_number,
is_active,
valid_from
) VALUES (
v_visitor_id,
p_contact_number,
TRUE,
current_timestamp
);
-- Insert into visitor_emails table (if email is provided and not an empty string)
IF p_email IS NOT NULL AND TRIM(p_email) <> '' THEN
INSERT INTO public.visitor_emails (
visitor_id,
email,
is_active,
valid_from
) VALUES (
v_visitor_id,
p_email,
TRUE,
current_timestamp
);
END IF;
-- Insert into visitor_identities table (if identity number is provided and not an empty string)
IF p_identity_number IS NOT NULL AND TRIM(p_identity_number) <> '' THEN
INSERT INTO public.visitor_identities (
visitor_id,
identity_type_id,
identity_number,
is_active,
valid_from,
created_by,
created_on_utc
) VALUES (
v_visitor_id,
p_identity_type_id,
p_identity_number,
TRUE,
current_timestamp,
p_created_by,
current_timestamp
);
END IF;
-- Insert into visitor_delivery_company table (if delivery_company_id is provided and not NULL)
IF p_delivery_company_id IS NOT NULL AND p_delivery_company_id > 0 THEN
INSERT INTO public.visitor_delivery_company (
visitor_id,
delivery_company_id,
is_active,
valid_from
) VALUES (
v_visitor_id,
p_delivery_company_id,
TRUE,
current_timestamp
);
END IF;
-- Return the newly created visitor ID
RETURN v_visitor_id;
END;
$function$
-- Function: create_visitor_with_pending_approval
CREATE OR REPLACE FUNCTION public.create_visitor_with_pending_approval(p_apartment_id uuid, p_first_name text, p_last_name text, p_image_url text, p_contact_number text, p_visitor_type_id integer, p_unit_ids integer[], p_created_by uuid)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id BIGINT;
v_visitor_log_id BIGINT;
v_unit_id INTEGER;
v_pending_status CONSTANT INTEGER := 1; -- Pending
BEGIN
/*
* 1. Find visitor by active phone number
*/
SELECT vp.visitor_id
INTO v_visitor_id
FROM visitor_phones vp
JOIN visitors v ON v.id = vp.visitor_id
WHERE vp.phone_number = p_contact_number
AND vp.is_active = TRUE
AND v.is_deleted = FALSE
LIMIT 1;
/*
* 2. Create visitor if not exists
*/
IF NOT FOUND THEN
INSERT INTO visitors (
first_name,
last_name,
image_url,
visitor_type_id,
created_on_utc,
created_by
)
VALUES (
p_first_name,
p_last_name,
p_image_url,
p_visitor_type_id,
NOW(),
p_created_by
)
RETURNING id INTO v_visitor_id;
INSERT INTO visitor_phones (
visitor_id,
phone_number,
is_active,
valid_from
)
VALUES (
v_visitor_id,
p_contact_number,
TRUE,
NOW()
);
END IF;
/*
* 3. Ensure apartment association exists
*/
IF NOT EXISTS (
SELECT 1
FROM visitor_apartments
WHERE visitor_id = v_visitor_id
AND visitor_apartment_id = p_apartment_id
) THEN
INSERT INTO visitor_apartments (
visitor_id,
visitor_apartment_id,
first_seen
)
VALUES (
v_visitor_id,
p_apartment_id,
NOW()
);
END IF;
/*
* 4. Insert visitor log with PENDING status
*/
INSERT INTO visitor_logs (
visitor_id,
apartment_id,
visitor_type_id,
entry_time,
created_on_utc,
created_by,
visitor_status_id
)
VALUES (
v_visitor_id,
p_apartment_id,
p_visitor_type_id,
NULL, -- Entry happens later
NOW(),
p_created_by,
v_pending_status
)
RETURNING id INTO v_visitor_log_id;
/*
* 5. Insert multi-unit visits with PENDING status
*/
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
INSERT INTO multi_unit_visits (
visitor_log_id,
unit_id,
visitor_statuses,
created_on_utc,
is_deleted,
created_by
)
VALUES (
v_visitor_log_id,
v_unit_id,
v_pending_status,
NOW(),
FALSE,
p_created_by
);
END LOOP;
RETURN v_visitor_log_id;
END;
$function$
-- Function: get_all_tickets
CREATE OR REPLACE FUNCTION public.get_all_tickets(p_apartment_id uuid)
RETURNS TABLE(id uuid, unit_id integer, unit text, title text, description text, ticket_category_id integer, ticket_category text, ticket_priority_id integer, ticket_priority text, ticket_status_id integer, sort_order integer, ticket_status text, ticket_assigned_to integer, ticket_assigned text, ticket_assignee_phone text, is_on_hold boolean, is_re_opened boolean, ticket_number text, ticket_for_id integer, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
LANGUAGE sql
STABLE
AS $function$
SELECT
t.id,
t.unit_id,
u.name AS unit,
t.title,
t.description,
t.ticket_category_id,
tc.name AS ticket_category,
t.ticket_priority_id,
tp.name AS ticket_priority,
t.ticket_status_id,
t.sort_order,
ts.name AS ticket_status,
t.ticket_assigned_to,
COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''),'Not Assigned' ) AS ticket_assigned,
vp.phone_number AS ticket_assignee_phone,
t.is_on_hold,
t.is_re_opened,
t.ticket_number,
t.ticket_for_id,
t.created_by,
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS created_by_name,
t.created_on_utc,
t.modified_by,
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS modified_by_name,
t.modified_on_utc
FROM tickets t
JOIN units u
ON u.id = t.unit_id
AND u.is_deleted = false
JOIN ticket_categories tc
ON tc.id = t.ticket_category_id
AND tc.is_deleted = false
JOIN ticket_priorities tp
ON tp.id = t.ticket_priority_id
AND tp.is_deleted = false
JOIN ticket_statuses ts
ON ts.id = t.ticket_status_id
AND ts.is_deleted = false
LEFT JOIN service_providers sp
ON sp.id = t.ticket_assigned_to
AND sp.is_deleted = false
LEFT JOIN visitors v
ON v.id = sp.visitor_id
AND v.is_deleted = false
LEFT JOIN visitor_phones vp
ON vp.visitor_id = v.id
AND vp.is_active = TRUE
LEFT JOIN users uc
ON uc.id = t.created_by
LEFT JOIN users um
ON um.id = t.modified_by
WHERE
t.apartment_id = p_apartment_id
AND t.is_deleted = false
ORDER BY
t.ticket_status_id,
t.sort_order,
t.created_on_utc DESC;
$function$
-- Function: get_all_tickets
CREATE OR REPLACE FUNCTION public.get_all_tickets(p_apartment_id uuid, p_from_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_to_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_service_provider_id integer DEFAULT NULL::integer, p_is_active boolean DEFAULT false)
RETURNS TABLE(id uuid, unit_id integer, unit text, title text, description text, ticket_category_id integer, ticket_category text, ticket_priority_id integer, ticket_priority text, ticket_status_id integer, sort_order integer, ticket_status text, ticket_assigned_to integer, ticket_assigned text, ticket_assignee_phone text, is_on_hold boolean, is_re_opened boolean, ticket_number text, ticket_for_id integer, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
LANGUAGE sql
STABLE
AS $function$
SELECT
t.id,
t.unit_id,
u.name AS unit,
t.title,
t.description,
t.ticket_category_id,
tc.name AS ticket_category,
t.ticket_priority_id,
tp.name AS ticket_priority,
t.ticket_status_id,
t.sort_order,
ts.name AS ticket_status,
t.ticket_assigned_to,
--COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''),'Not Assigned') AS ticket_assigned,
--NULLIF(concat_ws(' ', v.first_name, v.last_name), '') AS ticket_assigned,
COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''), NULL) AS ticket_assigned,
vp.phone_number AS ticket_assignee_phone,
t.is_on_hold,
t.is_re_opened,
t.ticket_number,
t.ticket_for_id,
t.created_by,
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS created_by_name,
t.created_on_utc,
t.modified_by,
COALESCE(NULLIF(TRIM(um.first_name || ' ' || um.last_name), ''), NULL) AS modified_by_name,
t.modified_on_utc
FROM tickets t
JOIN units u
ON u.id = t.unit_id
AND u.is_deleted = false
JOIN ticket_categories tc
ON tc.id = t.ticket_category_id
AND tc.is_deleted = false
JOIN ticket_priorities tp
ON tp.id = t.ticket_priority_id
AND tp.is_deleted = false
JOIN ticket_statuses ts
ON ts.id = t.ticket_status_id
AND ts.is_deleted = false
LEFT JOIN service_providers sp
ON sp.id = t.ticket_assigned_to
AND sp.is_deleted = false
LEFT JOIN visitors v
ON v.id = sp.visitor_id
AND v.is_deleted = false
LEFT JOIN visitor_phones vp
ON vp.visitor_id = v.id
AND vp.is_active = true
LEFT JOIN users uc
ON uc.id = t.created_by
LEFT JOIN users um
ON um.id = t.modified_by
WHERE
t.apartment_id = p_apartment_id
AND t.is_deleted = false
-- Date filters
AND (p_from_datetime IS NULL OR t.created_on_utc >= p_from_datetime)
AND (p_to_datetime IS NULL OR t.created_on_utc <= p_to_datetime)
-- Service provider filter
AND (p_service_provider_id IS NULL OR t.ticket_assigned_to = p_service_provider_id)
-- Active / All status filter
AND (
p_is_active = false
OR ts.name IN (
'In Queue',
'Assigned',
'In Progress',
'Reopened'
)
)
ORDER BY
t.ticket_status_id,
t.sort_order,
t.created_on_utc DESC;
$function$
-- Function: get_defaulter_contact
CREATE OR REPLACE FUNCTION public.get_defaulter_contact(p_customer_id uuid)
RETURNS text
LANGUAGE plpgsql
AS $function$
DECLARE
v_contact_number text;
BEGIN
SELECT
r.contact_number INTO v_contact_number
FROM
public.units u
INNER JOIN
public.resident_units ru ON u.id = ru.unit_id
INNER JOIN
public.residents r ON ru.resident_id = r.id
WHERE
u.customer_id = p_customer_id
AND u.is_deleted = false
AND ru.is_deleted = false
AND r.is_deleted = false
AND r.contact_number IS NOT NULL
LIMIT 1;
RETURN v_contact_number;
END;
$function$
-- Function: get_service_provider_subcategories
CREATE OR REPLACE FUNCTION public.get_service_provider_subcategories(p_category_id integer)
RETURNS TABLE(id integer, name text, description text)
LANGUAGE sql
AS $function$
SELECT sps.id, sps.name, sps.description
FROM public.service_provider_company_subtypes sps
WHERE sps.category_id = p_category_id;
$function$
-- Function: get_all_escalated_tickets
CREATE OR REPLACE FUNCTION public.get_all_escalated_tickets(p_apartment_id uuid, p_from_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_to_datetime timestamp without time zone DEFAULT NULL::timestamp without time zone, p_service_provider_id integer DEFAULT NULL::integer, p_is_active boolean DEFAULT false)
RETURNS TABLE(id uuid, unit_id integer, unit text, title text, description text, ticket_category_id integer, ticket_category text, ticket_priority_id integer, ticket_priority text, ticket_status_id integer, sort_order integer, ticket_status text, ticket_assigned_to integer, ticket_assigned text, ticket_assignee_phone text, is_on_hold boolean, is_re_opened boolean, ticket_number text, ticket_for_id integer, created_by uuid, created_by_name text, created_on_utc timestamp without time zone, modified_by uuid, modified_by_name text, modified_on_utc timestamp without time zone)
LANGUAGE sql
STABLE
AS $function$
WITH latest_ticket_log AS (
SELECT DISTINCT ON (tl.ticket_id)
tl.ticket_id,
tl.is_escalated
FROM ticket_logs tl
ORDER BY tl.ticket_id, tl.created_on_utc DESC
)
SELECT
t.id,
t.unit_id,
u.name AS unit,
t.title,
t.description,
t.ticket_category_id,
tc.name AS ticket_category,
t.ticket_priority_id,
tp.name AS ticket_priority,
t.ticket_status_id,
t.sort_order,
ts.name AS ticket_status,
t.ticket_assigned_to,
COALESCE(NULLIF(TRIM(v.first_name || ' ' || v.last_name), ''), NULL) AS ticket_assigned,
vp.phone_number AS ticket_assignee_phone,
t.is_on_hold,
t.is_re_opened,
t.ticket_number,
t.ticket_for_id,
t.created_by,
COALESCE(NULLIF(TRIM(uc.first_name || ' ' || uc.last_name), ''), NULL) AS created_by_name,
t.created_on_utc,
t.modified_by,
COALESCE(NULLIF(TRIM(um.first_name || ' ' || um.last_name), ''), NULL) AS modified_by_name,
t.modified_on_utc
FROM tickets t
JOIN latest_ticket_log ltl
ON ltl.ticket_id = t.id
AND ltl.is_escalated = true
JOIN units u
ON u.id = t.unit_id
AND u.is_deleted = false
JOIN ticket_categories tc
ON tc.id = t.ticket_category_id
AND tc.is_deleted = false
JOIN ticket_priorities tp
ON tp.id = t.ticket_priority_id
AND tp.is_deleted = false
JOIN ticket_statuses ts
ON ts.id = t.ticket_status_id
AND ts.is_deleted = false
LEFT JOIN service_providers sp
ON sp.id = t.ticket_assigned_to
AND sp.is_deleted = false
LEFT JOIN visitors v
ON v.id = sp.visitor_id
AND v.is_deleted = false
LEFT JOIN visitor_phones vp
ON vp.visitor_id = v.id
AND vp.is_active = true
LEFT JOIN users uc
ON uc.id = t.created_by
LEFT JOIN users um
ON um.id = t.modified_by
WHERE
t.apartment_id = p_apartment_id
AND t.is_deleted = false
-- Date filters
AND (p_from_datetime IS NULL OR t.created_on_utc >= p_from_datetime)
AND (p_to_datetime IS NULL OR t.created_on_utc <= p_to_datetime)
-- Service provider filter
AND (p_service_provider_id IS NULL OR t.ticket_assigned_to = p_service_provider_id)
-- Active / All status filter
AND (
p_is_active = false
OR ts.name IN (
'In Queue',
'Assigned',
'In Progress',
'Reopened'
)
)
ORDER BY
t.ticket_status_id,
t.sort_order,
t.created_on_utc DESC;
$function$
-- Function: visitor_approval_fcm_tokens_by_visitor_log_id
CREATE OR REPLACE FUNCTION public.visitor_approval_fcm_tokens_by_visitor_log_id(p_visitorlogid bigint)
RETURNS TABLE(id integer, user_ids uuid[], visitor_id integer, first_name text, last_name text, fcm_tokens text[], device_ids text[])
LANGUAGE sql
AS $function$
SELECT
ROW_NUMBER() OVER ()::INT AS id,
ARRAY_AGG(DISTINCT r.user_id) AS user_ids,
v.id AS visitor_id,
v.first_name,
v.last_name,
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.fcm_token), NULL) AS fcm_tokens,
ARRAY_REMOVE(ARRAY_AGG(DISTINCT uft.device_id), NULL) AS device_ids
FROM multi_unit_visits muv
INNER JOIN resident_units ru ON muv.unit_id = ru.unit_id
INNER JOIN residents r ON ru.resident_id = r.id
INNER JOIN visitor_logs vl ON vl.id = muv.visitor_log_id
INNER JOIN visitors v ON v.id = vl.visitor_id
LEFT JOIN user_fcm_tokens uft ON uft.user_id = r.user_id
WHERE muv.visitor_log_id = p_visitorlogid
AND muv.is_deleted = FALSE
AND ru.is_deleted = FALSE
AND r.is_deleted = FALSE
GROUP BY v.id, v.first_name, v.last_name;
$function$
-- Function: create_visitor_with_pending_approval1
CREATE OR REPLACE FUNCTION public.create_visitor_with_pending_approval1(p_apartment_id uuid, p_first_name text, p_last_name text, p_image_url text, p_contact_number text, p_visitor_type_id integer, p_unit_ids integer[], p_created_by uuid, p_sp_category_id integer DEFAULT NULL::integer, p_sp_subtype_id integer DEFAULT NULL::integer, p_sp_company_id integer DEFAULT NULL::integer)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id BIGINT;
v_visitor_log_id BIGINT;
v_unit_id INTEGER;
v_pending_status CONSTANT INTEGER := 1;
BEGIN
-- 1. Find visitor by phone
SELECT vp.visitor_id
INTO v_visitor_id
FROM visitor_phones vp
JOIN visitors v ON v.id = vp.visitor_id
WHERE vp.phone_number = p_contact_number
AND vp.is_active = TRUE
AND v.is_deleted = FALSE
LIMIT 1;
-- 2. Create visitor if not exists
IF NOT FOUND THEN
INSERT INTO visitors (
first_name,
last_name,
image_url,
visitor_type_id,
created_on_utc,
created_by
)
VALUES (
p_first_name,
p_last_name,
p_image_url,
p_visitor_type_id,
NOW(),
p_created_by
)
RETURNING id INTO v_visitor_id;
INSERT INTO visitor_phones (
visitor_id,
phone_number,
is_active,
valid_from
)
VALUES (
v_visitor_id,
p_contact_number,
TRUE,
NOW()
);
END IF;
-- 3. Apartment mapping
IF NOT EXISTS (
SELECT 1 FROM visitor_apartments
WHERE visitor_id = v_visitor_id
AND visitor_apartment_id = p_apartment_id
) THEN
INSERT INTO visitor_apartments (
visitor_id,
visitor_apartment_id,
first_seen
)
VALUES (
v_visitor_id,
p_apartment_id,
NOW()
);
END IF;
-- 4. Visitor log
INSERT INTO visitor_logs (
visitor_id,
apartment_id,
visitor_type_id,
entry_time,
created_on_utc,
created_by,
visitor_status_id
)
VALUES (
v_visitor_id,
p_apartment_id,
p_visitor_type_id,
NULL,
NOW(),
p_created_by,
v_pending_status
)
RETURNING id INTO v_visitor_log_id;
-- 5. Multi-unit visits
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
INSERT INTO multi_unit_visits (
visitor_log_id,
unit_id,
visitor_statuses,
created_on_utc,
is_deleted,
created_by
)
VALUES (
v_visitor_log_id,
v_unit_id,
v_pending_status,
NOW(),
FALSE,
p_created_by
);
END LOOP;
-- 6. OPTIONAL service provider company info
IF p_sp_company_id IS NOT NULL THEN
INSERT INTO visitor_sp_company_visits (
visitor_log_id,
sp_company_category_id,
sp_company_subtype_id,
sp_company_id
)
VALUES (
v_visitor_log_id,
p_sp_category_id,
p_sp_subtype_id,
p_sp_company_id
);
END IF;
RETURN v_visitor_log_id;
END;
$function$
-- Function: create_visitor_with_pending_approval
CREATE OR REPLACE FUNCTION public.create_visitor_with_pending_approval(p_apartment_id uuid, p_first_name text, p_last_name text, p_image_url text, p_contact_number text, p_visitor_type_id integer, p_unit_ids integer[], p_created_by uuid, p_sp_category_id integer DEFAULT NULL::integer, p_sp_subtype_id integer DEFAULT NULL::integer, p_sp_company_id integer DEFAULT NULL::integer)
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE
v_visitor_id BIGINT;
v_visitor_log_id BIGINT;
v_unit_id INTEGER;
v_pending_status CONSTANT INTEGER := 1;
BEGIN
-- 1. Find visitor by phone
SELECT vp.visitor_id
INTO v_visitor_id
FROM visitor_phones vp
JOIN visitors v ON v.id = vp.visitor_id
WHERE vp.phone_number = p_contact_number
AND vp.is_active = TRUE
AND v.is_deleted = FALSE
LIMIT 1;
-- 2. Create visitor if not exists
IF NOT FOUND THEN
INSERT INTO visitors (
first_name,
last_name,
image_url,
visitor_type_id,
created_on_utc,
created_by
)
VALUES (
p_first_name,
p_last_name,
p_image_url,
p_visitor_type_id,
NOW(),
p_created_by
)
RETURNING id INTO v_visitor_id;
INSERT INTO visitor_phones (
visitor_id,
phone_number,
is_active,
valid_from
)
VALUES (
v_visitor_id,
p_contact_number,
TRUE,
NOW()
);
END IF;
-- 3. Apartment mapping
IF NOT EXISTS (
SELECT 1 FROM visitor_apartments
WHERE visitor_id = v_visitor_id
AND visitor_apartment_id = p_apartment_id
) THEN
INSERT INTO visitor_apartments (
visitor_id,
visitor_apartment_id,
first_seen
)
VALUES (
v_visitor_id,
p_apartment_id,
NOW()
);
END IF;
-- 4. Visitor log
INSERT INTO visitor_logs (
visitor_id,
apartment_id,
visitor_type_id,
entry_time,
created_on_utc,
created_by,
visitor_status_id
)
VALUES (
v_visitor_id,
p_apartment_id,
p_visitor_type_id,
NULL,
NOW(),
p_created_by,
v_pending_status
)
RETURNING id INTO v_visitor_log_id;
-- 5. Multi-unit visits
FOREACH v_unit_id IN ARRAY p_unit_ids LOOP
INSERT INTO multi_unit_visits (
visitor_log_id,
unit_id,
visitor_statuses,
created_on_utc,
is_deleted,
created_by
)
VALUES (
v_visitor_log_id,
v_unit_id,
v_pending_status,
NOW(),
FALSE,
p_created_by
);
END LOOP;
-- 6. OPTIONAL service provider company info
IF p_sp_company_id IS NOT NULL THEN
INSERT INTO visitor_sp_company_visits (
visitor_log_id,
sp_company_category_id,
sp_company_subtype_id,
sp_company_id
)
VALUES (
v_visitor_log_id,
p_sp_category_id,
p_sp_subtype_id,
p_sp_company_id
);
END IF;
RETURN v_visitor_log_id;
END;
$function$
-- Procedure: purge_community_organization_data
CREATE OR REPLACE PROCEDURE public.purge_community_organization_data(IN p_organization_ids uuid[] DEFAULT NULL::uuid[])
LANGUAGE plpgsql
AS $procedure$
DECLARE
-- Retention policy (used only for org selection window)
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[] := '{}'; -- company ids under the orgs
v_apartments uuid[] := '{}'; -- apartment-scope ids (companies + apartments under orgs)
-- Events
v_event_ids uuid[] := '{}';
v_event_occurrence_ids int[] := '{}';
-- Tickets
v_ticket_ids uuid[] := '{}';
-- Service providers and related
v_service_provider_ids int[] := '{}';
-- Units and related
v_unit_ids int[] := '{}';
-- Residents and related
v_resident_ids int[] := '{}';
-- Visitors and related
v_visitor_ids int[] := '{}';
v_visitor_log_ids int[] := '{}';
BEGIN
START TRANSACTION;
--------------------------------------------------------------------
-- 1) Resolve target organizations and containers (companies/apartments)
--------------------------------------------------------------------
IF p_organization_ids IS NULL OR array_length(p_organization_ids, 1) IS NULL THEN
SELECT COALESCE(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 community cleanup.';
COMMIT;
RETURN;
END IF;
-- Companies by organization (community uses company_id as scope for many tables)
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 community purge. Orgs: %', v_orgs;
COMMIT;
RETURN;
END IF;
-- Apartments by organization (some tables use apartment_id = companies.id; also apartments can have organization_id)
SELECT COALESCE(array_agg(DISTINCT x.id), '{}')
INTO v_apartments
FROM (
SELECT UNNEST(v_companies) AS id
UNION
SELECT a.id FROM apartments a WHERE a.organization_id = ANY(v_orgs)
) AS x;
RAISE NOTICE 'Community purge targets - Organizations: %; Companies: %; Apartments: %', v_orgs, v_companies, v_apartments;
--------------------------------------------------------------------
-- 2) Collect IDs needed for childβparent deletions
--------------------------------------------------------------------
-- Events and occurrences (company scoped)
SELECT COALESCE(array_agg(id), '{}')
INTO v_event_ids
FROM events
WHERE company_id = ANY(v_companies);
SELECT COALESCE(array_agg(id), '{}')
INTO v_event_occurrence_ids
FROM event_occurrences
WHERE event_id = ANY(v_event_ids);
-- Tickets (apartment scoped)
SELECT COALESCE(array_agg(id), '{}')
INTO v_ticket_ids
FROM tickets
WHERE apartment_id = ANY(v_apartments);
-- Service providers (apartment scoped)
SELECT COALESCE(array_agg(id), '{}')
INTO v_service_provider_ids
FROM service_providers
WHERE apartment_id = ANY(v_apartments);
-- Units (apartment scoped)
SELECT COALESCE(array_agg(id), '{}')
INTO v_unit_ids
FROM units
WHERE apartment_id = ANY(v_apartments);
-- Residents (apartment scoped)
SELECT COALESCE(array_agg(id), '{}')
INTO v_resident_ids
FROM residents
WHERE apartment_id = ANY(v_apartments);
-- Visitors and their logs (apartment scoped)
SELECT COALESCE(array_agg(id), '{}')
INTO v_visitor_ids
FROM visitors
WHERE apartment_id = ANY(v_apartments);
SELECT COALESCE(array_agg(id), '{}')
INTO v_visitor_log_ids
FROM visitor_logs
WHERE visitor_id = ANY(v_visitor_ids);
--------------------------------------------------------------------
-- 3) Purge in strict child β parent order (avoid FK violations)
--------------------------------------------------------------------
-- Ticket OTPs β Tickets β Ticket workflow
DELETE FROM ticket_service_provider_otps
WHERE ticket_id = ANY(v_ticket_ids);
DELETE FROM tickets
WHERE id = ANY(v_ticket_ids);
DELETE FROM ticket_workflow
WHERE apartment_id = ANY(v_apartments);
-- Meeting data tied to event occurrences: agenda, notes, participants, actions
DELETE FROM meeting_agenda_items
WHERE occurrence_id = ANY(v_event_occurrence_ids);
DELETE FROM meeting_notes
WHERE occurrence_id = ANY(v_event_occurrence_ids);
DELETE FROM meeting_participants
WHERE occurrence_id = ANY(v_event_occurrence_ids);
DELETE FROM meeting_action_items
WHERE occurrence_id = ANY(v_event_occurrence_ids);
-- Event participants referencing event_id
DELETE FROM event_participants
WHERE event_id = ANY(v_event_ids);
-- Event occurrences β events
DELETE FROM event_occurrences
WHERE id = ANY(v_event_occurrence_ids);
DELETE FROM events
WHERE id = ANY(v_event_ids);
-- Visitor related: vehicles β multi_unit_visits β guest_approvals β visitor_logs β contacts/approvals β visitors
DELETE FROM visitor_vehicles
WHERE visitor_log_id = ANY(v_visitor_log_ids);
DELETE FROM multi_unit_visits
WHERE visitor_log_id = ANY(v_visitor_log_ids)
OR unit_id = ANY(v_unit_ids);
DELETE FROM guest_approvals
WHERE visitor_log_id = ANY(v_visitor_log_ids);
-- Partitioned table; deleting from parent will route to partitions
DELETE FROM visitor_logs
WHERE id = ANY(v_visitor_log_ids);
DELETE FROM visitor_logs_old
WHERE visitor_id = ANY(v_visitor_ids);
DELETE FROM visitor_contacts
WHERE visitor_id = ANY(v_visitor_ids);
DELETE FROM visitor_approvals
WHERE visitor_id = ANY(v_visitor_ids);
DELETE FROM visitors
WHERE id = ANY(v_visitor_ids);
-- Service provider related: verifications β ticket categories β logs β unit_service_providers β pins β service_providers
DELETE FROM service_provider_verifications
WHERE "service_Provider_id" = ANY(v_service_provider_ids);
DELETE FROM service_provider_ticket_categories
WHERE service_provider_id = ANY(v_service_provider_ids);
-- Partitioned table; deleting from parent will route to partitions
DELETE FROM service_provider_logs
WHERE service_provider_id = ANY(v_service_provider_ids);
DELETE FROM unit_service_providers
WHERE service_provider_id = ANY(v_service_provider_ids)
OR unit_id = ANY(v_unit_ids);
DELETE FROM pins
WHERE service_provider_id = ANY(v_service_provider_ids)
OR visitor_id IN (SELECT id FROM visitors WHERE apartment_id = ANY(v_apartments));
DELETE FROM service_providers
WHERE id = ANY(v_service_provider_ids);
-- Unit related: vehicles β unit vehicle limits β resident_units β units
DELETE FROM vehicles
WHERE unit_id = ANY(v_unit_ids);
DELETE FROM unit_vehicle_limits
WHERE unit_id = ANY(v_unit_ids);
DELETE FROM resident_units
WHERE unit_id = ANY(v_unit_ids)
OR resident_id = ANY(v_resident_ids);
DELETE FROM units
WHERE id = ANY(v_unit_ids);
-- Residents: tokens β residents
DELETE FROM resident_tokens
WHERE resident_id = ANY(v_resident_ids);
DELETE FROM residents
WHERE id = ANY(v_resident_ids);
-- Gates, Floors, Buildings (all apartment scoped; ensure child tables removed first)
DELETE FROM gates
WHERE apartment_id = ANY(v_apartments);
DELETE FROM floors
WHERE apartment_id = ANY(v_apartments);
DELETE FROM buildings
WHERE apartment_id = ANY(v_apartments);
-- Committee members and portfolios (apartment scoped)
DELETE FROM committee_members
WHERE apartment_id = ANY(v_apartments);
DELETE FROM portfolios
WHERE apartment_id = ANY(v_apartments);
-- Water tanker deliveries (company scoped)
DELETE FROM water_tanker_deliveries
WHERE company_id = ANY(v_companies);
RAISE NOTICE 'Community purge complete for companies: % (orgs: %).', v_companies, v_orgs;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE NOTICE 'purge_community_organization_data failed: %', SQLERRM;
-- Optionally rethrow
-- RAISE;
END;
$procedure$
-- Procedure: create_vehicle
CREATE OR REPLACE PROCEDURE public.create_vehicle(IN p_vehicle_number text, IN p_vehicle_type_id integer, IN p_unit_id integer, IN p_vehicle_rf_id text, IN p_vehicle_rf_id_secretcode text, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
INSERT INTO public.vehicles (
vehicle_number,
vehicle_type_id,
unit_id,
vehicle_rf_id,
vehicle_rf_id_secretcode,
created_on_utc,
is_deleted,
created_by
) VALUES (
p_vehicle_number,
p_vehicle_type_id,
p_unit_id,
p_vehicle_rf_id,
p_vehicle_rf_id_secretcode,
now() at time zone 'utc',
false,
p_created_by
);
END;
$procedure$
-- Procedure: delete_residents_by_apartment
CREATE OR REPLACE PROCEDURE public.delete_residents_by_apartment(IN p_apartment_id uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
DELETE FROM public.resident_units
WHERE resident_id IN (
SELECT id FROM public.residents
WHERE apartment_id = p_apartment_id
);
DELETE FROM public.residents
WHERE apartment_id = p_apartment_id;
END;
$procedure$
-- Procedure: hard_delete_apartment_data
CREATE OR REPLACE PROCEDURE public.hard_delete_apartment_data(IN p_apartment_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_id uuid;
BEGIN
-- Get the company_id from the apartment
SELECT company_id INTO v_company_id
FROM apartments
WHERE id = p_apartment_id;
-- === Delete apartment-linked data ===
DELETE FROM visitors
WHERE apartment_id = p_apartment_id;
DELETE FROM units
WHERE apartment_id = p_apartment_id;
DELETE FROM tickets
WHERE apartment_id = p_apartment_id;
DELETE FROM ticket_workflow
WHERE apartment_id = p_apartment_id;
DELETE FROM service_providers
WHERE apartment_id = p_apartment_id;
DELETE FROM residents
WHERE apartment_id = p_apartment_id;
DELETE FROM resident_requests
WHERE apartment_id = p_apartment_id;
DELETE FROM gates
WHERE apartment_id = p_apartment_id;
DELETE FROM floors
WHERE apartment_id = p_apartment_id;
DELETE FROM buildings
WHERE apartment_id = p_apartment_id;
-- === Delete company-linked data (derived from apartment) ===
IF v_company_id IS NOT NULL THEN
DELETE FROM events
WHERE company_id = v_company_id;
DELETE FROM users
WHERE company_id = v_company_id;
END IF;
-- Delete apartment record last
DELETE FROM apartments
WHERE id = p_apartment_id;
RAISE NOTICE 'Deleted data for apartment_id: % and company_id: %', p_apartment_id, v_company_id;
END;
$procedure$
-- Procedure: hard_delete_org_community
CREATE OR REPLACE PROCEDURE public.hard_delete_org_community(IN p_organization_id uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_apartment_id uuid;
BEGIN
-- Step 1: Loop over all "apartments" (companies) under this org
FOR v_apartment_id IN
SELECT id FROM companies WHERE organization_id = p_organization_id
LOOP
-- Delete data linked to apartment (i.e., company)
DELETE FROM visitors WHERE apartment_id = v_apartment_id;
DELETE FROM units WHERE apartment_id = v_apartment_id;
DELETE FROM tickets WHERE apartment_id = v_apartment_id;
DELETE FROM ticket_workflow WHERE apartment_id = v_apartment_id;
DELETE FROM service_providers WHERE apartment_id = v_apartment_id;
DELETE FROM residents WHERE apartment_id = v_apartment_id;
DELETE FROM resident_requests WHERE apartment_id = v_apartment_id;
DELETE FROM gates WHERE apartment_id = v_apartment_id;
DELETE FROM floors WHERE apartment_id = v_apartment_id;
DELETE FROM buildings WHERE apartment_id = v_apartment_id;
-- Apartment entity (i.e., company-level data)
DELETE FROM apartments WHERE id = v_apartment_id;
DELETE FROM events WHERE company_id = v_apartment_id;
DELETE FROM users WHERE company_id = v_apartment_id;
DELETE FROM companies WHERE id = v_apartment_id;
END LOOP;
-- Step 2: Delete the organization
DELETE FROM organizations WHERE id = p_organization_id;
RAISE NOTICE 'Deleted community data for organization_id: %', p_organization_id;
END;
$procedure$
-- Procedure: hard_delete_org_inventory
CREATE OR REPLACE PROCEDURE public.hard_delete_org_inventory(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 orders WHERE company_id = v_company_id;
DELETE FROM company_products WHERE company_id = v_company_id;
DELETE FROM company_categories WHERE company_id = v_company_id;
DELETE FROM product_tax_categories WHERE company_id = v_company_id;
DELETE FROM units 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 inventory data for organization_id: %', p_organization_id;
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
-- Bill Details
DELETE FROM bill_details
WHERE bill_header_id IN (
SELECT id FROM bill_headers WHERE company_id = v_company_id
);
-- Bill Payment Details
DELETE FROM bill_payment_details
WHERE bill_payment_header_id IN (
SELECT id FROM bill_payment_headers WHERE company_id = v_company_id
);
-- Draft Bill Details
DELETE FROM draft_bill_details
WHERE bill_header_id IN (
SELECT id FROM draft_bill_headers WHERE company_id = v_company_id
);
-- Vendor Note Details
DELETE FROM vendor_note_details
WHERE vendor_note_header_id IN (
SELECT id FROM vendor_note_headers WHERE company_id = v_company_id
);
-- Gate Pass Details
DELETE FROM gate_pass_details
WHERE gate_pass_header_id IN (
SELECT id FROM gate_pass_headers WHERE company_id = v_company_id
);
-- Remaining headers and master data
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_org_sales
CREATE OR REPLACE PROCEDURE public.hard_delete_org_sales(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 CUSTOMER NOTE DETAILS ==========
DELETE FROM customer_note_details
WHERE customer_note_header_id IN (
SELECT id FROM customer_note_headers WHERE company_id = v_company_id
);
DELETE FROM customer_note_headers WHERE company_id = v_company_id;
DELETE FROM customer_note_work_flows WHERE company_id = v_company_id;
-- ========== DELETE CUSTOMERS ==========
DELETE FROM customers WHERE company_id = v_company_id;
-- ========== DELETE GROUP INVOICES ==========
DELETE FROM group_invoice_details WHERE company_id = v_company_id;
DELETE FROM group_invoice_header_ids WHERE company_id = v_company_id;
DELETE FROM group_invoice_headers WHERE company_id = v_company_id;
DELETE FROM group_invoice_templates WHERE company_id = v_company_id;
-- ========== DELETE INVOICE ENTRIES ==========
DELETE FROM invoice_details
WHERE invoice_header_id IN (
SELECT id FROM invoice_headers WHERE company_id = v_company_id
);
DELETE FROM invoice_headers WHERE company_id = v_company_id;
DELETE FROM invoice_header_ids WHERE company_id = v_company_id;
DELETE FROM invoice_payment_details
WHERE invoice_payment_header_id IN (
SELECT id FROM invoice_payment_headers WHERE company_id = v_company_id
);
DELETE FROM invoice_payment_headers WHERE company_id = v_company_id;
DELETE FROM invoice_payment_header_ids WHERE company_id = v_company_id;
DELETE FROM invoice_voucher_ids WHERE company_id = v_company_id;
DELETE FROM invoice_workflow WHERE company_id = v_company_id;
-- ========== DELETE DRAFT INVOICES ==========
DELETE FROM draft_invoice_details
WHERE invoice_header_id IN (
SELECT id FROM draft_invoice_headers WHERE company_id = v_company_id
);
DELETE FROM draft_invoice_headers WHERE company_id = v_company_id;
DELETE FROM draft_invoice_header_ids WHERE company_id = v_company_id;
-- ========== DELETE GATE PASS ==========
DELETE FROM gate_pass_details
WHERE gate_pass_header_id IN (
SELECT id FROM gate_pass_headers WHERE company_id = v_company_id
);
DELETE FROM gate_pass_headers WHERE company_id = v_company_id;
-- ========== DELETE CONFIG & USERS ==========
DELETE FROM penalty_configs WHERE company_id = v_company_id;
DELETE FROM recurring_sales_schedules WHERE company_id = v_company_id;
DELETE FROM users WHERE company_id = v_company_id;
DELETE FROM companies WHERE id = v_company_id;
RAISE NOTICE 'Deleted sales data for company_id %', v_company_id;
END LOOP;
DELETE FROM organizations WHERE id = p_organization_id;
RAISE NOTICE 'Deleted sales data for organization_id %', p_organization_id;
END;
$procedure$
-- Procedure: insert_visitor_vehicle
CREATE OR REPLACE PROCEDURE public.insert_visitor_vehicle(IN p_visitor_log_id integer, IN p_vehicle_number text DEFAULT NULL::text, IN p_vehicle_type text DEFAULT NULL::text)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_created_by UUID; -- Declare variable to store created_by ID
BEGIN
-- Fetch created_by from the visitor_log table based on the visitor_log_id
SELECT created_by INTO v_created_by
FROM public.visitor_logs
WHERE id = p_visitor_log_id;
-- If no created_by found (i.e., visitor_log_id does not exist), set default value (e.g., 0 or NULL)
IF v_created_by IS NULL THEN
RAISE NOTICE 'No created_by found for visitor_log_id: %, using default value', p_visitor_log_id;
v_created_by := 'dd4f94f2-0f79-4748-b94b-bf935e3944c7'; -- System Id
END IF;
-- Insert the data into the VisitorVehicles table
INSERT INTO public.visitor_vehicles (visitor_log_id, vehicle_number, vehicle_type, created_on_utc, created_by)
VALUES (p_visitor_log_id, p_vehicle_number, P_vehicle_type, NOW(), v_created_by);
-- Optionally, raise a notice confirming the insertion
RAISE NOTICE 'Inserted Vehicle: %, % for VisitorLogId: %, CreatedBy: %',
COALESCE(p_vehicle_number, 'NULL'),
COALESCE(P_vehicle_type, 'NULL'),
p_visitor_log_id,
v_created_by;
END;
$procedure$
-- Procedure: upsert_meeting_action_items
CREATE OR REPLACE PROCEDURE public.upsert_meeting_action_items(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_action_items jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_occurrence_id INT;
v_item jsonb;
v_existing_ids INT[];
BEGIN
-- Step 1: Get or create event occurrence
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
-- Step 2: Get current action item IDs
SELECT array_agg(id)
INTO v_existing_ids
FROM meeting_action_items
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
-- Step 3: Loop through each item
FOR v_item IN SELECT * FROM jsonb_array_elements(p_action_items)
LOOP
IF (v_item->>'id')::int < 0 THEN
-- Insert new item (negative ID indicates new)
INSERT INTO meeting_action_items (
occurrence_id,
action_description,
assigned_to_user_id,
due_date,
status,
created_on_utc,
created_by,
is_deleted
)
VALUES (
v_occurrence_id,
v_item->>'action_description',
(v_item->>'assigned_to_user_id')::uuid,
NULLIF(v_item->>'due_date', '')::timestamp,
v_item->>'status',
now(),
p_user_uuid,
false
);
ELSE
-- Update existing action item (positive ID)
UPDATE meeting_action_items
SET
action_description = v_item->>'action_description',
assigned_to_user_id = (v_item->>'assigned_to_user_id')::uuid,
due_date = NULLIF(v_item->>'due_date', '')::timestamp,
status = v_item->>'status',
modified_on_utc = now(),
modified_by = p_user_uuid
WHERE id = (v_item->>'id')::int
AND occurrence_id = v_occurrence_id;
-- Mark as handled
v_existing_ids := array_remove(v_existing_ids, (v_item->>'id')::int);
END IF;
END LOOP;
-- Step 4: Soft delete action items that were removed
IF v_existing_ids IS NOT NULL THEN
UPDATE meeting_action_items
SET is_deleted = true,
deleted_on_utc = now(),
modified_by = p_user_uuid
WHERE id = ANY(v_existing_ids);
END IF;
END;
$procedure$
-- Procedure: upsert_meeting_agenda_items
CREATE OR REPLACE PROCEDURE public.upsert_meeting_agenda_items(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_agenda_items jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_occurrence_id INT;
v_item jsonb;
v_existing_ids INT[];
BEGIN
-- Step 1: Get or create occurrence
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
-- Step 2: Track existing agenda item IDs for this occurrence
SELECT array_agg(id)
INTO v_existing_ids
FROM meeting_agenda_items
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
-- Step 3: Loop through input items
FOR v_item IN SELECT * FROM jsonb_array_elements(p_agenda_items)
LOOP
IF (v_item->>'id')::int < 0 THEN
-- Insert new item (negative ID indicates new)
INSERT INTO meeting_agenda_items (
occurrence_id, item_text, order_no, created_on_utc, created_by, is_deleted
)
VALUES (
v_occurrence_id,
v_item->>'item_text',
(v_item->>'order_no')::int,
now(),
p_user_uuid,
false
);
ELSE
-- Update existing item (positive ID)
UPDATE meeting_agenda_items
SET item_text = v_item->>'item_text',
order_no = (v_item->>'order_no')::int,
modified_on_utc = now(),
modified_by = p_user_uuid
WHERE id = (v_item->>'id')::int AND occurrence_id = v_occurrence_id;
-- Remove updated ID from existing ID list (so itβs not soft-deleted later)
v_existing_ids := array_remove(v_existing_ids, (v_item->>'id')::int);
END IF;
END LOOP;
-- Step 4: Soft delete missing items (not sent in input)
UPDATE meeting_agenda_items
SET is_deleted = true,
deleted_on_utc = now(),
modified_by = p_user_uuid
WHERE id = ANY(v_existing_ids);
END;
$procedure$
-- Procedure: upsert_meeting_participants
CREATE OR REPLACE PROCEDURE public.upsert_meeting_participants(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_participant_user_ids jsonb)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_occurrence_id INT;
v_user_id UUID;
v_existing_ids UUID[];
BEGIN
-- Get or create occurrence
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
RAISE NOTICE 'Occurrence ID: %', v_occurrence_id;
-- Existing participants
SELECT array_agg(user_id) INTO v_existing_ids
FROM meeting_participants
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
RAISE NOTICE 'Existing participants count: %', COALESCE(array_length(v_existing_ids, 1), 0);
-- Loop input participants
FOR v_user_id IN
SELECT (elem->>'user_id')::uuid
FROM jsonb_array_elements(p_participant_user_ids) AS elem
LOOP
RAISE NOTICE 'Processing user: %', v_user_id;
IF v_existing_ids IS NULL OR NOT v_user_id = ANY(v_existing_ids) THEN
INSERT INTO meeting_participants (
occurrence_id, user_id, created_on_utc, created_by, is_deleted
)
VALUES (v_occurrence_id, v_user_id, now(), p_user_uuid, false);
RAISE NOTICE 'Inserted user %', v_user_id;
ELSE
UPDATE meeting_participants
SET modified_on_utc = now(),
modified_by = p_user_uuid
WHERE occurrence_id = v_occurrence_id
AND user_id = v_user_id;
RAISE NOTICE 'Updated user %', v_user_id;
v_existing_ids := array_remove(v_existing_ids, v_user_id);
END IF;
END LOOP;
-- Soft delete removed participants
IF v_existing_ids IS NOT NULL THEN
UPDATE meeting_participants
SET is_deleted = true,
deleted_on_utc = now(),
modified_by = p_user_uuid
WHERE occurrence_id = v_occurrence_id
AND user_id = ANY(v_existing_ids);
RAISE NOTICE 'Soft deleted users: %', v_existing_ids;
END IF;
END;
$procedure$
-- Procedure: create_service_provider
CREATE OR REPLACE PROCEDURE public.create_service_provider(IN p_first_name character varying, IN p_last_name character varying, IN p_email character varying, IN p_visiting_from character varying, IN p_contact_number character varying, IN p_permanent_address_id uuid, IN p_present_address_id uuid, IN p_service_provider_type_id integer, IN p_service_provider_sub_type_id integer, IN p_vehicle_number character varying, IN p_identity_type_id integer, IN p_identity_number character varying, IN p_validity_date date, IN p_police_verification_status boolean, IN p_is_hireable boolean, IN p_is_visible boolean, IN p_is_frequent_visitor boolean, IN p_apartment_id uuid, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
pin_code character varying;
BEGIN
-- Generate the random pin code
pin_code := public.generate_random_pin();
INSERT INTO public.service_providers (
first_name,
last_name,
email,
visiting_from,
contact_number,
permanent_address_id,
present_address_id,
service_provider_type_id,
service_provider_sub_type_id,
vehicle_number,
identity_type_id,
identity_number,
validity_date,
policeverification_status,
is_hireable,
is_visible,
is_frequent_visitor,
apartment_id,
pin,
created_on_utc,
created_by
)
VALUES (
p_first_name,
p_last_name,
p_email,
p_visiting_from,
p_contact_number,
p_permanent_address_id,
p_present_address_id,
p_service_provider_type_id,
p_service_provider_sub_type_id,
p_vehicle_number,
p_identity_type_id,
p_identity_number,
p_validity_date,
p_police_verification_status,
p_is_hireable,
p_is_visible,
p_is_frequent_visitor,
p_apartment_id,
pin_code,
current_timestamp,
p_created_by
);
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; -- Variable to hold company IDs associated with the organization
BEGIN
-- Get 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 users associated with the company
DELETE FROM public.users
WHERE users.company_id = p_company_id;
-- Delete from companies
DELETE FROM public.companies
WHERE public.companies.id = p_company_id;
END LOOP;
-- 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_organization
CREATE OR REPLACE PROCEDURE public.initialize_organization(IN p_id uuid, IN p_name text, IN p_company_guids 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 character varying, IN p_email character varying, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_company_id uuid;
v_company_name text;
v_company_ids uuid[];
v_company_names text[];
i integer;
v_organization_exists boolean;
v_user_exists boolean;
v_company_exists boolean;
v_existing_user_id uuid;
BEGIN
-- Check if organization already exists
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 % or Name % already exists. Skipping organization creation.', p_id, p_name;
ELSE
-- Insert organization if it doesn't exist
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_guids, ',');
v_company_names := string_to_array(p_company_names, ',');
-- Initialize companies with duplicate checks
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];
-- Check if company already exists
SELECT EXISTS (
SELECT 1 FROM public.companies
WHERE id = v_company_id
) INTO v_company_exists;
IF NOT v_company_exists THEN
CALL public.initialize_company(
v_company_id,
p_id,
true, -- Indicates it's an apartment
v_company_name,
p_created_by
);
RAISE NOTICE 'Initialized company: % with ID: %', v_company_name, v_company_id;
ELSE
RAISE NOTICE 'Company % (ID: %) already exists for organization %. Skipping initialization.',
v_company_name, v_company_id, p_id;
END IF;
END LOOP;
-- Assign the first company ID from the array
v_company_id := v_company_ids[1];
-- Check if user already exists and get the existing ID if found
SELECT id INTO v_existing_user_id FROM public.users
WHERE id = p_user_id OR email = p_email
LIMIT 1;
v_user_exists := (v_existing_user_id IS NOT NULL);
IF NOT v_user_exists THEN
-- Create user if they don't exist
INSERT INTO public.users (
id, email, contact_number, first_name, last_name,
password_hash, created_on_utc, created_by, company_id, is_owner
) VALUES (
p_user_id, p_email, p_phone_number, p_user_first_name, p_user_last_name,
'', NOW(), p_created_by, v_company_id, false
);
RAISE NOTICE 'Created user % (%) for organization %', p_email, p_user_id, p_id;
ELSE
-- Update existing user's company association
UPDATE public.users
SET company_id = v_company_id
WHERE id = v_existing_user_id;
RAISE NOTICE 'updated user % (%) for organization %', p_email, p_user_id, p_id;
END IF;
RAISE NOTICE 'Organization initialization process completed for % (ID: %)', p_name, p_id;
END;
$procedure$
-- Procedure: update_ticket_status
CREATE OR REPLACE PROCEDURE public.update_ticket_status(IN p_apartment_id uuid, IN p_ticket_status_id integer, IN p_ticket_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_ticket RECORD;
BEGIN
-- Process each ticket in the given list
FOR v_ticket IN
SELECT id, ticket_status_id
FROM public.tickets
WHERE id = ANY(p_ticket_ids)
LOOP
-- Validate that the status transition is allowed
IF EXISTS (
SELECT 1 FROM public.ticket_workflow
WHERE status = v_ticket.ticket_status_id
AND next_status = p_ticket_status_id
AND apartment_id = p_apartment_id
) THEN
-- Update the ticket status
UPDATE public.tickets
SET ticket_status_id = p_ticket_status_id,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_ticket.id;
RAISE NOTICE 'Ticket % updated to status %', v_ticket.id, p_ticket_status_id;
ELSE
RAISE WARNING 'Invalid status transition for Ticket ID: %', v_ticket.id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: upsert_meeting_note
CREATE OR REPLACE PROCEDURE public.upsert_meeting_note(IN p_event_uuid uuid, IN p_occ_date date, IN p_user_uuid uuid, IN p_note_text text)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_occurrence_id INT;
v_note_id INT;
BEGIN
-- Get or create occurrence
v_occurrence_id := check_or_create_event_occurrence(p_event_uuid, p_occ_date, p_user_uuid);
-- Check if note exists for occurrence
SELECT id INTO v_note_id
FROM meeting_notes
WHERE occurrence_id = v_occurrence_id AND is_deleted = false;
IF v_note_id IS NOT NULL THEN
-- Update existing note
UPDATE meeting_notes
SET note_text = p_note_text,
modified_on_utc = now(),
modified_by = p_user_uuid
WHERE id = v_note_id;
ELSE
-- Insert new note
INSERT INTO meeting_notes (
occurrence_id, note_text, created_on_utc, created_by, is_deleted
) VALUES (
v_occurrence_id, p_note_text, now(), p_user_uuid, false
);
END IF;
END;
$procedure$
-- Procedure: initialize_company
CREATE OR REPLACE PROCEDURE public.initialize_company(IN p_company_id uuid, IN p_organization_id uuid, IN p_is_apartment boolean, IN p_name text, IN p_created_by uuid)
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Insert into companies table
INSERT INTO public.companies (
id,
organization_id,
is_apartment,
name,
created_on_utc,
created_by
) VALUES (
p_company_id,
p_organization_id,
p_is_apartment,
p_name,
NOW(),
p_created_by
);
-- If this company is an apartment, insert into apartments table as well
IF p_is_apartment THEN
INSERT INTO public.apartments (
id,
organization_id,
name,
apartment_type_id,
created_on_utc,
created_by
) VALUES (
p_company_id,
p_organization_id,
p_name,
1, -- default apartment_type_id
NOW(),
p_created_by
);
END IF;
END;
$procedure$
-- Procedure: update_ticket_status_with_logs
CREATE OR REPLACE PROCEDURE public.update_ticket_status_with_logs(IN p_apartment_id uuid, IN p_ticket_status_id integer, IN p_ticket_ids uuid[], IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_ticket RECORD;
v_old_status int;
v_is_on_hold bool;
v_new_log_id uuid;
BEGIN
-- Process each ticket in the given list
FOR v_ticket IN
SELECT id, ticket_status_id, is_on_hold
FROM public.tickets
WHERE id = ANY(p_ticket_ids)
LOOP
v_old_status := v_ticket.ticket_status_id;
v_is_on_hold := v_ticket.is_on_hold;
-- Validate status transition
IF EXISTS (
SELECT 1
FROM public.ticket_workflow
WHERE status = v_old_status
AND next_status = p_ticket_status_id
AND apartment_id = p_apartment_id
) THEN
-- Update the ticket
UPDATE public.tickets
SET ticket_status_id = p_ticket_status_id,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_ticket.id;
-- Insert log entry
v_new_log_id := gen_random_uuid();
INSERT INTO public.ticket_logs (
id,
ticket_id,
old_status_id,
new_status_id,
is_on_hold,
created_by,
created_on_utc
)
VALUES (
v_new_log_id,
v_ticket.id,
v_old_status,
p_ticket_status_id,
v_is_on_hold,
p_modified_by,
now()
);
RAISE NOTICE 'Ticket % updated from % to %', v_ticket.id, v_old_status, p_ticket_status_id;
ELSE
RAISE WARNING 'Invalid status transition for Ticket ID: % (old=% new=%)',
v_ticket.id, v_old_status, p_ticket_status_id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: toggle_ticket_hold_with_logs
CREATE OR REPLACE PROCEDURE public.toggle_ticket_hold_with_logs(IN p_ticket_ids uuid[], IN p_hold boolean, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_ticket RECORD;
v_log_id uuid;
BEGIN
FOR v_ticket IN
SELECT id, ticket_status_id, is_on_hold
FROM public.tickets
WHERE id = ANY(p_ticket_ids)
LOOP
----------------------------------------------------------------
-- Skip if state is already same
----------------------------------------------------------------
IF v_ticket.is_on_hold = p_hold THEN
IF p_hold THEN
RAISE NOTICE 'Ticket % is already ON HOLD', v_ticket.id;
ELSE
RAISE NOTICE 'Ticket % is already NOT on hold', v_ticket.id;
END IF;
CONTINUE;
END IF;
----------------------------------------------------------------
-- Update ticket hold state
----------------------------------------------------------------
UPDATE public.tickets
SET is_on_hold = p_hold,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_ticket.id;
----------------------------------------------------------------
-- Insert TicketLog
----------------------------------------------------------------
v_log_id := gen_random_uuid();
INSERT INTO public.ticket_logs (
id,
ticket_id,
old_status_id,
new_status_id,
is_on_hold,
created_by,
created_on_utc
)
VALUES (
v_log_id,
v_ticket.id,
v_ticket.ticket_status_id,
v_ticket.ticket_status_id, -- no status change
p_hold, -- NEW hold value
p_modified_by,
now()
);
----------------------------------------------------------------
-- Messages
----------------------------------------------------------------
IF p_hold THEN
RAISE NOTICE 'Ticket % set to ON HOLD', v_ticket.id;
ELSE
RAISE NOTICE 'Ticket % UN-HOLD done', v_ticket.id;
END IF;
END LOOP;
END;
$procedure$
-- Procedure: mark_ticket_reopened_with_logs
CREATE OR REPLACE PROCEDURE public.mark_ticket_reopened_with_logs(IN p_ticket_ids uuid[], IN p_reopen_status_id integer, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_ticket RECORD;
v_log_id uuid;
BEGIN
FOR v_ticket IN
SELECT id, ticket_status_id, is_re_opened
FROM public.tickets
WHERE id = ANY(p_ticket_ids)
LOOP
----------------------------------------------------------------
-- Skip if already reopened once (idempotent)
----------------------------------------------------------------
IF v_ticket.is_re_opened THEN
RAISE NOTICE 'Ticket % is already marked as reopened', v_ticket.id;
CONTINUE;
END IF;
----------------------------------------------------------------
-- Update ticket (set reopened flag + change status)
----------------------------------------------------------------
UPDATE public.tickets
SET
ticket_status_id = p_reopen_status_id, -- dynamic
is_re_opened = true, -- YOUR COLUMN NAME
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_ticket.id;
----------------------------------------------------------------
-- Insert Ticket Log (NO EXTRA BOOLEAN)
----------------------------------------------------------------
v_log_id := gen_random_uuid();
INSERT INTO public.ticket_logs (
id,
ticket_id,
old_status_id,
new_status_id,
is_on_hold,
created_by,
created_on_utc
)
VALUES (
v_log_id,
v_ticket.id,
v_ticket.ticket_status_id,
p_reopen_status_id, -- new status
false, -- hold unchanged
p_modified_by,
now()
);
RAISE NOTICE 'Ticket % marked as REOPENED', v_ticket.id;
END LOOP;
END;
$procedure$
-- Procedure: assign_ticket_with_logs
CREATE OR REPLACE PROCEDURE public.assign_ticket_with_logs(IN p_ticket_id uuid, IN p_service_provider_id integer, IN p_new_status_id integer, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_ticket RECORD;
v_log_id uuid;
BEGIN
----------------------------------------------------------------
-- Fetch full ticket row
----------------------------------------------------------------
SELECT
id,
ticket_status_id,
ticket_assigned_to,
is_on_hold
INTO v_ticket
FROM public.tickets
WHERE id = p_ticket_id;
IF v_ticket.id IS NULL THEN
RAISE EXCEPTION 'Ticket % not found', p_ticket_id;
END IF;
----------------------------------------------------------------
-- Update assigned_to and status
----------------------------------------------------------------
UPDATE public.tickets
SET
ticket_assigned_to = p_service_provider_id,
ticket_status_id = p_new_status_id,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = p_ticket_id;
----------------------------------------------------------------
-- Insert assignment log
----------------------------------------------------------------
v_log_id := gen_random_uuid();
INSERT INTO public.ticket_logs (
id,
ticket_id,
old_status_id,
new_status_id,
is_on_hold,
created_by,
created_on_utc
)
VALUES (
v_log_id,
p_ticket_id,
v_ticket.ticket_status_id, -- OLD STATUS
p_new_status_id, -- NEW STATUS
v_ticket.is_on_hold, -- Hold flag unchanged
p_modified_by,
now()
);
END;
$procedure$
-- Procedure: assign_multiple_tickets_with_logs_json
CREATE OR REPLACE PROCEDURE public.assign_multiple_tickets_with_logs_json(IN p_assignments jsonb, IN p_new_status_id integer, IN p_modified_by uuid)
LANGUAGE plpgsql
AS $procedure$
DECLARE
v_item jsonb;
v_ticket_id uuid;
v_sp_id int;
v_ticket RECORD;
v_log_id uuid;
BEGIN
FOR v_item IN SELECT * FROM jsonb_array_elements(p_assignments)
LOOP
v_ticket_id := (v_item->>'ticketId')::uuid;
v_sp_id := (v_item->>'serviceProviderId')::int;
-- Fetch ticket
SELECT id, ticket_status_id, ticket_assigned_to, is_on_hold
INTO v_ticket
FROM public.tickets
WHERE id = v_ticket_id;
IF v_ticket.id IS NULL THEN
RAISE EXCEPTION 'Ticket % not found', v_ticket_id;
END IF;
-- Update ticket
UPDATE public.tickets
SET
ticket_assigned_to = v_sp_id,
ticket_status_id = p_new_status_id,
modified_by = p_modified_by,
modified_on_utc = now()
WHERE id = v_ticket_id;
-- Insert log
v_log_id := gen_random_uuid();
INSERT INTO public.ticket_logs (
id, ticket_id, old_status_id, new_status_id,
is_on_hold, created_by, created_on_utc
)
VALUES (
v_log_id,
v_ticket_id,
v_ticket.ticket_status_id,
p_new_status_id,
v_ticket.is_on_hold,
p_modified_by,
now()
);
END LOOP;
END;
$procedure$
-- Procedure: add_existing_user_role_permissions
CREATE OR REPLACE PROCEDURE public.add_existing_user_role_permissions(IN p_user_id uuid, IN p_created_by uuid, IN p_organization_id uuid DEFAULT NULL::uuid, IN p_company_id uuid DEFAULT NULL::uuid, IN p_permission_ids uuid[] DEFAULT NULL::uuid[], IN p_role_ids integer[] DEFAULT NULL::integer[])
LANGUAGE plpgsql
AS $procedure$
BEGIN
-- Insert user to organization if organization_id is provided
IF p_organization_id IS NOT NULL THEN
CALL public.insert_organization_user(p_user_id, p_created_by, p_organization_id);
END IF;
-- Insert user to company if company_id is provided
IF p_company_id IS NOT NULL THEN
CALL public.insert_company_user(p_user_id, p_created_by, p_company_id);
END IF;
-- Insert permissions if both permission_ids array and organization_id are provided
IF p_permission_ids IS NOT NULL AND p_organization_id IS NOT NULL THEN
CALL public.insert_user_permission(p_user_id, p_organization_id, p_permission_ids, p_created_by);
END IF;
-- Insert roles if role_ids array is provided
IF p_role_ids IS NOT NULL THEN
CALL public.insert_user_roles(p_user_id, p_role_ids, p_organization_id, p_created_by);
END IF;
END;
$procedure$