-- Fix historical customer-code duplicates before restoring the intended constraint. -- The first row keeps its business code; later duplicates receive a traceable migration code. begin; set local lock_timeout = '10s'; set local statement_timeout = '5min'; select pg_advisory_xact_lock(hashtext('20260713_fix_crm_codes_pg17')); with ranked_customer_codes as ( select id, row_number() over (partition by customer_code order by created_at nulls last, id) as row_no from crm_customer where customer_code is not null and btrim(customer_code) <> '' ) update crm_customer customer set customer_code = 'CUS-MIG-' || customer.id::text, updated_at = now() from ranked_customer_codes duplicate where duplicate.id = customer.id and duplicate.row_no > 1; do $$ declare constraint_definition text; begin select pg_get_constraintdef(oid) into constraint_definition from pg_constraint where conrelid = 'crm_customer'::regclass and conname = 'uk_crm_customer_code'; if constraint_definition is not null and constraint_definition not ilike 'UNIQUE (customer_code)%' then alter table crm_customer drop constraint uk_crm_customer_code; constraint_definition := null; end if; if constraint_definition is null then alter table crm_customer add constraint uk_crm_customer_code unique (customer_code); end if; end $$; create sequence if not exists crm_customer_code_seq start with 1 increment by 1 minvalue 1; create sequence if not exists crm_opportunity_code_seq start with 1 increment by 1 minvalue 1; -- Lock sequence consumers before reading their current values. alter sequence crm_customer_code_seq no cycle; alter sequence crm_opportunity_code_seq no cycle; do $$ declare sales_id_sequence regclass; channel_id_sequence regclass; begin if to_regclass('crm_sales_expansion') is not null then sales_id_sequence := pg_get_serial_sequence('crm_sales_expansion', 'id')::regclass; end if; if to_regclass('crm_channel_expansion') is not null then channel_id_sequence := pg_get_serial_sequence('crm_channel_expansion', 'id')::regclass; end if; if sales_id_sequence is not null then execute format('alter sequence %s no cycle', sales_id_sequence); end if; if channel_id_sequence is not null then execute format('alter sequence %s no cycle', channel_id_sequence); end if; end $$; -- ALTER SEQUENCE RESTART is transactional; setval() would survive a rollback. do $$ declare customer_code_next bigint; opportunity_code_next bigint; begin select greatest( coalesce(( select max(substring(customer_code from '([0-9]+)$')::bigint) + 1 from crm_customer where customer_code ~ '^CUS-[0-9]{8}-[0-9]+$' ), 1), (select case when is_called then last_value + 1 else last_value end from crm_customer_code_seq) ) into customer_code_next; select greatest( coalesce(( select max(substring(opportunity_code from '([0-9]+)$')::bigint) + 1 from crm_opportunity where opportunity_code ~ '^OPP-[0-9]{8}-[0-9]+$' ), 1), (select case when is_called then last_value + 1 else last_value end from crm_opportunity_code_seq) ) into opportunity_code_next; execute format('alter sequence crm_customer_code_seq restart with %s', customer_code_next); execute format('alter sequence crm_opportunity_code_seq restart with %s', opportunity_code_next); end $$; do $$ declare sales_id_sequence regclass; channel_id_sequence regclass; sales_id_max bigint; sales_id_current_next bigint; channel_id_max bigint; channel_id_current_next bigint; begin if to_regclass('crm_sales_expansion') is not null then sales_id_sequence := pg_get_serial_sequence('crm_sales_expansion', 'id')::regclass; end if; if to_regclass('crm_channel_expansion') is not null then channel_id_sequence := pg_get_serial_sequence('crm_channel_expansion', 'id')::regclass; end if; if sales_id_sequence is not null then select coalesce(max(id), 0) into sales_id_max from crm_sales_expansion; if sales_id_max = 9223372036854775807 then raise exception 'crm_sales_expansion identity sequence has exhausted bigint values'; end if; execute format( 'select case when is_called then last_value + 1 else last_value end from %s', sales_id_sequence ) into sales_id_current_next; execute format( 'alter sequence %s restart with %s', sales_id_sequence, greatest(sales_id_max + 1, sales_id_current_next) ); end if; if channel_id_sequence is not null then select coalesce(max(id), 0) into channel_id_max from crm_channel_expansion; if channel_id_max = 9223372036854775807 then raise exception 'crm_channel_expansion identity sequence has exhausted bigint values'; end if; execute format( 'select case when is_called then last_value + 1 else last_value end from %s', channel_id_sequence ) into channel_id_current_next; execute format( 'alter sequence %s restart with %s', channel_id_sequence, greatest(channel_id_max + 1, channel_id_current_next) ); end if; end $$; commit;