-- 20260929_confirmation_channel.sql -- Каким каналом клиент согласовал доставку/самовывоз. -- -- Владелец: «нужно видеть, согласование прошло через пуш / смс / звонок / вручную -- через менеджера», потому что в реестре и карточке было просто «Согласовано». -- -- Источник правды — order_groups.delivery_date_source. Его уже пишут: -- manual — логист в админке; -- autocall — робот (edge fn autocall-result). -- Здесь добавляем клиентские каналы: страница /delivery/{hash}?src=<канал> передаёт -- src в RPC, а RPC сохраняет его в delivery_date_source и в action_logs.details. DROP FUNCTION IF EXISTS public.confirm_delivery_choice_by_token( text, text, text, text, text, text, text ); CREATE OR REPLACE FUNCTION public.confirm_delivery_choice_by_token( p_token text, p_delivery_date text, p_delivery_time text, p_delivery_type text DEFAULT 'delivery'::text, p_pickup_date text DEFAULT NULL::text, p_pickup_time_slot text DEFAULT NULL::text, p_delivery_address text DEFAULT NULL::text, p_src text DEFAULT NULL::text ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public', 'extensions' AS $function$ DECLARE v_invitation record; v_order_group_id uuid; v_current_status text; v_delivery_status text; v_notification_status text; v_delivery_date_val date; v_existing_address text; v_effective_address text; v_delivery_days int[]; v_dow int; v_day_num int; v_channel text; BEGIN -- Канал подтверждения: push / sms / messenger из ссылки, иначе «клиент» (прямой заход). v_channel := CASE WHEN p_src IN ('push', 'sms', 'messenger') THEN p_src ELSE 'client' END; SELECT * INTO v_invitation FROM delivery_invitations WHERE token_hash = encode(digest(p_token, 'sha256'), 'hex') AND state IN ('awaiting_choice', 'sent') LIMIT 1; IF NOT found THEN RETURN jsonb_build_object('ok', false, 'error', 'Invitation not found or not active'); END IF; v_order_group_id := v_invitation.order_group_id; SELECT delivery_status, customer_address INTO v_current_status, v_existing_address FROM order_groups WHERE id = v_order_group_id; IF v_existing_address IS NOT NULL AND UPPER(TRIM(v_existing_address)) = 'САМОВЫВОЗ' THEN v_existing_address := NULL; END IF; IF v_current_status NOT IN ('pending_confirmation', 'manual_confirmation_required') THEN RETURN jsonb_build_object('ok', false, 'error', 'Order is not pending confirmation'); END IF; v_effective_address := COALESCE(NULLIF(p_delivery_address, ''), v_existing_address); IF p_delivery_type = 'pickup' THEN v_delivery_status := 'pickup'; v_notification_status := 'confirmed'; ELSIF v_effective_address IS NOT NULL AND v_effective_address != '' THEN v_delivery_status := 'agreed'; v_notification_status := 'confirmed'; ELSE v_delivery_status := 'requires_address'; v_notification_status := 'address_required'; END IF; v_delivery_date_val := CASE WHEN p_delivery_date IS NOT NULL AND p_delivery_date != '' THEN p_delivery_date::date ELSE NULL END; -- Check delivery_days from business_schedule_settings (only for delivery type) IF p_delivery_type = 'delivery' AND v_delivery_date_val IS NOT NULL THEN SELECT delivery_days INTO v_delivery_days FROM business_schedule_settings WHERE id = 1; IF v_delivery_days IS NULL THEN v_delivery_days := ARRAY[1,2,3,4,5]; END IF; -- extract(dow): 0=Sunday, 1=Monday, ..., 6=Saturday -- business_schedule uses 1=Monday ... 7=Sunday v_dow := extract(dow from v_delivery_date_val)::int; v_day_num := CASE WHEN v_dow = 0 THEN 7 ELSE v_dow END; IF NOT (v_day_num = ANY(v_delivery_days)) THEN RETURN jsonb_build_object('ok', false, 'error', 'Delivery is not available on this day'); END IF; END IF; UPDATE delivery_invitations SET state = 'agreed', delivery_date = COALESCE(v_delivery_date_val, delivery_date), delivery_time = COALESCE(NULLIF(p_delivery_time, ''), delivery_time), confirmed_at = now(), access_count = COALESCE(access_count, 0) + 1, last_accessed_at = now() WHERE id = v_invitation.id; UPDATE order_groups SET delivery_status = v_delivery_status, delivery_date = COALESCE(v_delivery_date_val, delivery_date), delivery_time = COALESCE(NULLIF(p_delivery_time, ''), delivery_time), delivery_type = p_delivery_type, notification_status = v_notification_status, delivery_date_source = v_channel, pickup_date = CASE WHEN p_delivery_type = 'pickup' THEN COALESCE(NULLIF(p_pickup_date, '')::date, v_delivery_date_val) ELSE NULL END, pickup_time_slot = CASE WHEN p_delivery_type = 'pickup' THEN COALESCE(NULLIF(p_pickup_time_slot, ''), p_delivery_time) ELSE NULL END, delivery_address = CASE WHEN p_delivery_type = 'delivery' AND v_effective_address IS NOT NULL AND v_effective_address != '' THEN v_effective_address ELSE delivery_address END, customer_address = CASE WHEN p_delivery_type = 'delivery' AND v_effective_address IS NOT NULL AND v_effective_address != '' THEN v_effective_address ELSE customer_address END, assigned_driver_id = CASE WHEN p_delivery_type = 'pickup' THEN NULL ELSE assigned_driver_id END, status = CASE WHEN p_delivery_type = 'pickup' AND v_delivery_status = 'pickup' THEN 'pickup' WHEN v_delivery_status = 'agreed' THEN 'agreed' WHEN v_delivery_status = 'requires_address' THEN 'requires_address' ELSE status END, updated_at = now() WHERE id = v_order_group_id; INSERT INTO action_logs (order_group_id, action, old_value, new_value, details) VALUES ( v_order_group_id, 'client_confirmed', v_current_status, v_delivery_status, jsonb_build_object( 'delivery_date', p_delivery_date, 'delivery_time', p_delivery_time, 'delivery_type', p_delivery_type, 'pickup_date', p_pickup_date, 'pickup_time_slot', p_pickup_time_slot, 'source', 'auto', 'channel', v_channel ) ); RETURN jsonb_build_object( 'ok', true, 'orderGroupId', v_order_group_id, 'deliveryStatus', v_delivery_status, 'channel', v_channel ); END; $function$;