From 5ac23219ca0824e79e2c8a52b9b2fb6fe3febdf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=B8=D0=BD?= Date: Tue, 21 Jul 2026 20:18:49 +0300 Subject: [PATCH] fix: owner event bookings list and paid plan durations from plan_to_months. Fixes EventHub/EventHubBack#51 Fixes EventHub/EventHubBack#52 Co-authored-by: Cursor --- src/core/core_subscription.erl | 25 +++++------------- src/logic/logic_booking.erl | 29 +++++++++++++++++++-- src/logic/logic_subscription.erl | 7 +++-- test/unit/core_subscription_tests.erl | 33 +++++++++++++++++++++++ test/unit/logic_booking_tests.erl | 29 ++++++++++++++++++++- test/unit/logic_subscription_tests.erl | 36 ++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 26 deletions(-) diff --git a/src/core/core_subscription.erl b/src/core/core_subscription.erl index 1c5eef7..ec4d9c4 100644 --- a/src/core/core_subscription.erl +++ b/src/core/core_subscription.erl @@ -8,35 +8,27 @@ -export([count_subscriptions_by_plan/0, count_subscriptions_by_status/0, count_trial_subscriptions/0, get_ending_paid_subscriptions/1]). --define(TRIAL_DAYS, 30). - %%%------------------------------------------------------------------- %%% @doc Создание подписки. -%%% `TrialUsed` – `true`, если подписка платная; `false` для пробного периода. +%%% `TrialUsed` – флаг (использован ли trial); на длительность не влияет. +%%% Длительность всегда считается из `Plan` через `plan_to_months/1`. %%% Все поля записи инициализированы, `undefined` не возникает. %%% @end %%%------------------------------------------------------------------- --spec create(UserId :: binary(), Plan :: monthly | quarterly | biannual | annual, +-spec create(UserId :: binary(), Plan :: monthly | quarterly | biannual | annual | trial, TrialUsed :: boolean()) -> {ok, #subscription{}} | {error, term()}. create(UserId, Plan, TrialUsed) -> Id = infra_utils:generate_id(16), Now = calendar:universal_time(), - {StartDate, EndDate} = case TrialUsed of - true -> - DurationMonths = plan_to_months(Plan), - End = add_months(Now, DurationMonths), - {Now, End}; - false -> - End = add_days(Now, ?TRIAL_DAYS), - {Now, End} - end, + DurationMonths = plan_to_months(Plan), + EndDate = add_months(Now, DurationMonths), Subscription = #subscription{ id = Id, user_id = UserId, plan = Plan, status = active, trial_used = TrialUsed, - started_at = StartDate, + started_at = Now, expires_at = EndDate, created_at = Now, updated_at = Now @@ -167,11 +159,6 @@ add_months(DateTime, Months) -> NewDays = Days + (Months * 30), calendar:gregorian_seconds_to_datetime(NewDays * 86400). --spec add_days(calendar:datetime(), pos_integer()) -> calendar:datetime(). -add_days(DateTime, Days) -> - Seconds = calendar:datetime_to_gregorian_seconds(DateTime), - calendar:gregorian_seconds_to_datetime(Seconds + (Days * 86400)). - %%%=================================================================== %%% Новые обёртки для админки %%%=================================================================== diff --git a/src/logic/logic_booking.erl b/src/logic/logic_booking.erl index 527ba92..45b5896 100644 --- a/src/logic/logic_booking.erl +++ b/src/logic/logic_booking.erl @@ -3,7 +3,8 @@ -export([create_booking/2, confirm_booking/2, confirm_booking/3, cancel_booking/2, cancel_booking/3, get_booking/2, list_bookings/2, list_user_bookings/1, delete_booking/2, - list_bookings_admin/0, get_booking_admin/1, list_event_bookings/1]). + list_bookings_admin/0, get_booking_admin/1, + list_event_bookings/1, list_event_bookings/2]). %%%------------------------------------------------------------------- %%% @doc Создание бронирования со статусом `pending`. @@ -151,13 +152,37 @@ get_booking_admin(BookingId) -> core_booking:get_by_id(BookingId). %%%------------------------------------------------------------------- -%%% @doc Список бронирований события (административный). +%%% @doc Список бронирований события (административный / внутренний). %%% @end %%%------------------------------------------------------------------- -spec list_event_bookings(EventId :: binary()) -> {ok, [#booking{}]}. list_event_bookings(EventId) -> core_booking:list_by_event(EventId). +%%%------------------------------------------------------------------- +%%% @doc Список бронирований события для владельца календаря (или admin). +%%% Возвращает полный список заявок без фильтрации по участнику. +%%% @end +%%%------------------------------------------------------------------- +-spec list_event_bookings(UserId :: binary(), EventId :: binary()) -> + {ok, [#booking{}]} | {error, not_found | access_denied}. +list_event_bookings(UserId, EventId) -> + case core_event:get_by_id(EventId) of + {ok, Event} -> + case core_calendar:get_by_id(Event#event.calendar_id) of + {ok, Calendar} -> + case admin_utils:is_admin(UserId) + orelse Calendar#calendar.owner_id =:= UserId of + true -> core_booking:list_by_event(EventId); + false -> {error, access_denied} + end; + {error, not_found} -> + {error, not_found} + end; + {error, not_found} -> + {error, not_found} + end. + %%%------------------------------------------------------------------- %%% @doc Список всех бронирований (административный). %%% @end diff --git a/src/logic/logic_subscription.erl b/src/logic/logic_subscription.erl index 74bbbae..71f0198 100644 --- a/src/logic/logic_subscription.erl +++ b/src/logic/logic_subscription.erl @@ -6,8 +6,6 @@ -export([check_user_subscription/1, can_create_commercial_calendar/1]). -export([handle_expired_subscriptions/0]). --define(TRIAL_DAYS, 30). - %% ============ Управление подписками ============ %% Начать пробный период (вызывается при первой попытке создать commercial календарь) @@ -22,6 +20,7 @@ start_trial(UserId) -> true -> {error, trial_already_used}; false -> + % trial_used=true — флаг; длительность из plan=trial (~1 месяц) case core_subscription:create(UserId, trial, true) of {ok, Subscription} -> {ok, Subscription}; @@ -34,7 +33,7 @@ start_trial(UserId) -> activate_subscription(UserId, Plan, PaymentInfo) -> case process_payment(PaymentInfo, plan_price(Plan)) of ok -> - % Проверяем, была ли у пользователя хоть одна подписка + % Флаг trial_used: была ли уже любая подписка (не влияет на длительность) {ok, AllSubs} = core_subscription:list_by_user(UserId), TrialUsed = length(AllSubs) > 0, @@ -45,7 +44,7 @@ activate_subscription(UserId, Plan, PaymentInfo) -> _ -> ok end, - % Создаём новую подписку + % Длительность всегда из Plan (plan_to_months), TrialUsed — только флаг case core_subscription:create(UserId, Plan, TrialUsed) of {ok, Subscription} -> {ok, Subscription}; diff --git a/test/unit/core_subscription_tests.erl b/test/unit/core_subscription_tests.erl index 0262254..1e3fef1 100644 --- a/test/unit/core_subscription_tests.erl +++ b/test/unit/core_subscription_tests.erl @@ -27,6 +27,10 @@ core_subscription_test_() -> [ {"Create trial subscription", fun test_create_trial/0}, {"Create paid subscription", fun test_create_paid/0}, + {"Create monthly duration", fun test_create_monthly_duration/0}, + {"Create quarterly duration", fun test_create_quarterly_duration/0}, + {"Create biannual duration", fun test_create_biannual_duration/0}, + {"Create annual duration", fun test_create_annual_duration/0}, {"Get active by user", fun test_get_active_by_user/0}, {"List by user", fun test_list_by_user/0}, {"Update status", fun test_update_status/0}, @@ -51,6 +55,35 @@ test_create_paid() -> ?assertEqual(true, Sub#subscription.trial_used), ?assertEqual(active, Sub#subscription.status). +%% Длительность не зависит от trial_used — только от Plan (месяц ≈ 30 дней). +%% add_months считает по календарным дням от полуночи даты старта. +assert_plan_duration(Sub, Months) -> + {{Y1, M1, D1}, _} = Sub#subscription.started_at, + {{Y2, M2, D2}, Time2} = Sub#subscription.expires_at, + StartDays = calendar:date_to_gregorian_days({Y1, M1, D1}), + EndDays = calendar:date_to_gregorian_days({Y2, M2, D2}), + ?assertEqual(Months * 30, EndDays - StartDays), + ?assertEqual({0, 0, 0}, Time2). + +test_create_monthly_duration() -> + %% Даже при trial_used=false длительность = 1 месяц, не «trial 30 дней» отдельно + {ok, Sub} = core_subscription:create(<<"u-m">>, monthly, false), + ?assertEqual(false, Sub#subscription.trial_used), + assert_plan_duration(Sub, 1). + +test_create_quarterly_duration() -> + {ok, Sub} = core_subscription:create(<<"u-q">>, quarterly, false), + ?assertEqual(false, Sub#subscription.trial_used), + assert_plan_duration(Sub, 3). + +test_create_biannual_duration() -> + {ok, Sub} = core_subscription:create(<<"u-b">>, biannual, true), + assert_plan_duration(Sub, 6). + +test_create_annual_duration() -> + {ok, Sub} = core_subscription:create(<<"u-a">>, annual, false), + assert_plan_duration(Sub, 12). + test_get_active_by_user() -> UserId = <<"user123">>, {ok, Sub1} = core_subscription:create(UserId, trial, false), diff --git a/test/unit/logic_booking_tests.erl b/test/unit/logic_booking_tests.erl index a517f4f..39b7416 100644 --- a/test/unit/logic_booking_tests.erl +++ b/test/unit/logic_booking_tests.erl @@ -2,7 +2,7 @@ -include_lib("eunit/include/eunit.hrl"). -include("records.hrl"). --define(TABLES, [user, calendar, event, booking]). +-define(TABLES, [user, calendar, event, booking, admin]). setup() -> eh_test_support:start_mnesia(), @@ -29,6 +29,8 @@ logic_booking_test_() -> {"Cancel booking by participant", fun test_cancel_booking/0}, {"Cancel booking access denied", fun test_cancel_access_denied/0}, {"List event bookings", fun test_list_event_bookings/0}, + {"List event bookings as owner", fun test_list_event_bookings_owner/0}, + {"List event bookings non-owner denied", fun test_list_event_bookings_non_owner/0}, {"List user bookings", fun test_list_user_bookings/0} ]}. @@ -163,6 +165,31 @@ test_list_event_bookings() -> {ok, Bookings} = logic_booking:list_event_bookings(EventId), ?assertEqual(2, length(Bookings)). +test_list_event_bookings_owner() -> + OwnerId = create_test_user(user), + Participant1Id = create_test_user(user), + Participant2Id = create_test_user(user), + CalendarId = create_test_calendar(OwnerId, manual), + EventId = create_test_event(CalendarId), + + {ok, _} = logic_booking:create_booking(Participant1Id, EventId), + {ok, _} = logic_booking:create_booking(Participant2Id, EventId), + + {ok, Bookings} = logic_booking:list_event_bookings(OwnerId, EventId), + ?assertEqual(2, length(Bookings)). + +test_list_event_bookings_non_owner() -> + OwnerId = create_test_user(user), + ParticipantId = create_test_user(user), + StrangerId = create_test_user(user), + CalendarId = create_test_calendar(OwnerId, manual), + EventId = create_test_event(CalendarId), + + {ok, _} = logic_booking:create_booking(ParticipantId, EventId), + + {error, access_denied} = logic_booking:list_event_bookings(StrangerId, EventId), + {error, access_denied} = logic_booking:list_event_bookings(ParticipantId, EventId). + test_list_user_bookings() -> OwnerId = create_test_user(user), ParticipantId = create_test_user(user), diff --git a/test/unit/logic_subscription_tests.erl b/test/unit/logic_subscription_tests.erl index c097137..1900f19 100644 --- a/test/unit/logic_subscription_tests.erl +++ b/test/unit/logic_subscription_tests.erl @@ -11,6 +11,10 @@ logic_subscription_test_() -> {"Start trial duplicate", fun test_start_trial_duplicate/0}, {"Activate subscription (no trial)", fun test_activate_subscription_no_trial/0}, {"Activate subscription (after trial)", fun test_activate_subscription_after_trial/0}, + {"Activate quarterly duration", fun test_activate_quarterly_duration/0}, + {"Activate monthly duration", fun test_activate_monthly_duration/0}, + {"Activate biannual duration", fun test_activate_biannual_duration/0}, + {"Activate annual duration", fun test_activate_annual_duration/0}, {"Check subscription - free", fun test_check_free/0}, {"Check subscription - trial", fun test_check_trial/0}, {"Check subscription - paid", fun test_check_paid/0}, @@ -76,6 +80,38 @@ test_activate_subscription_after_trial() -> ?assertEqual(monthly, Sub#subscription.plan), ?assertEqual(true, Sub#subscription.trial_used). +%% Длительность при активации (в т.ч. первой платной с trial_used=false) из Plan. +assert_plan_duration(Sub, Months) -> + {{Y1, M1, D1}, _} = Sub#subscription.started_at, + {{Y2, M2, D2}, Time2} = Sub#subscription.expires_at, + StartDays = calendar:date_to_gregorian_days({Y1, M1, D1}), + EndDays = calendar:date_to_gregorian_days({Y2, M2, D2}), + ?assertEqual(Months * 30, EndDays - StartDays), + ?assertEqual({0, 0, 0}, Time2). + +test_activate_monthly_duration() -> + UserId = create_test_user(), + {ok, Sub} = logic_subscription:activate_subscription(UserId, monthly, #{card => "4242"}), + ?assertEqual(false, Sub#subscription.trial_used), + assert_plan_duration(Sub, 1). + +test_activate_quarterly_duration() -> + UserId = create_test_user(), + {ok, Sub} = logic_subscription:activate_subscription(UserId, quarterly, #{card => "4242"}), + ?assertEqual(quarterly, Sub#subscription.plan), + ?assertEqual(false, Sub#subscription.trial_used), + assert_plan_duration(Sub, 3). + +test_activate_biannual_duration() -> + UserId = create_test_user(), + {ok, Sub} = logic_subscription:activate_subscription(UserId, biannual, #{card => "4242"}), + assert_plan_duration(Sub, 6). + +test_activate_annual_duration() -> + UserId = create_test_user(), + {ok, Sub} = logic_subscription:activate_subscription(UserId, annual, #{card => "4242"}), + assert_plan_duration(Sub, 12). + test_check_free() -> UserId = create_test_user(), {ok, free, free} = logic_subscription:check_user_subscription(UserId).