diff --git a/app/frontend/src/javascript/components/machines/machine-card.tsx b/app/frontend/src/javascript/components/machines/machine-card.tsx index 40f889e6c..ebf07f390 100644 --- a/app/frontend/src/javascript/components/machines/machine-card.tsx +++ b/app/frontend/src/javascript/components/machines/machine-card.tsx @@ -13,13 +13,14 @@ interface MachineCardProps { onLoginRequested: () => Promise, onEnrollRequested: (trainingId: number) => void, onError: (message: string) => void, + onSuccess: (message: string) => void, } /** * This component is a box showing the picture of the given machine and two buttons: one to start the reservation process * and another to redirect the user to the machine description page. */ -const MachineCardComponent: React.FC = ({ user, machine, onShowMachine, onReserveMachine, onError, onLoginRequested, onEnrollRequested }) => { +const MachineCardComponent: React.FC = ({ user, machine, onShowMachine, onReserveMachine, onError, onSuccess, onLoginRequested, onEnrollRequested }) => { const { t } = useTranslation('public'); // shall we display a loader to prevent double-clicking, while the machine details are loading? @@ -60,6 +61,7 @@ const MachineCardComponent: React.FC = ({ user, machine, onSho onLoadingStart={() => setLoading(true)} onLoadingEnd={() => setLoading(false)} onError={onError} + onSuccess={onSuccess} onReserveMachine={handleReserveMachine} onLoginRequested={onLoginRequested} onEnrollRequested={onEnrollRequested} @@ -79,10 +81,10 @@ const MachineCardComponent: React.FC = ({ user, machine, onSho } -export const MachineCard: React.FC = ({ user, machine, onShowMachine, onReserveMachine, onError, onLoginRequested, onEnrollRequested }) => { +export const MachineCard: React.FC = ({ user, machine, onShowMachine, onReserveMachine, onError, onSuccess, onLoginRequested, onEnrollRequested }) => { return ( - + ); } diff --git a/app/frontend/src/javascript/components/machines/machines-list.tsx b/app/frontend/src/javascript/components/machines/machines-list.tsx index 7f286a246..5177eea62 100644 --- a/app/frontend/src/javascript/components/machines/machines-list.tsx +++ b/app/frontend/src/javascript/components/machines/machines-list.tsx @@ -13,6 +13,7 @@ declare var Application: IApplication; interface MachinesListProps { user?: User, onError: (message: string) => void, + onSuccess: (message: string) => void, onShowMachine: (machine: Machine) => void, onReserveMachine: (machine: Machine) => void, onLoginRequested: () => Promise, @@ -22,7 +23,7 @@ interface MachinesListProps { /** * This component shows a list of all machines and allows filtering on that list. */ -const MachinesList: React.FC = ({ onError, onShowMachine, onReserveMachine, onLoginRequested, onEnrollRequested, user }) => { +const MachinesList: React.FC = ({ onError, onSuccess, onShowMachine, onReserveMachine, onLoginRequested, onEnrollRequested, user }) => { // shown machines const [machines, setMachines] = useState>(null); // we keep the full list of machines, for filtering @@ -65,6 +66,7 @@ const MachinesList: React.FC = ({ onError, onShowMachine, onR onShowMachine={onShowMachine} onReserveMachine={onReserveMachine} onError={onError} + onSuccess={onSuccess} onLoginRequested={onLoginRequested} onEnrollRequested={onEnrollRequested} /> })} @@ -74,12 +76,12 @@ const MachinesList: React.FC = ({ onError, onShowMachine, onR } -const MachinesListWrapper: React.FC = ({ user, onError, onShowMachine, onReserveMachine, onLoginRequested, onEnrollRequested }) => { +const MachinesListWrapper: React.FC = ({ user, onError, onSuccess, onShowMachine, onReserveMachine, onLoginRequested, onEnrollRequested }) => { return ( - + ); } -Application.Components.component('machinesList', react2angular(MachinesListWrapper, ['user', 'onError', 'onShowMachine', 'onReserveMachine', 'onLoginRequested', 'onEnrollRequested'])); +Application.Components.component('machinesList', react2angular(MachinesListWrapper, ['user', 'onError', 'onSuccess', 'onShowMachine', 'onReserveMachine', 'onLoginRequested', 'onEnrollRequested'])); diff --git a/app/frontend/src/javascript/components/machines/propose-packs-modal.tsx b/app/frontend/src/javascript/components/machines/propose-packs-modal.tsx index 66dccda5b..ba9a5c61c 100644 --- a/app/frontend/src/javascript/components/machines/propose-packs-modal.tsx +++ b/app/frontend/src/javascript/components/machines/propose-packs-modal.tsx @@ -9,6 +9,8 @@ import { IFablab } from '../../models/fablab'; import { FabButton } from '../base/fab-button'; import PriceAPI from '../../api/price'; import { Price } from '../../models/price'; +import { PaymentMethod, ShoppingCart } from '../../models/payment'; +import { PaymentModal } from '../payment/payment-modal'; declare var Fablab: IFablab; @@ -17,18 +19,22 @@ interface ProposePacksModalProps { toggleModal: () => void, machine: Machine, customer: User, + operator: User, onError: (message: string) => void, onDecline: (machine: Machine) => void, + onSuccess: (message:string, machine: Machine) => void, } /** * Modal dialog shown to offer prepaid-packs for purchase, to the current user. */ -export const ProposePacksModal: React.FC = ({ isOpen, toggleModal, machine, customer, onError, onDecline }) => { +export const ProposePacksModal: React.FC = ({ isOpen, toggleModal, machine, customer, operator, onError, onDecline, onSuccess }) => { const { t } = useTranslation('logged'); const [price, setPrice] = useState(null); const [packs, setPacks] = useState>(null); + const [cart, setCart] = useState(null); + const [paymentModal, setPaymentModal] = useState(false); useEffect(() => { PrepaidPackAPI.index({ priceable_id: machine.id, priceable_type: 'Machine', group_id: customer.group_id, disabled: false }) @@ -40,6 +46,13 @@ export const ProposePacksModal: React.FC = ({ isOpen, to }, [machine]); + /** + * Open/closes the payment modal + */ + const togglePaymentModal = (): void => { + setPaymentModal(!paymentModal); + } + /** * Return the formatted localized amount for the given price (e.g. 20.5 => "20,50 €") */ @@ -78,14 +91,28 @@ export const ProposePacksModal: React.FC = ({ isOpen, to } /** - * The user has accepted to buy the provided pack, process with teh payment + * The user has accepted to buy the provided pack, process with the payment */ const handleBuyPack = (pack: PrepaidPack) => { - return (event: BaseSyntheticEvent): void => { - console.log(pack); + return (): void => { + setCart({ + customer_id: customer.id, + payment_method: PaymentMethod.Card, + items: [ + { prepaid_pack: { id: pack.id }} + ] + }); + togglePaymentModal(); } } + /** + * Callback triggered when the user has bought the pack with a successful payment + */ + const handlePackBought = (): void => { + onSuccess(t('app.logged.propose_packs_modal.pack_bought_success'), machine); + } + /** * Render the given prepaid-pack */ @@ -118,6 +145,13 @@ export const ProposePacksModal: React.FC = ({ isOpen, to
{packs?.map(p => renderPack(p))}
+ {cart && } ); } diff --git a/app/frontend/src/javascript/components/machines/reserve-button.tsx b/app/frontend/src/javascript/components/machines/reserve-button.tsx index ecd92d046..e359d7bfa 100644 --- a/app/frontend/src/javascript/components/machines/reserve-button.tsx +++ b/app/frontend/src/javascript/components/machines/reserve-button.tsx @@ -18,6 +18,7 @@ interface ReserveButtonProps { onLoadingStart?: () => void, onLoadingEnd?: () => void, onError: (message: string) => void, + onSuccess: (message: string) => void, onReserveMachine: (machine: Machine) => void, onLoginRequested: () => Promise, onEnrollRequested: (trainingId: number) => void, @@ -27,7 +28,7 @@ interface ReserveButtonProps { /** * Button component that makes the training verification before redirecting the user to the reservation calendar */ -const ReserveButtonComponent: React.FC = ({ currentUser, machineId, onLoginRequested, onLoadingStart, onLoadingEnd, onError, onReserveMachine, onEnrollRequested, className, children }) => { +const ReserveButtonComponent: React.FC = ({ currentUser, machineId, onLoginRequested, onLoadingStart, onLoadingEnd, onError, onSuccess, onReserveMachine, onEnrollRequested, className, children }) => { const { t } = useTranslation('shared'); const [machine, setMachine] = useState(null); @@ -87,6 +88,15 @@ const ReserveButtonComponent: React.FC = ({ currentUser, mac setProposePacks(!proposePacks); } + /** + * Callback triggered when the user has successfully bought a pre-paid pack. + * Display the success message and redirect him to the booking page. + */ + const handlePackBought = (message: string, machine: Machine): void => { + onSuccess(message); + onReserveMachine(machine); + } + /** * Check that the current user has passed the required training before allowing him to book */ @@ -154,20 +164,22 @@ const ReserveButtonComponent: React.FC = ({ currentUser, mac machine={machine} onError={onError} customer={currentUser} - onDecline={onReserveMachine} />} + onDecline={onReserveMachine} + operator={currentUser} + onSuccess={handlePackBought} />} ); } -export const ReserveButton: React.FC = ({ currentUser, machineId, onLoginRequested, onLoadingStart, onLoadingEnd, onError, onReserveMachine, onEnrollRequested, className, children }) => { +export const ReserveButton: React.FC = ({ currentUser, machineId, onLoginRequested, onLoadingStart, onLoadingEnd, onError, onSuccess, onReserveMachine, onEnrollRequested, className, children }) => { return ( - + {children} ); } -Application.Components.component('reserveButton', react2angular(ReserveButton, ['currentUser', 'machineId', 'onLoadingStart', 'onLoadingEnd', 'onError', 'onReserveMachine', 'onLoginRequested', 'onEnrollRequested', 'className'])); +Application.Components.component('reserveButton', react2angular(ReserveButton, ['currentUser', 'machineId', 'onLoadingStart', 'onLoadingEnd', 'onError', 'onSuccess', 'onReserveMachine', 'onLoginRequested', 'onEnrollRequested', 'className'])); diff --git a/app/frontend/src/javascript/components/payment/abstract-payment-modal.tsx b/app/frontend/src/javascript/components/payment/abstract-payment-modal.tsx index d28bf749f..e6b345d9b 100644 --- a/app/frontend/src/javascript/components/payment/abstract-payment-modal.tsx +++ b/app/frontend/src/javascript/components/payment/abstract-payment-modal.tsx @@ -39,7 +39,7 @@ interface AbstractPaymentModalProps { afterSuccess: (result: Invoice|PaymentSchedule) => void, cart: ShoppingCart, currentUser: User, - schedule: PaymentSchedule, + schedule?: PaymentSchedule, customer: User, logoFooter: ReactNode, GatewayForm: FunctionComponent, diff --git a/app/frontend/src/javascript/components/payment/payment-modal.tsx b/app/frontend/src/javascript/components/payment/payment-modal.tsx index aa774352a..1d6482b62 100644 --- a/app/frontend/src/javascript/components/payment/payment-modal.tsx +++ b/app/frontend/src/javascript/components/payment/payment-modal.tsx @@ -21,7 +21,7 @@ interface PaymentModalProps { onError: (message: string) => void, cart: ShoppingCart, currentUser: User, - schedule: PaymentSchedule, + schedule?: PaymentSchedule, customer: User } @@ -29,7 +29,7 @@ interface PaymentModalProps { * This component open a modal dialog for the configured payment gateway, allowing the user to input his card data * to process an online payment. */ -const PaymentModal: React.FC = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule , cart, customer }) => { +const PaymentModalComponent: React.FC = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule , cart, customer }) => { const { t } = useTranslation('shared'); const [gateway, setGateway] = useState(null); @@ -88,12 +88,12 @@ const PaymentModal: React.FC = ({ isOpen, toggleModal, afterS } -const PaymentModalWrapper: React.FC = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule , cart, customer }) => { +export const PaymentModal: React.FC = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule , cart, customer }) => { return ( - + ); } -Application.Components.component('paymentModal', react2angular(PaymentModalWrapper, ['isOpen', 'toggleModal', 'afterSuccess', 'onError', 'currentUser', 'schedule', 'cart', 'customer'])); +Application.Components.component('paymentModal', react2angular(PaymentModal, ['isOpen', 'toggleModal', 'afterSuccess', 'onError', 'currentUser', 'schedule', 'cart', 'customer'])); diff --git a/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx b/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx index 873d1ff1c..d69191b2f 100644 --- a/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx +++ b/app/frontend/src/javascript/components/payment/payzen/payzen-form.tsx @@ -17,7 +17,7 @@ import { Invoice } from '../../../models/invoice'; // we use these two additional parameters to update the card, if provided interface PayzenFormProps extends GatewayFormProps { updateCard?: boolean, - paymentScheduleId: number, + paymentScheduleId?: number, } /** diff --git a/app/frontend/src/javascript/components/payment/payzen/payzen-modal.tsx b/app/frontend/src/javascript/components/payment/payzen/payzen-modal.tsx index c7f4f5873..a8b9256ba 100644 --- a/app/frontend/src/javascript/components/payment/payzen/payzen-modal.tsx +++ b/app/frontend/src/javascript/components/payment/payzen/payzen-modal.tsx @@ -17,7 +17,7 @@ interface PayZenModalProps { afterSuccess: (result: Invoice|PaymentSchedule) => void, cart: ShoppingCart, currentUser: User, - schedule: PaymentSchedule, + schedule?: PaymentSchedule, customer: User } diff --git a/app/frontend/src/javascript/components/payment/stripe/stripe-modal.tsx b/app/frontend/src/javascript/components/payment/stripe/stripe-modal.tsx index 899d56868..c201d8994 100644 --- a/app/frontend/src/javascript/components/payment/stripe/stripe-modal.tsx +++ b/app/frontend/src/javascript/components/payment/stripe/stripe-modal.tsx @@ -18,7 +18,7 @@ interface StripeModalProps { afterSuccess: (result: Invoice|PaymentSchedule) => void, cart: ShoppingCart, currentUser: User, - schedule: PaymentSchedule, + schedule?: PaymentSchedule, customer: User } diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb index cd1524da8..dfd3727c1 100644 --- a/app/frontend/src/javascript/controllers/machines.js.erb +++ b/app/frontend/src/javascript/controllers/machines.js.erb @@ -114,6 +114,13 @@ Application.Controllers.controller('MachinesController', ['$scope', '$state', '_ growl.error(message); } + /** + * Shows a success message forwarded from a child react components + */ + $scope.onSuccess = function (message) { + growl.success(message) + } + /** * Open the modal dialog to log the user and resolves the returned promise when the logging process * was successfully completed. @@ -311,6 +318,14 @@ Application.Controllers.controller('ShowMachineController', ['$scope', '$state', growl.error(message); } + /** + * Shows a success message forwarded from a child react components + */ + $scope.onSuccess = function (message) { + growl.success(message) + } + + /** * Open the modal dialog to log the user and resolves the returned promise when the logging process * was successfully completed. diff --git a/app/frontend/src/javascript/models/payment.ts b/app/frontend/src/javascript/models/payment.ts index 9106eb8a8..7bc5f99da 100644 --- a/app/frontend/src/javascript/models/payment.ts +++ b/app/frontend/src/javascript/models/payment.ts @@ -19,7 +19,7 @@ export enum PaymentMethod { Other = '' } -export type CartItem = { reservation: Reservation }|{ subscription: SubscriptionRequest }|{ card_update: { date: Date } }; +export type CartItem = { reservation: Reservation }|{ subscription: SubscriptionRequest }|{ prepaid_pack: { id: number } }; export interface ShoppingCart { customer_id: number, diff --git a/app/frontend/templates/machines/index.html b/app/frontend/templates/machines/index.html index f27fe56bf..0bc7e619b 100644 --- a/app/frontend/templates/machines/index.html +++ b/app/frontend/templates/machines/index.html @@ -43,6 +43,7 @@ diff --git a/app/models/cart_item/prepaid_pack.rb b/app/models/cart_item/prepaid_pack.rb new file mode 100644 index 000000000..f7b1b802c --- /dev/null +++ b/app/models/cart_item/prepaid_pack.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# A prepaid-pack added to the shopping cart +class CartItem::PrepaidPack < CartItem::BaseItem + def initialize(pack, customer) + raise TypeError unless pack.is_a? PrepaidPack + + @pack = pack + @customer = customer + super + end + + def pack + raise InvalidGroupError if @pack.group_id != @customer.group_id + + @pack + end + + def price + amount = pack.amount + elements = { pack: amount } + + { elements: elements, amount: amount } + end + + def name + "#{@pack.minutes / 60} h" + end + + def to_object + ::StatisticProfilePrepaidPack.new( + prepaid_pack_id: @pack.id, + statistic_profile_id: StatisticProfile.find_by(user: @customer).id + ) + end +end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index 37e5aa6cd..3e0e6a108 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -54,7 +54,7 @@ class Invoice < PaymentDocument # for debug & used by rake task "fablab:maintenance:regenerate_invoices" def regenerate_invoice_pdf - pdf = ::PDF::Invoice.new(self, invoice_items.find(&:subscription)&.expiration_date).render + pdf = ::PDF::Invoice.new(self, invoice_items.find_by(object_type: Subscription.name)&.expiration_date).render File.binwrite(file, pdf) end diff --git a/app/models/invoice_item.rb b/app/models/invoice_item.rb index 3b9fc0519..3b9865100 100644 --- a/app/models/invoice_item.rb +++ b/app/models/invoice_item.rb @@ -12,6 +12,7 @@ class InvoiceItem < Footprintable belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'object_id' belongs_to :wallet_transaction, foreign_type: 'WalletTransaction', foreign_key: 'object_id' belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'object_id' + belongs_to :statistic_profile_prepaid_pack, foreign_type: 'StatisticProfilePrepaidPack', foreign_key: 'object_id' after_create :chain_record after_update :log_changes diff --git a/app/models/payment_schedule_object.rb b/app/models/payment_schedule_object.rb index 9c7f99fc0..b5785d16c 100644 --- a/app/models/payment_schedule_object.rb +++ b/app/models/payment_schedule_object.rb @@ -7,6 +7,7 @@ class PaymentScheduleObject < Footprintable belongs_to :subscription, foreign_type: 'Subscription', foreign_key: 'object_id' belongs_to :wallet_transaction, foreign_type: 'WalletTransaction', foreign_key: 'object_id' belongs_to :offer_day, foreign_type: 'OfferDay', foreign_key: 'object_id' + belongs_to :statistic_profile_prepaid_pack, foreign_type: 'StatisticProfilePrepaidPack', foreign_key: 'object_id' belongs_to :payment_schedule after_create :chain_record diff --git a/app/models/statistic_profile_prepaid_pack.rb b/app/models/statistic_profile_prepaid_pack.rb index a4c9aec11..913d9ccaf 100644 --- a/app/models/statistic_profile_prepaid_pack.rb +++ b/app/models/statistic_profile_prepaid_pack.rb @@ -5,4 +5,15 @@ class StatisticProfilePrepaidPack < ApplicationRecord belongs_to :prepaid_pack belongs_to :statistic_profile + + has_many :invoice_items, as: :object, dependent: :destroy + has_one :payment_schedule_object, as: :object, dependent: :destroy + + before_create :set_expiration_date + + private + + def set_expiration_date + self.expires_at = DateTime.current + prepaid_pack.validity + end end diff --git a/app/pdfs/pdf/invoice.rb b/app/pdfs/pdf/invoice.rb index 5add0adf0..4065fe6b1 100644 --- a/app/pdfs/pdf/invoice.rb +++ b/app/pdfs/pdf/invoice.rb @@ -110,6 +110,8 @@ class PDF::Invoice < Prawn::Document object = offer_day_verbose(invoice.main_item.object, name) when 'Error' object = I18n.t('invoices.error_invoice') + when 'StatisticProfilePrepaidPack' + object = I18n.t('invoices.prepaid_pack') else puts "ERROR : specified main_item.object_type type (#{invoice.main_item.object_type}) is unknown" end @@ -132,7 +134,7 @@ class PDF::Invoice < Prawn::Document details = invoice.is_a?(Avoir) ? I18n.t('invoices.cancellation') + ' - ' : '' - if item.subscription ### Subscription + if item.object_type == Subscription.name subscription = item.subscription if invoice.main_item.object_type == 'OfferDay' details += I18n.t('invoices.subscription_extended_for_free_from_START_to_END', @@ -154,7 +156,7 @@ class PDF::Invoice < Prawn::Document end - else ### Reservation + elsif item.object_type == Reservation.name case invoice.main_item.object.try(:reservable_type) ### Machine reservation when 'Machine' @@ -179,6 +181,8 @@ class PDF::Invoice < Prawn::Document else details += item.description end + else + details += item.description end data += [[details, number_to_currency(price)]] diff --git a/app/services/cart_service.rb b/app/services/cart_service.rb index 5b44d9ca4..95d439e1f 100644 --- a/app/services/cart_service.rb +++ b/app/services/cart_service.rb @@ -20,6 +20,8 @@ class CartService items.push(CartItem::Subscription.new(plan_info[:plan], @customer)) if plan_info[:new_subscription] elsif ['reservation', :reservation].include?(item.keys.first) items.push(reservable_from_hash(item[:reservation], plan_info)) + elsif ['prepaid_pack', :prepaid_pack].include?(item.keys.first) + items.push(CartItem::PrepaidPack.new(PrepaidPack.find(item[:prepaid_pack][:id]), @customer)) end end @@ -45,10 +47,12 @@ class CartService items = [] payment_schedule.payment_schedule_objects.each do |object| - if object.subscription + if object.object_type == Subscription.name items.push(CartItem::Subscription.new(object.subscription.plan, @customer)) - elsif object.reservation + elsif object.object_type == Reservation.name items.push(reservable_from_payment_schedule_object(object, plan)) + elsif object.object_type == PrepaidPack.name + items.push(CartItem::PrepaidPack.new(object.statistic_profile_prepaid_pack.prepaid_pack_id, @customer)) end end diff --git a/app/services/invoices_service.rb b/app/services/invoices_service.rb index ae0f3ade1..6e46eaf73 100644 --- a/app/services/invoices_service.rb +++ b/app/services/invoices_service.rb @@ -64,7 +64,7 @@ class InvoicesService # Create an Invoice with an associated array of InvoiceItem matching the given parameters # @param payment_details {Hash} as generated by ShoppingCart.total # @param operator_profile_id {Number} ID of the user that operates the invoice generation (may be an admin, a manager or the customer himself) - # @param objects {Array} the booking reservation and/or subscription + # @param objects {Array} the booked reservation and/or subscription or pack # @param user {User} the customer # @param payment_id {String} ID of the payment, a returned by the gateway, if the current invoice is paid by card # @param payment_method {String} the payment method used @@ -96,7 +96,7 @@ class InvoicesService # Generate an array of {InvoiceItem} with the elements in provided reservation, price included. # @param invoice {Invoice} the parent invoice # @param payment_details {Hash} as generated by ShoppingCart.total - # @param objects {Array} + # @param objects {Array} ## def self.generate_invoice_items(invoice, payment_details, objects) objects.each_with_index do |object, index| @@ -106,6 +106,8 @@ class InvoicesService InvoicesService.generate_subscription_item(invoice, object, payment_details, index.zero?) elsif object.is_a?(Reservation) InvoicesService.generate_reservation_item(invoice, object, payment_details, index.zero?) + elsif object.is_a?(StatisticProfilePrepaidPack) + InvoicesService.generate_prepaid_pack_item(invoice, object, payment_details, index.zero?) else InvoicesService.generate_generic_item(invoice, object, payment_details, index.zero?) end @@ -179,6 +181,21 @@ class InvoicesService ) end + ## + # Generate an InvoiceItem for the given StatisticProfilePrepaidPack and save it in invoice.invoice_items. + # This method must be called only with a valid pack-statistic_profile relation + ## + def self.generate_prepaid_pack_item(invoice, pack, payment_details, main = false) + raise TypeError unless pack + + invoice.invoice_items.push InvoiceItem.new( + amount: payment_details[:elements][:pack], + description: I18n.t('invoices.pack_item', COUNT: pack.prepaid_pack.minutes / 60), + object: pack, + main: main + ) + end + def self.generate_generic_item(invoice, item, payment_details, main = false) invoice.invoice_items.push InvoiceItem.new( amount: payment_details[:elements][item.class.name.to_sym], diff --git a/config/locales/app.logged.en.yml b/config/locales/app.logged.en.yml index c648c1a56..dc8866813 100644 --- a/config/locales/app.logged.en.yml +++ b/config/locales/app.logged.en.yml @@ -184,6 +184,7 @@ en: no_thanks: "No, thanks" pack_DURATION: "{DURATION} hours" buy_this_pack: "Buy this pack" + pack_bought_success: "You have successfully bought this pack of prepaid-hours. Your invoice will ba available soon from your dashboard." validity: "Usable for {COUNT} {PERIODS}" period: day: "{COUNT, plural, one{day} other{days}}" diff --git a/config/locales/en.yml b/config/locales/en.yml index 5a42aaea6..03993db1a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -113,6 +113,8 @@ en: and: 'and' 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" #PDF payment schedule generation payment_schedules: schedule_reference: "Payment schedule reference: %{REF}"