diff --git a/app/frontend/src/javascript/components/prepaid-packs/packs-summary.tsx b/app/frontend/src/javascript/components/prepaid-packs/packs-summary.tsx index 3e5d70478..f5354f2fd 100644 --- a/app/frontend/src/javascript/components/prepaid-packs/packs-summary.tsx +++ b/app/frontend/src/javascript/components/prepaid-packs/packs-summary.tsx @@ -24,9 +24,10 @@ interface PacksSummaryProps { operator: User, onError: (message: string) => void, onSuccess: (message: string) => void, + refresh?: Promise } -const PacksSummaryComponent: React.FC = ({ item, itemType, customer, operator, onError, onSuccess }) => { +const PacksSummaryComponent: React.FC = ({ item, itemType, customer, operator, onError, onSuccess, refresh }) => { const { t } = useTranslation('logged'); const [userPacks, setUserPacks] = useState>(null); @@ -42,10 +43,23 @@ const PacksSummaryComponent: React.FC = ({ item, itemType, cu useEffect(() => { if (_.isEmpty(customer)) return; + getUserPacksData(); + }, [item, itemType, customer]); + + useEffect(() => { + if (refresh instanceof Promise) { + refresh.then(getUserPacksData); + } + }, [refresh]); + + /** + * Fetch the user packs data from the API + */ + const getUserPacksData = (): void => { UserPackAPI.index({ user_id: customer.id, priceable_type: itemType, priceable_id: item.id }) .then(data => setUserPacks(data)) .catch(error => onError(error)); - }, [item, itemType, customer]); + } /** * Total of minutes used by the customer @@ -108,7 +122,10 @@ const PacksSummaryComponent: React.FC = ({ item, itemType, cu

{t('app.logged.packs_summary.prepaid_hours')}

- {t('app.logged.packs_summary.remaining_HOURS', { HOURS: totalHours(), ITEM: itemType })} + + {totalHours() > 0 && t('app.logged.packs_summary.remaining_HOURS', { HOURS: totalHours(), ITEM: itemType })} + {totalHours() === 0 && t('app.logged.packs_summary.no_hours', { ITEM: itemType })} + {shouldDisplayButton() &&
}> {t('app.logged.packs_summary.buy_a_new_pack')} @@ -128,12 +145,12 @@ const PacksSummaryComponent: React.FC = ({ item, itemType, cu ); } -export const PacksSummary: React.FC = ({ item, itemType, customer, operator, onError, onSuccess }) => { +export const PacksSummary: React.FC = ({ item, itemType, customer, operator, onError, onSuccess, refresh }) => { return ( - + ); } -Application.Components.component('packsSummary', react2angular(PacksSummary, ['item', 'itemType', 'customer', 'operator', 'onError', 'onSuccess'])); +Application.Components.component('packsSummary', react2angular(PacksSummary, ['item', 'itemType', 'customer', 'operator', 'onError', 'onSuccess', 'refresh'])); diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb index eed36ed6e..4b4a795cc 100644 --- a/app/frontend/src/javascript/controllers/machines.js.erb +++ b/app/frontend/src/javascript/controllers/machines.js.erb @@ -415,6 +415,9 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat // current machine to reserve $scope.machine = machinePromise; + // will be set to a Promise and resolved after the payment is sone + $scope.afterPaymentPromise = null; + // fullCalendar (v2) configuration $scope.calendarConfig = CalendarConfig({ minTime: moment.duration(moment(settingsPromise.booking_window_start).format('HH:mm:ss')), @@ -618,6 +621,14 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat } refetchCalendar(); + + // trigger the refresh of react components + setTimeout(() => { + $scope.afterPaymentPromise = new Promise(resolve => { + resolve(); + }); + $scope.$apply(); + }, 50); }); }; diff --git a/app/frontend/templates/machines/reserve.html b/app/frontend/templates/machines/reserve.html index 23c03e5dd..f8c6b5000 100644 --- a/app/frontend/templates/machines/reserve.html +++ b/app/frontend/templates/machines/reserve.html @@ -34,7 +34,8 @@ customer="ctrl.member" operator="currentUser" on-error="onError" - on-success="onSuccess"> + on-success="onSuccess" + refresh="afterPaymentPromise"> = slot_minutes + consumed = slot_minutes + consumed = options[:prepaid][:minutes] if slot_minutes > options[:prepaid][:minutes] + real_price = (slot_minutes - consumed) * (slot_rate / MINUTES_PER_HOUR) + options[:prepaid][:minutes] -= consumed end unless options[:elements].nil? @@ -97,14 +103,6 @@ class CartItem::Reservation < CartItem::BaseItem real_price end - def get_prepaid_minutes(user, priceable) - user_packs = PrepaidPackService.user_packs(user, priceable) - total_available = user_packs.map { |up| up.prepaid_pack.minutes }.reduce(:+) || 0 - total_used = user_packs.map(&:minutes_used).reduce(:+) || 0 - - total_available - total_used - end - ## # Compute the number of remaining hours in the users current credits (for machine or space) ## diff --git a/app/models/shopping_cart.rb b/app/models/shopping_cart.rb index a32fa5a17..0a90a7161 100644 --- a/app/models/shopping_cart.rb +++ b/app/models/shopping_cart.rb @@ -56,6 +56,7 @@ class ShoppingCart objects.push(save_item(item)) end update_credits(objects) + update_packs(objects) payment = create_payment_document(price, objects, payment_id, payment_type) WalletService.debit_user_wallet(payment, @customer) @@ -119,4 +120,12 @@ class ShoppingCart UsersCredits::Manager.new(reservation: r).update_credits end end + + # Handle the update of the user's prepaid-packs + # The total booked minutes are subtracted from the user's prepaid minutes + def update_packs(objects) + objects.filter { |o| o.is_a? Reservation }.each do |reservation| + PrepaidPackService.update_user_minutes(@customer, reservation) + end + end end diff --git a/app/services/invoices_service.rb b/app/services/invoices_service.rb index 6e46eaf73..055fa7fd8 100644 --- a/app/services/invoices_service.rb +++ b/app/services/invoices_service.rb @@ -190,7 +190,7 @@ class InvoicesService invoice.invoice_items.push InvoiceItem.new( amount: payment_details[:elements][:pack], - description: I18n.t('invoices.pack_item', COUNT: pack.prepaid_pack.minutes / 60), + description: I18n.t('invoices.pack_item', COUNT: pack.prepaid_pack.minutes / 60, ITEM: pack.prepaid_pack.priceable.name), object: pack, main: main ) diff --git a/app/services/prepaid_pack_service.rb b/app/services/prepaid_pack_service.rb index c57e2a8a0..8032955e0 100644 --- a/app/services/prepaid_pack_service.rb +++ b/app/services/prepaid_pack_service.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +SECONDS_PER_MINUTE = 60.0 + # Provides methods for PrepaidPack class PrepaidPackService class << self @@ -28,5 +30,43 @@ class PrepaidPackService .where('prepaid_packs.priceable_id = ?', priceable.id) .where('prepaid_packs.priceable_type = ?', priceable.class.name) end + + # subtract the number of used prepaid minutes from the user's count + def update_user_minutes(user, reservation) + # total number of minutes available in user's packs + available_minutes = minutes_available(user, reservation.reservable) + return if available_minutes.zero? + + # total number of minutes in the reservation's slots + slots_minutes = reservation.slots.map do |slot| + (slot.end_at.to_time - slot.start_at.to_time) / SECONDS_PER_MINUTE + end + reservation_minutes = slots_minutes.reduce(:+) || 0 + + # total number of prepaid minutes used by this reservation + consumed = reservation_minutes + consumed = available_minutes if reservation_minutes > available_minutes + + # subtract the consumed minutes for user's current packs + packs = user_packs(user, reservation.reservable).order(minutes_used: :desc) + packs.each do |pack| + pack_available = pack.prepaid_pack.minutes - pack.minutes_used + remaining = pack_available - consumed + remaining = 0 if remaining.negative? + pack_consumed = pack.prepaid_pack.minutes - remaining + pack.update_attributes(minutes_used: pack_consumed) + + consumed -= pack_consumed + end + end + + ## Total number of prepaid minutes available + def minutes_available(user, priceable) + user_packs = user_packs(user, priceable) + total_available = user_packs.map { |up| up.prepaid_pack.minutes }.reduce(:+) || 0 + total_used = user_packs.map(&:minutes_used).reduce(:+) || 0 + + total_available - total_used + end end end diff --git a/config/locales/app.logged.en.yml b/config/locales/app.logged.en.yml index 7d3f4502e..df0c17c53 100644 --- a/config/locales/app.logged.en.yml +++ b/config/locales/app.logged.en.yml @@ -194,6 +194,7 @@ en: packs_summary: prepaid_hours: "Prepaid hours" remaining_HOURS: "You have {HOURS} prepaid hours remaining for this {ITEM, select, Machine{machine} Space{space} other{}}." + no_hours: "You don't have any prepaid hours for this {ITEM, select, Machine{machine} Space{space} other{}}." buy_a_new_pack: "Buy a new pack" #book a training trainings_reserve: diff --git a/config/locales/en.yml b/config/locales/en.yml index 03993db1a..a53d56744 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -114,7 +114,7 @@ en: invoice_text_example: "Our association is not subject to VAT" error_invoice: "Erroneous invoice. The items below ware not booked. Please contact the FabLab for a refund." prepaid_pack: "Prepaid pack of hours" - pack_item: "Pack of %{COUNT} hours" + pack_item: "Pack of %{COUNT} hours for the %{ITEM}" #PDF payment schedule generation payment_schedules: schedule_reference: "Payment schedule reference: %{REF}"