mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-29 18:52:22 +01:00
Merge branch 'dev' for release 5.3.2
This commit is contained in:
commit
f59a01d370
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
||||
# Changelog Fab-manager
|
||||
|
||||
# v5.3.2 2022 January 19
|
||||
|
||||
- Add a test for statistics generation
|
||||
- Fix a bug: missing the Other payment method
|
||||
- Fix a bug: do not display an untranslated string if a prepaid pack has no maximum validity
|
||||
- Fix a bug: statistics not built for instances with plans created before v4.3.3
|
||||
- Fix a bug: when requesting to send the sso migration code, the email was case-sensitive
|
||||
- Fix a bug: the adminsys email was case-sensitive
|
||||
- Fix a bug: members are unable to buy prepaid-packs by wallet
|
||||
- Fix a bug: prepaid-packs without expiration date do not work
|
||||
- [TODO DEPLOY] `rails fablab:maintenance:regenerate_statistics[2020,04]`
|
||||
|
||||
# v5.3.1 2022 January 17
|
||||
|
||||
- Definition of extended prices for spaces is now made in hours (previously in minutes)
|
||||
|
@ -52,10 +52,9 @@ class API::AuthProvidersController < API::ApiController
|
||||
@previous = AuthProvider.previous
|
||||
end
|
||||
|
||||
|
||||
def send_code
|
||||
authorize AuthProvider
|
||||
user = User.find_by(email: params[:email])
|
||||
user = User.find_by('lower(email) = ?', params[:email]&.downcase)
|
||||
|
||||
if user&.auth_token
|
||||
if AuthProvider.active.providable_type != DatabaseProvider.name
|
||||
|
@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface PaymentModalProps {
|
||||
interface CardPaymentModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
||||
@ -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 PaymentModalComponent: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule, cart, customer }) => {
|
||||
const CardPaymentModalComponent: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule, cart, customer }) => {
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
const [gateway, setGateway] = useState<Setting>(null);
|
||||
@ -89,12 +89,12 @@ const PaymentModalComponent: React.FC<PaymentModalProps> = ({ isOpen, toggleModa
|
||||
}
|
||||
};
|
||||
|
||||
export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule, cart, customer }) => {
|
||||
export const CardPaymentModal: React.FC<CardPaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, currentUser, schedule, cart, customer }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<PaymentModalComponent isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} onError={onError} currentUser={currentUser} schedule={schedule} cart={cart} customer={customer} />
|
||||
<CardPaymentModalComponent isOpen={isOpen} toggleModal={toggleModal} afterSuccess={afterSuccess} onError={onError} currentUser={currentUser} schedule={schedule} cart={cart} customer={customer} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('paymentModal', react2angular(PaymentModal, ['isOpen', 'toggleModal', 'afterSuccess', 'onError', 'currentUser', 'schedule', 'cart', 'customer']));
|
||||
Application.Components.component('cardPaymentModal', react2angular(CardPaymentModal, ['isOpen', 'toggleModal', 'afterSuccess', 'onError', 'currentUser', 'schedule', 'cart', 'customer']));
|
@ -6,7 +6,7 @@ import LocalPaymentAPI from '../../../api/local-payment';
|
||||
import FormatLib from '../../../lib/format';
|
||||
import SettingAPI from '../../../api/setting';
|
||||
import { SettingName } from '../../../models/setting';
|
||||
import { PaymentModal } from '../payment-modal';
|
||||
import { CardPaymentModal } from '../card-payment-modal';
|
||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||
import { HtmlTranslate } from '../../base/html-translate';
|
||||
|
||||
@ -147,7 +147,7 @@ export const LocalPaymentForm: React.FC<GatewayFormProps> = ({ onSubmit, onSucce
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<PaymentModal isOpen={onlinePaymentModal}
|
||||
<CardPaymentModal isOpen={onlinePaymentModal}
|
||||
toggleModal={toggleOnlinePaymentModal}
|
||||
afterSuccess={afterCreatePaymentSchedule}
|
||||
onError={onError}
|
||||
|
@ -3,7 +3,7 @@ import { AbstractPaymentModal, GatewayFormProps } from '../abstract-payment-moda
|
||||
import { LocalPaymentForm } from './local-payment-form';
|
||||
import { ShoppingCart } from '../../../models/payment';
|
||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||
import { User, UserRole } from '../../../models/user';
|
||||
import { User } from '../../../models/user';
|
||||
import { Invoice } from '../../../models/invoice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ModalSize } from '../../base/fab-modal';
|
||||
|
@ -25,7 +25,7 @@ interface PayZenModalProps {
|
||||
* This component enables the user to input his card data or process payments, using the PayZen gateway.
|
||||
* Supports Strong-Customer Authentication (SCA).
|
||||
*
|
||||
* This component should not be called directly. Prefer using <PaymentModal> which can handle the configuration
|
||||
* This component should not be called directly. Prefer using <CardPaymentModal> which can handle the configuration
|
||||
* of a different payment gateway.
|
||||
*/
|
||||
export const PayZenModal: React.FC<PayZenModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, currentUser, schedule, customer }) => {
|
||||
|
@ -0,0 +1,95 @@
|
||||
import { Invoice } from '../../../models/invoice';
|
||||
import { PaymentSchedule } from '../../../models/payment-schedule';
|
||||
import { ShoppingCart } from '../../../models/payment';
|
||||
import { User } from '../../../models/user';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import WalletAPI from '../../../api/wallet';
|
||||
import { Wallet } from '../../../models/wallet';
|
||||
import WalletLib from '../../../lib/wallet';
|
||||
import UserLib from '../../../lib/user';
|
||||
import { LocalPaymentModal } from '../local-payment/local-payment-modal';
|
||||
import { CardPaymentModal } from '../card-payment-modal';
|
||||
import PriceAPI from '../../../api/price';
|
||||
import { ComputePriceResult } from '../../../models/price';
|
||||
|
||||
interface PaymentModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
afterSuccess: (result: Invoice|PaymentSchedule) => void,
|
||||
onError: (message: string) => void,
|
||||
cart: ShoppingCart,
|
||||
updateCart: (cart: ShoppingCart) => void,
|
||||
operator: User,
|
||||
schedule?: PaymentSchedule,
|
||||
customer: User
|
||||
}
|
||||
|
||||
/**
|
||||
* This component is responsible for rendering the payment modal.
|
||||
*/
|
||||
export const PaymentModal: React.FC<PaymentModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, updateCart, operator, schedule, customer }) => {
|
||||
// the user's wallet
|
||||
const [wallet, setWallet] = useState<Wallet>(null);
|
||||
// the price of the cart
|
||||
const [price, setPrice] = useState<ComputePriceResult>(null);
|
||||
// the remaining price to pay, after the wallet was changed
|
||||
const [remainingPrice, setRemainingPrice] = useState<number>(null);
|
||||
|
||||
// refresh the wallet when the customer changes
|
||||
useEffect(() => {
|
||||
WalletAPI.getByUser(customer.id).then(wallet => {
|
||||
setWallet(wallet);
|
||||
});
|
||||
}, [customer]);
|
||||
|
||||
// refresh the price when the cart changes
|
||||
useEffect(() => {
|
||||
PriceAPI.compute(cart).then(price => {
|
||||
setPrice(price);
|
||||
});
|
||||
}, [cart]);
|
||||
|
||||
// refresh the remaining price when the cart price was computed and the wallet was retrieved
|
||||
useEffect(() => {
|
||||
if (price && wallet) {
|
||||
setRemainingPrice(new WalletLib(wallet).computeRemainingPrice(price?.price));
|
||||
}
|
||||
}, [price, wallet]);
|
||||
|
||||
/**
|
||||
* Check the conditions for the local payment
|
||||
*/
|
||||
const isLocalPayment = (): boolean => {
|
||||
return (new UserLib(operator).isPrivileged(customer) || remainingPrice === 0);
|
||||
};
|
||||
|
||||
// do not render the modal until the real remaining price is computed
|
||||
if (remainingPrice === null) return null;
|
||||
|
||||
if (isLocalPayment()) {
|
||||
return (
|
||||
<LocalPaymentModal isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
afterSuccess={afterSuccess}
|
||||
onError={onError}
|
||||
cart={cart}
|
||||
updateCart={updateCart}
|
||||
currentUser={operator}
|
||||
customer={customer}
|
||||
schedule={schedule}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<CardPaymentModal isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
afterSuccess={afterSuccess}
|
||||
onError={onError}
|
||||
cart={cart}
|
||||
currentUser={operator}
|
||||
customer={customer}
|
||||
schedule={schedule}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
@ -26,7 +26,7 @@ interface StripeModalProps {
|
||||
* This component enables the user to input his card data or process payments, using the Stripe gateway.
|
||||
* Supports Strong-Customer Authentication (SCA).
|
||||
*
|
||||
* This component should not be called directly. Prefer using <PaymentModal> which can handle the configuration
|
||||
* This component should not be called directly. Prefer using <CardPaymentModal> which can handle the configuration
|
||||
* of a different payment gateway.
|
||||
*/
|
||||
export const StripeModal: React.FC<StripeModalProps> = ({ isOpen, toggleModal, afterSuccess, onError, cart, currentUser, schedule, customer }) => {
|
||||
|
@ -9,10 +9,8 @@ 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';
|
||||
import UserLib from '../../lib/user';
|
||||
import { LocalPaymentModal } from '../payment/local-payment/local-payment-modal';
|
||||
import FormatLib from '../../lib/format';
|
||||
import { PaymentModal } from '../payment/stripe/payment-modal';
|
||||
|
||||
type PackableItem = Machine;
|
||||
|
||||
@ -38,7 +36,6 @@ export const ProposePacksModal: React.FC<ProposePacksModalProps> = ({ isOpen, to
|
||||
const [packs, setPacks] = useState<Array<PrepaidPack>>(null);
|
||||
const [cart, setCart] = useState<ShoppingCart>(null);
|
||||
const [paymentModal, setPaymentModal] = useState<boolean>(false);
|
||||
const [localPaymentModal, setLocalPaymentModal] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
PrepaidPackAPI.index({ priceable_id: item.id, priceable_type: itemType, group_id: customer.group_id, disabled: false })
|
||||
@ -56,13 +53,6 @@ export const ProposePacksModal: React.FC<ProposePacksModalProps> = ({ isOpen, to
|
||||
setPaymentModal(!paymentModal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Open/closes the local payment modal (for admins and managers)
|
||||
*/
|
||||
const toggleLocalPaymentModal = (): void => {
|
||||
setLocalPaymentModal(!localPaymentModal);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the hourly-based price of the given prive, to a total price, based on the duration of the given pack
|
||||
*/
|
||||
@ -82,6 +72,8 @@ export const ProposePacksModal: React.FC<ProposePacksModalProps> = ({ isOpen, to
|
||||
* Return a user-friendly string for the validity of the provided pack
|
||||
*/
|
||||
const formatValidity = (pack: PrepaidPack): string => {
|
||||
if (!pack.validity_interval) return null;
|
||||
|
||||
const period = t(`app.logged.propose_packs_modal.period.${pack.validity_interval}`, { COUNT: pack.validity_count });
|
||||
return t('app.logged.propose_packs_modal.validity', { COUNT: pack.validity_count, PERIODS: period });
|
||||
};
|
||||
@ -105,9 +97,6 @@ export const ProposePacksModal: React.FC<ProposePacksModalProps> = ({ isOpen, to
|
||||
{ prepaid_pack: { id: pack.id } }
|
||||
]
|
||||
});
|
||||
if (new UserLib(operator).isPrivileged(customer)) {
|
||||
return toggleLocalPaymentModal();
|
||||
}
|
||||
togglePaymentModal();
|
||||
};
|
||||
};
|
||||
@ -157,16 +146,9 @@ export const ProposePacksModal: React.FC<ProposePacksModalProps> = ({ isOpen, to
|
||||
afterSuccess={handlePackBought}
|
||||
onError={onError}
|
||||
cart={cart}
|
||||
currentUser={operator}
|
||||
customer={customer} />
|
||||
<LocalPaymentModal isOpen={localPaymentModal}
|
||||
toggleModal={toggleLocalPaymentModal}
|
||||
afterSuccess={handlePackBought}
|
||||
onError={onError}
|
||||
cart={cart}
|
||||
updateCart={setCart}
|
||||
currentUser={operator}
|
||||
customer={customer} />
|
||||
operator={operator}
|
||||
customer={customer}
|
||||
updateCart={setCart} />
|
||||
</div>}
|
||||
</FabModal>
|
||||
);
|
||||
|
@ -35,7 +35,6 @@ interface RenewModalProps {
|
||||
* Modal dialog shown to renew the current subscription of a customer, for free
|
||||
*/
|
||||
const RenewModal: React.FC<RenewModalProps> = ({ isOpen, toggleModal, subscription, customer, operator, onError, onSuccess }) => {
|
||||
|
||||
// we do not render the modal if the subscription was not provided
|
||||
if (!subscription) return null;
|
||||
|
||||
|
@ -8,7 +8,7 @@ export default class WalletLib {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the price remaining to pay, after we have used the maximum possible amount in the wallet
|
||||
* Return the price remaining to pay, after we have used the maximum possible amount in the wallet.
|
||||
*/
|
||||
computeRemainingPrice = (price: number): number => {
|
||||
if (this.wallet.amount > price) {
|
||||
|
@ -19,7 +19,8 @@ export interface IntentConfirmation {
|
||||
export enum PaymentMethod {
|
||||
Card = 'card',
|
||||
Check = 'check',
|
||||
Transfer = 'transfer'
|
||||
Transfer = 'transfer',
|
||||
Other = ''
|
||||
}
|
||||
|
||||
export type CartItem = { reservation: Reservation }|
|
||||
|
@ -206,13 +206,13 @@
|
||||
</div>
|
||||
|
||||
<div ng-if="onlinePayment.showModal">
|
||||
<payment-modal is-open="onlinePayment.showModal"
|
||||
toggle-modal="toggleOnlinePaymentModal"
|
||||
after-success="afterOnlinePaymentSuccess"
|
||||
on-error="onOnlinePaymentError"
|
||||
cart="onlinePayment.cartItems"
|
||||
current-user="currentUser"
|
||||
customer="ctrl.member"/>
|
||||
<card-payment-modal is-open="onlinePayment.showModal"
|
||||
toggle-modal="toggleOnlinePaymentModal"
|
||||
after-success="afterOnlinePaymentSuccess"
|
||||
on-error="onOnlinePaymentError"
|
||||
cart="onlinePayment.cartItems"
|
||||
current-user="currentUser"
|
||||
customer="ctrl.member"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -200,14 +200,14 @@
|
||||
|
||||
</div>
|
||||
<div ng-if="onlinePayment.showModal">
|
||||
<payment-modal is-open="onlinePayment.showModal"
|
||||
toggle-modal="toggleOnlinePaymentModal"
|
||||
after-success="afterOnlinePaymentSuccess"
|
||||
on-error="onOnlinePaymentError"
|
||||
cart="onlinePayment.cartItems"
|
||||
current-user="currentUser"
|
||||
customer="user"
|
||||
schedule="schedule.payment_schedule"/>
|
||||
<card-payment-modal is-open="onlinePayment.showModal"
|
||||
toggle-modal="toggleOnlinePaymentModal"
|
||||
after-success="afterOnlinePaymentSuccess"
|
||||
on-error="onOnlinePaymentError"
|
||||
cart="onlinePayment.cartItems"
|
||||
current-user="currentUser"
|
||||
customer="user"
|
||||
schedule="schedule.payment_schedule"/>
|
||||
</div>
|
||||
|
||||
<div ng-if="localPayment.showModal">
|
||||
|
@ -49,13 +49,13 @@
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-info" ng-click="ok()" ng-disabled="attempting" ng-bind-html="validButtonName"></button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
<payment-modal is-open="isOpenOnlinePaymentModal"
|
||||
toggle-modal="toggleOnlinePaymentModal"
|
||||
after-success="afterCreatePaymentSchedule"
|
||||
on-error="onCreatePaymentScheduleError"
|
||||
cart="cartItems"
|
||||
current-user="currentUser"
|
||||
schedule="schedule"
|
||||
customer="user"
|
||||
processPayment="false"/>
|
||||
<card-payment-modal is-open="isOpenOnlinePaymentModal"
|
||||
toggle-modal="toggleOnlinePaymentModal"
|
||||
after-success="afterCreatePaymentSchedule"
|
||||
on-error="onCreatePaymentScheduleError"
|
||||
cart="cartItems"
|
||||
current-user="currentUser"
|
||||
schedule="schedule"
|
||||
customer="user"
|
||||
processPayment="false"/>
|
||||
</div>
|
||||
|
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StatConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
@ -12,7 +14,7 @@ module StatConcern
|
||||
attribute :group, String
|
||||
|
||||
# has include Elasticsearch::Persistence::Model
|
||||
index_name "stats"
|
||||
document_type self.to_s.demodulize.underscore
|
||||
index_name 'stats'
|
||||
document_type to_s.demodulize&.underscore
|
||||
end
|
||||
end
|
||||
|
@ -18,7 +18,9 @@ class PrepaidPack < ApplicationRecord
|
||||
validates :amount, :group_id, :priceable_id, :priceable_type, :minutes, presence: true
|
||||
|
||||
def validity
|
||||
validity_count.send(validity_interval)
|
||||
return nil if validity_interval.nil?
|
||||
|
||||
validity_count&.send(validity_interval)
|
||||
end
|
||||
|
||||
def destroyable?
|
||||
|
@ -14,6 +14,8 @@ class StatisticProfilePrepaidPack < ApplicationRecord
|
||||
private
|
||||
|
||||
def set_expiration_date
|
||||
return unless prepaid_pack.validity
|
||||
|
||||
self.expires_at = DateTime.current + prepaid_pack.validity
|
||||
end
|
||||
end
|
||||
|
@ -121,7 +121,7 @@ class User < ApplicationRecord
|
||||
def self.adminsys
|
||||
return unless Rails.application.secrets.adminsys_email.present?
|
||||
|
||||
User.find_by(email: Rails.application.secrets.adminsys_email)
|
||||
User.find_by('lower(email) = ?', Rails.application.secrets.adminsys_email&.downcase)
|
||||
end
|
||||
|
||||
def training_machine?(machine)
|
||||
|
@ -24,7 +24,7 @@ class PrepaidPackService
|
||||
.includes(:prepaid_pack)
|
||||
.references(:prepaid_packs)
|
||||
.where('statistic_profile_id = ?', user.statistic_profile.id)
|
||||
.where('expires_at > ?', DateTime.current)
|
||||
.where('expires_at > ? OR expires_at IS NULL', DateTime.current)
|
||||
.where('prepaid_packs.priceable_id = ?', priceable.id)
|
||||
.where('prepaid_packs.priceable_type = ?', priceable.class.name)
|
||||
.where('minutes_used < prepaid_packs.minutes')
|
||||
|
@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# From Fab-manager v4.3.3, the behavior of ActiveSupport::Duration#to_i has changed.
|
||||
# Previously, a month = 30 days, from since a month = 30.436875 days.
|
||||
# Also, previously a year = 365.25 days, from since a year = 365.2425 days.
|
||||
# This introduced a bug due to the key of the statistic types for subscriptions were equal to
|
||||
# the number of seconds of the plan duration, but this duration has changed due to the
|
||||
# change reported above.
|
||||
# This migration fixes the problem by changing the key of the statistic types for subscriptions
|
||||
# to the new plans durations.
|
||||
class FixSubscriptionStatisticTypes < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
one_month = 2_592_000
|
||||
(1..12).each do |n|
|
||||
StatisticType.where(key: (one_month * n).to_s).update_all(key: n.months.to_i)
|
||||
end
|
||||
one_year = 31_557_600
|
||||
(1..10).each do |n|
|
||||
StatisticType.where(key: (one_year * n).to_s).update_all(key: n.years.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
one_month = 2_592_000
|
||||
(1..12).each do |n|
|
||||
StatisticType.where(key: n.months.to_i.to_s).update_all(key: (one_month * n).to_i)
|
||||
end
|
||||
one_year = 31_557_600
|
||||
(1..10).each do |n|
|
||||
StatisticType.where(key: n.years.to_i).update_all(key: (one_year * n).to_s)
|
||||
end
|
||||
end
|
||||
end
|
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2022_01_11_134253) do
|
||||
ActiveRecord::Schema.define(version: 2022_01_18_123741) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "fuzzystrmatch"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fab-manager",
|
||||
"version": "5.3.1",
|
||||
"version": "5.3.2",
|
||||
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
|
||||
"keywords": [
|
||||
"fablab",
|
||||
|
10
test/fixtures/availabilities.yml
vendored
10
test/fixtures/availabilities.yml
vendored
@ -179,3 +179,13 @@ availability_18:
|
||||
updated_at: 2017-02-15 15:53:35.154433000 Z
|
||||
nb_total_places: 5
|
||||
destroying: false
|
||||
|
||||
availability_19:
|
||||
id: 19
|
||||
start_at: <%= 1.day.from_now.utc.change({hour: 8}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
|
||||
end_at: <%= 1.day.from_now.utc.change({hour: 18}).strftime('%Y-%m-%d %H:%M:%S.%9N Z') %>
|
||||
available_type: machines
|
||||
created_at: 2017-02-15 15:53:35.154433000 Z
|
||||
updated_at: 2017-02-15 15:53:35.154433000 Z
|
||||
nb_total_places:
|
||||
destroying: false
|
||||
|
5
test/fixtures/machines_availabilities.yml
vendored
5
test/fixtures/machines_availabilities.yml
vendored
@ -103,3 +103,8 @@ machines_availability_21:
|
||||
id: 21
|
||||
machine_id: 2
|
||||
availability_id: 16
|
||||
|
||||
machines_availability_22:
|
||||
id: 22
|
||||
machine_id: 1
|
||||
availability_id: 19
|
||||
|
61
test/services/statistic_service_test.rb
Normal file
61
test/services/statistic_service_test.rb
Normal file
@ -0,0 +1,61 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
class StatisticServiceTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@user = User.members.without_subscription.first
|
||||
@admin = User.with_role(:admin).first
|
||||
login_as(@admin, scope: :user)
|
||||
end
|
||||
|
||||
def test
|
||||
machine_stats_count = Stats::Machine.all.count
|
||||
subscription_stats_count = Stats::Subscription.all.count
|
||||
|
||||
# Create a reservation to generate an invoice
|
||||
machine = Machine.find(1)
|
||||
availability = Availability.find(19)
|
||||
post '/api/local_payment/confirm_payment', params: {
|
||||
customer_id: @user.id,
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_attributes: [
|
||||
{
|
||||
start_at: availability.start_at.to_s(:iso8601),
|
||||
end_at: (availability.start_at + 1.hour).to_s(:iso8601),
|
||||
availability_id: availability.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}.to_json, headers: default_headers
|
||||
|
||||
# Create a subscription to generate another invoice
|
||||
plan = Plan.find_by(group_id: @user.group.id, type: 'Plan')
|
||||
post '/api/local_payment/confirm_payment',
|
||||
params: {
|
||||
customer_id: @user.id,
|
||||
items: [
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
]
|
||||
}.to_json, headers: default_headers
|
||||
|
||||
# Build the stats for today, we expect the above invoices (reservation+subscription) to appear in the resulting stats
|
||||
StatisticService.new.generate_statistic(
|
||||
start_date: DateTime.current.beginning_of_day,
|
||||
end_date: DateTime.current.end_of_day
|
||||
)
|
||||
|
||||
assert_equal machine_stats_count + 1, Stats::Machine.all.count
|
||||
assert_equal subscription_stats_count + 1, Stats::Subscription.all.count
|
||||
end
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user