From b69e5172d605b3c6b354bd6849f1f05f44743c72 Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 28 Sep 2026 11:34:45 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20SQL=20=D0=B0=D1=83=D0=B4=D0=B8=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D0=B2=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20?= =?UTF-8?q?+=20=D1=83=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20=D0=BC=D1=83=D1=81?= =?UTF-8?q?=D0=BE=D1=80=20=D0=B8=D0=B7=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - supabase/log_order_group_change.sql, supabase/order_history_rls.sql: триггер логирования изменений order_groups и его RLS существовали только на сервере (в БД работают с 2026-07-29), в git исходников не было — восстановить было нечем - scripts/__pycache__/*.pyc и DriverDeliveryPlanner.jsx.bak сняты с отслеживания (артефакты и бэкап, в .gitignore уже были __pycache__/ и *.bak — добавлен *.bak-*) --- .gitignore | 1 + .../sms_first_campaign.cpython-311.pyc | Bin 34414 -> 0 bytes .../driver/DriverDeliveryPlanner.jsx.bak | 371 ------------------ supabase/log_order_group_change.sql | 123 ++++++ supabase/order_history_rls.sql | 24 ++ 5 files changed, 148 insertions(+), 371 deletions(-) delete mode 100644 scripts/__pycache__/sms_first_campaign.cpython-311.pyc delete mode 100644 src/components/driver/DriverDeliveryPlanner.jsx.bak create mode 100644 supabase/log_order_group_change.sql create mode 100644 supabase/order_history_rls.sql diff --git a/.gitignore b/.gitignore index 244c37a..4a919bc 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ volumes/db/data/ __pycache__/ *.pyc *.bak +*.bak-* diff --git a/scripts/__pycache__/sms_first_campaign.cpython-311.pyc b/scripts/__pycache__/sms_first_campaign.cpython-311.pyc deleted file mode 100644 index 3752fafc9631933af7107036c07ab6cdbb452e9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34414 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 VJip_`9kc9j(ZCtY;mzj2{{lBN>qY { - if (!address || typeof address !== "string") return null; - const trimmed = address.trim(); - if (!trimmed) return null; - - // Patterns: "г.Ялта", "г. Ялта", "г Ялта", "г Ялта ", "Ялта,", "г.Севастополь", etc. - const cityMatch = trimmed.match(/(?:г\.?\s*|г\s+)([А-ЯЁA-Z][а-яёa-zA-Z\s\-]+?)(?:\s*[,;.]|$)/i); - if (cityMatch) { - return cityMatch[1].trim(); - } - - // Try common city names directly in the address - const knownCities = [ - "Севастополь", "Ялта", "Симферополь", "Феодосия", "Евпатория", - "Керчь", "Алушта", "Бахчисарай", "Судак", "Инкерман", - "Джанкой", "Красногвардейское", "Раздольное", "Черноморское", - ]; - for (const city of knownCities) { - if (trimmed.toLowerCase().includes(city.toLowerCase())) { - return city; - } - } - - // Fallback: first comma-separated segment if it looks like a city - const firstSegment = trimmed.split(/[,;]/)[0].trim(); - if (firstSegment.length > 2 && firstSegment.length < 30 && !/^\d/.test(firstSegment)) { - return firstSegment; - } - - return null; -}; - -const normalizeCity = (address) => { - const city = extractCity(address); - return city || "Севастополь"; -}; - -const DRIVER_DELIVERY_STATUS_OPTIONS = [ - { value: "all", label: "Все статусы" }, - ...DRIVER_VISIBLE_DELIVERY_STATUSES.map((status) => ({ - value: status, - label: status === "driver_assigned" ? "Назначено вам" : getOrderGroupDeliveryStatusLabel(status), - })), -]; - -export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder, currentUser }) => { - const [filters, setFilters] = React.useState({ - selectedDate: "", - deliveryStatus: "all", - selectedCity: "", - }); - - const driverOrderGroups = React.useMemo( - () => orderGroups.filter((group) => { - const isVisible = isOrderGroupVisibleToDriver(group); - const isAssignedToMe = currentUser && group.assignedDriverId === currentUser.id; - return isVisible && isAssignedToMe; - }), - [orderGroups, currentUser], - ); - - // Build map of date -> count - const dateDeliveryMap = React.useMemo(() => { - const map = new Map(); - driverOrderGroups.forEach((group) => { - const date = group.deliveryDate; - if (date) { - map.set(date, (map.get(date) || 0) + 1); - } - }); - return map; - }, [driverOrderGroups]); - - const sortedDeliveryDates = React.useMemo(() => { - return Array.from(dateDeliveryMap.keys()).sort(); - }, [dateDeliveryMap]); - - // Build map of city -> count - const cityDeliveryMap = React.useMemo(() => { - const map = new Map(); - driverOrderGroups.forEach((group) => { - const city = normalizeCity(group.deliveryAddress || group.delivery_address); - map.set(city, (map.get(city) || 0) + 1); - }); - return map; - }, [driverOrderGroups]); - - const sortedCities = React.useMemo(() => { - return Array.from(cityDeliveryMap.keys()).sort((a, b) => { - // Севастополь first, then alphabetical - if (a === "Севастополь") return -1; - if (b === "Севастополь") return 1; - return a.localeCompare(b, "ru"); - }); - }, [cityDeliveryMap]); - - const filteredOrderGroups = React.useMemo(() => { - let result = [...driverOrderGroups]; - if (filters.selectedDate) { - result = result.filter((group) => group.deliveryDate === filters.selectedDate); - } - if (filters.deliveryStatus !== "all") { - result = result.filter((group) => (group.deliveryStatus || group.delivery_status) === filters.deliveryStatus); - } - if (filters.selectedCity) { - result = result.filter((group) => { - const city = normalizeCity(group.deliveryAddress || group.delivery_address); - return city === filters.selectedCity; - }); - } - return result; - }, [driverOrderGroups, filters.selectedDate, filters.deliveryStatus, filters.selectedCity]); - - const groupedOrderGroups = React.useMemo( - () => groupOrderGroupsByDate(filteredOrderGroups), - [filteredOrderGroups], - ); - - const deliveryCountLabel = `${filteredOrderGroups.length} ${ - filteredOrderGroups.length === 1 ? "доставка" : filteredOrderGroups.length < 5 ? "доставки" : "доставок" - }`; - - const isDateSelected = (date) => filters.selectedDate === date; - - return ( -
- -
-
-
-
-

Мои доставки

- {deliveryCountLabel} -
-

- Показываем только назначенные вам группы доставки. Выберите дату и город. -

-
-
- -
- - -
- - {/* Date pills */} - {sortedDeliveryDates.length > 0 && ( -
- - {sortedDeliveryDates.map((date) => { - const count = dateDeliveryMap.get(date) || 0; - const selected = isDateSelected(date); - return ( - - ); - })} -
- )} - - {/* City pills */} - {sortedCities.length > 1 && ( -
- - {sortedCities.map((city) => { - const count = cityDeliveryMap.get(city) || 0; - const selected = filters.selectedCity === city; - return ( - - ); - })} -
- )} -
-
- - {groupedOrderGroups.length ? ( - groupedOrderGroups.map((group) => { - // Group items by delivery status within each date - const statusBuckets = new Map(); - for (const item of group.items) { - const s = item.deliveryStatus || item.delivery_status || "unknown"; - const label = s === "driver_assigned" ? "Назначено вам" : getOrderGroupDeliveryStatusLabel(s); - const tone = getOrderGroupDeliveryStatusTone(s); - if (!statusBuckets.has(s)) { - statusBuckets.set(s, { label, tone, items: [] }); - } - statusBuckets.get(s).items.push(item); - } - - // Sort status buckets in driver-relevant order - const statusOrder = ["driver_assigned", "loaded", "on_route", "delivered", "problem"]; - const sortedBuckets = Array.from(statusBuckets.entries()).sort(([a], [b]) => { - const ia = statusOrder.indexOf(a); - const ib = statusOrder.indexOf(b); - if (ia === -1 && ib === -1) return a.localeCompare(b); - if (ia === -1) return 1; - if (ib === -1) return -1; - return ia - ib; - }); - - return ( - -
-
-

- {parseGroupDate(group.date)?.toLocaleDateString("ru-RU", { - day: "numeric", - month: "long", - weekday: "long", - }) || "Без даты"} -

-

- {group.items.length} {group.items.length === 1 ? "группа" : "группы"} -

-
- - {(() => { - const d = parseGroupDate(group.date); - if (!d) return group.date || "—"; - const day = String(d.getDate()).padStart(2, "0"); - const month = String(d.getMonth() + 1).padStart(2, "0"); - const year = d.getFullYear(); - return `${day}.${month}.${year}`; - })()} - -
- - {sortedBuckets.map(([statusValue, { label, tone, items }]) => ( -
-
- {label} - {items.length} {items.length === 1 ? "группа" : "группы"} -
-
- {items.map((item) => ( - - ))} -
-
- ))} -
- ); - }) - ) : ( - -

Доставки не найдены

-

- Сейчас у вас нет назначенных групп доставки. -

-
- )} -
- ); -}; diff --git a/supabase/log_order_group_change.sql b/supabase/log_order_group_change.sql new file mode 100644 index 0000000..ea8e29b --- /dev/null +++ b/supabase/log_order_group_change.sql @@ -0,0 +1,123 @@ +CREATE OR REPLACE FUNCTION public.log_order_group_change() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path TO 'public' +AS $$ +DECLARE + v_user_id uuid; + v_changes jsonb := '{}'::jsonb; + v_action text; + v_source text; +BEGIN + -- Determine who made the change + v_user_id := auth.uid(); + v_source := CASE WHEN v_user_id IS NOT NULL THEN 'user' ELSE 'system' END; + + -- Track only meaningful field changes + IF NEW.notification_status IS DISTINCT FROM OLD.notification_status THEN + v_changes := v_changes || jsonb_build_object( + 'notification_status', + jsonb_build_object('old', OLD.notification_status, 'new', NEW.notification_status) + ); + END IF; + + IF NEW.delivery_status IS DISTINCT FROM OLD.delivery_status THEN + v_changes := v_changes || jsonb_build_object( + 'delivery_status', + jsonb_build_object('old', OLD.delivery_status, 'new', NEW.delivery_status) + ); + END IF; + + IF NEW.status IS DISTINCT FROM OLD.status THEN + v_changes := v_changes || jsonb_build_object( + 'status', + jsonb_build_object('old', OLD.status, 'new', NEW.status) + ); + END IF; + + IF NEW.assigned_driver_id IS DISTINCT FROM OLD.assigned_driver_id THEN + v_changes := v_changes || jsonb_build_object( + 'assigned_driver_id', + jsonb_build_object('old', OLD.assigned_driver_id, 'new', NEW.assigned_driver_id) + ); + END IF; + + IF NEW.delivery_link IS DISTINCT FROM OLD.delivery_link THEN + v_changes := v_changes || jsonb_build_object( + 'delivery_link', + jsonb_build_object('old', OLD.delivery_link, 'new', NEW.delivery_link) + ); + END IF; + + IF NEW.first_sms_sent_at IS DISTINCT FROM OLD.first_sms_sent_at THEN + v_changes := v_changes || jsonb_build_object( + 'first_sms_sent_at', + jsonb_build_object('old', OLD.first_sms_sent_at, 'new', NEW.first_sms_sent_at) + ); + END IF; + + IF NEW.second_sms_sent_at IS DISTINCT FROM OLD.second_sms_sent_at THEN + v_changes := v_changes || jsonb_build_object( + 'second_sms_sent_at', + jsonb_build_object('old', OLD.second_sms_sent_at, 'new', NEW.second_sms_sent_at) + ); + END IF; + + IF NEW.last_sms_error IS DISTINCT FROM OLD.last_sms_error THEN + v_changes := v_changes || jsonb_build_object( + 'last_sms_error', + jsonb_build_object('old', OLD.last_sms_error, 'new', NEW.last_sms_error) + ); + END IF; + + -- Only insert if something meaningful changed + IF v_changes = '{}'::jsonb THEN + RETURN NEW; + END IF; + + -- Determine action label + v_action := CASE + WHEN NEW.delivery_status IS DISTINCT FROM OLD.delivery_status THEN 'delivery_status_change' + WHEN NEW.notification_status IS DISTINCT FROM OLD.notification_status THEN 'notification_status_change' + WHEN NEW.assigned_driver_id IS DISTINCT FROM OLD.assigned_driver_id THEN 'driver_assignment_change' + WHEN NEW.delivery_link IS DISTINCT FROM OLD.delivery_link THEN 'delivery_link_change' + ELSE 'field_change' + END; + + INSERT INTO order_history (order_group_id, action, old_status, new_status, user_id, metadata) + VALUES ( + NEW.id, + v_action, + OLD.delivery_status, + NEW.delivery_status, + v_user_id, + jsonb_build_object('changes', v_changes, 'source', v_source, 'group_key', NEW.group_key) + ); + + RETURN NEW; +END; +$$; + +-- Drop old trigger if exists +DROP TRIGGER IF EXISTS log_order_group_change_trigger ON public.order_groups; + +-- Create trigger AFTER UPDATE (only when fields actually change) +CREATE TRIGGER log_order_group_change_trigger +AFTER UPDATE ON public.order_groups +FOR EACH ROW +WHEN ( + NEW.notification_status IS DISTINCT FROM OLD.notification_status + OR NEW.delivery_status IS DISTINCT FROM OLD.delivery_status + OR NEW.status IS DISTINCT FROM OLD.status + OR NEW.assigned_driver_id IS DISTINCT FROM OLD.assigned_driver_id + OR NEW.delivery_link IS DISTINCT FROM OLD.delivery_link + OR NEW.first_sms_sent_at IS DISTINCT FROM OLD.first_sms_sent_at + OR NEW.second_sms_sent_at IS DISTINCT FROM OLD.second_sms_sent_at + OR NEW.last_sms_error IS DISTINCT FROM OLD.last_sms_error +) +EXECUTE FUNCTION public.log_order_group_change(); + +-- Grant access +GRANT SELECT ON order_history TO authenticated; +GRANT INSERT ON order_history TO authenticated; \ No newline at end of file diff --git a/supabase/order_history_rls.sql b/supabase/order_history_rls.sql new file mode 100644 index 0000000..d37e723 --- /dev/null +++ b/supabase/order_history_rls.sql @@ -0,0 +1,24 @@ +-- Drop old policies that reference orders table +DROP POLICY IF EXISTS "history select by order role" ON order_history; +DROP POLICY IF EXISTS "history insert workflow" ON order_history; + +-- New SELECT policy: users can read history for order_groups they have access to +CREATE POLICY "order_history_select" ON order_history +FOR SELECT TO authenticated +USING ( + EXISTS ( + SELECT 1 FROM order_groups og + WHERE og.id = order_history.order_group_id + AND ( + current_role_name() = ANY (ARRAY['admin', 'mega_admin', 'production_lead']) + OR (current_role_name() = 'manager') + OR (current_role_name() = 'logistician') + OR (current_role_name() = 'driver' AND og.assigned_driver_id = auth.uid()) + ) + ) +); + +-- INSERT policy: only trigger (SECURITY DEFINER) inserts, users don't insert directly +CREATE POLICY "order_history_insert_trigger" ON order_history +FOR INSERT TO authenticated +WITH CHECK (true); \ No newline at end of file