Skip to content

Commit e165a3e

Browse files
committed
feat: Recalculate activity_category codes when code_pattern changes
Add trigger on activity_category_standard that cascades code_pattern changes to all related activity_category rows. When code_pattern is modified (e.g., from 'digits' to 'dot_after_two_digits'), the trigger touches all activity_category rows for that standard, which fires the existing lookup_parent_and_derive_code() trigger to recalculate codes. This ensures consistency between the configured code pattern and the derived code values without requiring manual recalculation.
1 parent b03bde0 commit e165a3e

File tree

4 files changed

+554
-0
lines changed

4 files changed

+554
-0
lines changed

app/src/lib/database.types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12089,6 +12089,10 @@ export type Database = {
1208912089
Args: never
1209012090
Returns: string[]
1209112091
},
12092+
recalculate_activity_category_codes: {
12093+
Args: never
12094+
Returns: unknown
12095+
},
1209212096
refresh: {
1209312097
Args: never
1209412098
Returns: unknown

migrations/20240107000000_create_table_activity_category.up.sql

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,27 @@ BEFORE INSERT OR UPDATE ON public.activity_category
6767
FOR EACH ROW
6868
EXECUTE FUNCTION public.lookup_parent_and_derive_code();
6969

70+
-- Trigger function to recalculate codes when activity_category_standard.code_pattern changes
71+
-- This "touches" all related activity_category rows, which triggers lookup_parent_and_derive_code()
72+
CREATE FUNCTION public.recalculate_activity_category_codes()
73+
RETURNS trigger
74+
LANGUAGE plpgsql
75+
AS $recalculate_activity_category_codes$
76+
BEGIN
77+
-- Touch all activity_category rows for this standard
78+
-- This triggers lookup_parent_and_derive_code() to recalculate code
79+
UPDATE public.activity_category
80+
SET updated_at = statement_timestamp()
81+
WHERE standard_id = NEW.id;
82+
RETURN NEW;
83+
END;
84+
$recalculate_activity_category_codes$;
85+
86+
-- Trigger on activity_category_standard to recalculate codes when code_pattern changes
87+
CREATE TRIGGER recalculate_activity_category_codes_after_update
88+
AFTER UPDATE OF code_pattern ON public.activity_category_standard
89+
FOR EACH ROW
90+
WHEN (OLD.code_pattern IS DISTINCT FROM NEW.code_pattern)
91+
EXECUTE FUNCTION public.recalculate_activity_category_codes();
92+
7093
END;
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
-- Test: activity_category code recalculation when activity_category_standard.code_pattern changes
2+
-- Verifies that changing code_pattern cascades to recalculate all related activity_category.code values
3+
\echo '>>> Test Suite: Activity Category Code Pattern Cascade <<<'
4+
>>> Test Suite: Activity Category Code Pattern Cascade <<<
5+
SET client_min_messages TO NOTICE;
6+
-- =============================================================================
7+
-- Setup: Create a test activity_category_standard and some activity_category entries
8+
-- =============================================================================
9+
\echo '--- Setup: Create test data ---'
10+
--- Setup: Create test data ---
11+
-- Insert a test standard with 'digits' pattern
12+
INSERT INTO public.activity_category_standard(code, name, description, code_pattern)
13+
VALUES ('test_std', 'Test Standard', 'Test Standard for code pattern cascade', 'digits');
14+
-- Get the standard_id for our test standard
15+
\echo '-- Verify test standard created --'
16+
-- Verify test standard created --
17+
SELECT code, code_pattern FROM public.activity_category_standard WHERE code = 'test_std';
18+
code | code_pattern
19+
----------+--------------
20+
test_std | digits
21+
(1 row)
22+
23+
-- Insert some activity categories with paths that will show the difference between patterns
24+
-- Path 'A.01.1.1.0' should become:
25+
-- - 'digits': '01110'
26+
-- - 'dot_after_two_digits': '01.110'
27+
INSERT INTO public.activity_category(standard_id, path, name, description, active, custom)
28+
SELECT acs.id, 'A', 'Section A', 'Agriculture', true, false
29+
FROM public.activity_category_standard AS acs WHERE acs.code = 'test_std';
30+
INSERT INTO public.activity_category(standard_id, path, name, description, active, custom)
31+
SELECT acs.id, 'A.01', 'Division 01', 'Crop production', true, false
32+
FROM public.activity_category_standard AS acs WHERE acs.code = 'test_std';
33+
INSERT INTO public.activity_category(standard_id, path, name, description, active, custom)
34+
SELECT acs.id, 'A.01.1', 'Group 01.1', 'Growing of non-perennial crops', true, false
35+
FROM public.activity_category_standard AS acs WHERE acs.code = 'test_std';
36+
INSERT INTO public.activity_category(standard_id, path, name, description, active, custom)
37+
SELECT acs.id, 'A.01.1.1', 'Class 01.11', 'Growing of cereals', true, false
38+
FROM public.activity_category_standard AS acs WHERE acs.code = 'test_std';
39+
INSERT INTO public.activity_category(standard_id, path, name, description, active, custom)
40+
SELECT acs.id, 'A.01.1.1.0', 'Subclass 01.110', 'Growing of cereals (detailed)', true, false
41+
FROM public.activity_category_standard AS acs WHERE acs.code = 'test_std';
42+
-- Also insert an inactive category to verify inactive categories are also updated
43+
INSERT INTO public.activity_category(standard_id, path, name, description, active, custom)
44+
SELECT acs.id, 'B.05', 'Division 05 (inactive)', 'Mining of coal', false, false
45+
FROM public.activity_category_standard AS acs WHERE acs.code = 'test_std';
46+
\echo '-- Verify activity categories with digits pattern --'
47+
-- Verify activity categories with digits pattern --
48+
SELECT ac.path, ac.code, ac.active
49+
FROM public.activity_category AS ac
50+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
51+
WHERE acs.code = 'test_std'
52+
ORDER BY ac.path;
53+
path | code | active
54+
------------+-------+--------
55+
A | | t
56+
A.01 | 01 | t
57+
A.01.1 | 011 | t
58+
A.01.1.1 | 0111 | t
59+
A.01.1.1.0 | 01110 | t
60+
B.05 | 05 | f
61+
(6 rows)
62+
63+
-- =============================================================================
64+
-- Test 1: Verify initial codes are correct for 'digits' pattern
65+
-- =============================================================================
66+
\echo '--- Test 1: Verify initial codes with digits pattern ---'
67+
--- Test 1: Verify initial codes with digits pattern ---
68+
DO $$
69+
BEGIN
70+
BEGIN
71+
RAISE NOTICE 'Test 1: Verifying initial codes with digits pattern';
72+
73+
-- Check that codes are correctly derived with 'digits' pattern
74+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
75+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
76+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1.1.0') = '01110',
77+
'Path A.01.1.1.0 should have code 01110 with digits pattern';
78+
79+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
80+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
81+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01') = '01',
82+
'Path A.01 should have code 01 with digits pattern';
83+
84+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
85+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
86+
WHERE acs.code = 'test_std' AND ac.path::text = 'B.05') = '05',
87+
'Path B.05 (inactive) should have code 05 with digits pattern';
88+
89+
RAISE NOTICE 'Test 1: PASSED - Initial codes correct for digits pattern';
90+
EXCEPTION
91+
WHEN ASSERT_FAILURE THEN
92+
RAISE NOTICE 'Test 1: FAILED (ASSERT_FAILURE): %', SQLERRM;
93+
WHEN OTHERS THEN
94+
RAISE NOTICE 'Test 1: FAILED (OTHER ERROR): %', SQLERRM;
95+
END;
96+
END;
97+
$$;
98+
NOTICE: Test 1: Verifying initial codes with digits pattern
99+
NOTICE: Test 1: PASSED - Initial codes correct for digits pattern
100+
-- =============================================================================
101+
-- Test 2: Change code_pattern and verify codes are recalculated
102+
-- =============================================================================
103+
\echo '--- Test 2: Change code_pattern to dot_after_two_digits ---'
104+
--- Test 2: Change code_pattern to dot_after_two_digits ---
105+
-- Change the code_pattern - this should trigger recalculation
106+
UPDATE public.activity_category_standard
107+
SET code_pattern = 'dot_after_two_digits'
108+
WHERE code = 'test_std';
109+
\echo '-- Verify activity categories after code_pattern change --'
110+
-- Verify activity categories after code_pattern change --
111+
SELECT ac.path, ac.code, ac.active
112+
FROM public.activity_category AS ac
113+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
114+
WHERE acs.code = 'test_std'
115+
ORDER BY ac.path;
116+
path | code | active
117+
------------+--------+--------
118+
A | | t
119+
A.01 | 01 | t
120+
A.01.1 | 01.1 | t
121+
A.01.1.1 | 01.11 | t
122+
A.01.1.1.0 | 01.110 | t
123+
B.05 | 05 | f
124+
(6 rows)
125+
126+
DO $$
127+
BEGIN
128+
BEGIN
129+
RAISE NOTICE 'Test 2: Verifying codes after changing to dot_after_two_digits pattern';
130+
131+
-- Check that codes are correctly recalculated with 'dot_after_two_digits' pattern
132+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
133+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
134+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1.1.0') = '01.110',
135+
'Path A.01.1.1.0 should have code 01.110 with dot_after_two_digits pattern';
136+
137+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
138+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
139+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01') = '01',
140+
'Path A.01 should still have code 01 (no change for 2-digit codes)';
141+
142+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
143+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
144+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1') = '01.1',
145+
'Path A.01.1 should have code 01.1 with dot_after_two_digits pattern';
146+
147+
-- Verify inactive categories are also updated
148+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
149+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
150+
WHERE acs.code = 'test_std' AND ac.path::text = 'B.05') = '05',
151+
'Path B.05 (inactive) should still have code 05 (no change for 2-digit codes)';
152+
153+
RAISE NOTICE 'Test 2: PASSED - Codes correctly recalculated after code_pattern change';
154+
EXCEPTION
155+
WHEN ASSERT_FAILURE THEN
156+
RAISE NOTICE 'Test 2: FAILED (ASSERT_FAILURE): %', SQLERRM;
157+
WHEN OTHERS THEN
158+
RAISE NOTICE 'Test 2: FAILED (OTHER ERROR): %', SQLERRM;
159+
END;
160+
END;
161+
$$;
162+
NOTICE: Test 2: Verifying codes after changing to dot_after_two_digits pattern
163+
NOTICE: Test 2: PASSED - Codes correctly recalculated after code_pattern change
164+
-- =============================================================================
165+
-- Test 3: Change code_pattern back and verify codes revert
166+
-- =============================================================================
167+
\echo '--- Test 3: Change code_pattern back to digits ---'
168+
--- Test 3: Change code_pattern back to digits ---
169+
UPDATE public.activity_category_standard
170+
SET code_pattern = 'digits'
171+
WHERE code = 'test_std';
172+
\echo '-- Verify activity categories after reverting code_pattern --'
173+
-- Verify activity categories after reverting code_pattern --
174+
SELECT ac.path, ac.code, ac.active
175+
FROM public.activity_category AS ac
176+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
177+
WHERE acs.code = 'test_std'
178+
ORDER BY ac.path;
179+
path | code | active
180+
------------+-------+--------
181+
A | | t
182+
A.01 | 01 | t
183+
A.01.1 | 011 | t
184+
A.01.1.1 | 0111 | t
185+
A.01.1.1.0 | 01110 | t
186+
B.05 | 05 | f
187+
(6 rows)
188+
189+
DO $$
190+
BEGIN
191+
BEGIN
192+
RAISE NOTICE 'Test 3: Verifying codes after reverting to digits pattern';
193+
194+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
195+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
196+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1.1.0') = '01110',
197+
'Path A.01.1.1.0 should revert to code 01110 with digits pattern';
198+
199+
ASSERT (SELECT ac.code FROM public.activity_category AS ac
200+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
201+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1') = '011',
202+
'Path A.01.1 should revert to code 011 with digits pattern';
203+
204+
RAISE NOTICE 'Test 3: PASSED - Codes correctly reverted to digits pattern';
205+
EXCEPTION
206+
WHEN ASSERT_FAILURE THEN
207+
RAISE NOTICE 'Test 3: FAILED (ASSERT_FAILURE): %', SQLERRM;
208+
WHEN OTHERS THEN
209+
RAISE NOTICE 'Test 3: FAILED (OTHER ERROR): %', SQLERRM;
210+
END;
211+
END;
212+
$$;
213+
NOTICE: Test 3: Verifying codes after reverting to digits pattern
214+
NOTICE: Test 3: PASSED - Codes correctly reverted to digits pattern
215+
-- =============================================================================
216+
-- Test 4: Verify no-op when code_pattern doesn't change
217+
-- =============================================================================
218+
\echo '--- Test 4: Verify no recalculation when code_pattern unchanged ---'
219+
--- Test 4: Verify no recalculation when code_pattern unchanged ---
220+
DO $$
221+
DECLARE
222+
original_updated_at timestamptz;
223+
new_updated_at timestamptz;
224+
BEGIN
225+
BEGIN
226+
RAISE NOTICE 'Test 4: Verifying no recalculation when code_pattern is unchanged';
227+
228+
-- Get the current updated_at for a category
229+
SELECT updated_at INTO original_updated_at
230+
FROM public.activity_category AS ac
231+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
232+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1.1.0';
233+
234+
-- Wait a tiny bit to ensure timestamp would change if update happened
235+
PERFORM pg_sleep(0.01);
236+
237+
-- Update the standard but don't change code_pattern (change name instead)
238+
UPDATE public.activity_category_standard
239+
SET name = 'Test Standard Updated'
240+
WHERE code = 'test_std';
241+
242+
-- Get the updated_at again
243+
SELECT updated_at INTO new_updated_at
244+
FROM public.activity_category AS ac
245+
JOIN public.activity_category_standard AS acs ON ac.standard_id = acs.id
246+
WHERE acs.code = 'test_std' AND ac.path::text = 'A.01.1.1.0';
247+
248+
-- The updated_at should NOT have changed
249+
ASSERT original_updated_at = new_updated_at,
250+
format('updated_at should not change when code_pattern is unchanged (was %s, now %s)',
251+
original_updated_at, new_updated_at);
252+
253+
RAISE NOTICE 'Test 4: PASSED - No recalculation when code_pattern unchanged';
254+
EXCEPTION
255+
WHEN ASSERT_FAILURE THEN
256+
RAISE NOTICE 'Test 4: FAILED (ASSERT_FAILURE): %', SQLERRM;
257+
WHEN OTHERS THEN
258+
RAISE NOTICE 'Test 4: FAILED (OTHER ERROR): %', SQLERRM;
259+
END;
260+
END;
261+
$$;
262+
NOTICE: Test 4: Verifying no recalculation when code_pattern is unchanged
263+
NOTICE: Test 4: PASSED - No recalculation when code_pattern unchanged
264+
-- =============================================================================
265+
-- Cleanup: Remove test data
266+
-- =============================================================================
267+
\echo '--- Cleanup: Remove test data ---'
268+
--- Cleanup: Remove test data ---
269+
-- Delete activity categories first (due to FK constraint)
270+
DELETE FROM public.activity_category
271+
WHERE standard_id = (SELECT id FROM public.activity_category_standard WHERE code = 'test_std');
272+
-- Delete the test standard
273+
DELETE FROM public.activity_category_standard WHERE code = 'test_std';
274+
\echo '>>> Test Suite: Activity Category Code Pattern Cascade Complete <<<'
275+
>>> Test Suite: Activity Category Code Pattern Cascade Complete <<<

0 commit comments

Comments
 (0)