mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2024-11-28 09:24:24 +01:00
(merge) Merge branch 'dev' into product-store
This commit is contained in:
commit
a63975dd18
@ -13,7 +13,7 @@ Metrics/PerceivedComplexity:
|
||||
Metrics/AbcSize:
|
||||
Max: 45
|
||||
Metrics/ClassLength:
|
||||
Max: 200
|
||||
Max: 210
|
||||
Metrics/BlockLength:
|
||||
Max: 30
|
||||
Exclude:
|
||||
|
25
CHANGELOG.md
25
CHANGELOG.md
@ -1,11 +1,34 @@
|
||||
# Changelog Fab-manager
|
||||
|
||||
- Script to download translations from Crowdin
|
||||
- Fablab's store module
|
||||
- Fix a bug: missing translations in PayZen configuration screens
|
||||
- Fix a bug: wrong translation key prevents the display of the schedule deadline's payment mean
|
||||
- [TODO DEPLOY] `rails db:seed`
|
||||
|
||||
## v5.4.21 2022 October 05
|
||||
|
||||
- Ability to dismiss a user to a lower privileged role
|
||||
- Fix a bug: unable to generate statistics
|
||||
- [TODO DEPLOY] `rails fablab:maintenance:regenerate_statistics[2022,08]`
|
||||
|
||||
## v5.4.20 2022 September 27
|
||||
|
||||
- Fix a bug: unable to show the daily view of the public agenda, if it contains trainings or events
|
||||
- Fix a bug: plan's categories descriptions are not shown
|
||||
- Fix a bug: groups without plans are shown but empty
|
||||
- Fix a bug: unable to display the payment schedules management interface
|
||||
|
||||
## v5.4.19 2022 September 13
|
||||
|
||||
- Fix a bug: computing the wallet amount to debit ignores the applied coupon
|
||||
|
||||
## v5.4.18 2022 September 12
|
||||
|
||||
- Script to download translations from Crowdin
|
||||
- Fix a bug: admin and managers can't cancel or move event reservations
|
||||
- Fix a bug: phone numbers with hyphens and spaces prevent profile completion when the data is provided by an SSO
|
||||
- Fix a bug: unable to complete profile from SSO when the account validation is enabled
|
||||
|
||||
## v5.4.17 2022 September 06
|
||||
|
||||
- OpenAPI spaces endpoints (index/show)
|
||||
|
@ -18,15 +18,7 @@ class API::MembersController < API::ApiController
|
||||
end
|
||||
|
||||
def last_subscribed
|
||||
@query = User.active.with_role(:member)
|
||||
.includes(:statistic_profile, profile: [:user_avatar])
|
||||
.where('is_allow_contact = true AND confirmed_at IS NOT NULL')
|
||||
.order('created_at desc')
|
||||
.limit(params[:last])
|
||||
|
||||
# remove unmerged profiles from list
|
||||
@members = @query.to_a
|
||||
@members.delete_if(&:need_completion?)
|
||||
@query, @members = Members::MembersService.last_registered(params[:last])
|
||||
|
||||
@requested_attributes = ['profile']
|
||||
render :index
|
||||
@ -74,9 +66,7 @@ class API::MembersController < API::ApiController
|
||||
def export_subscriptions
|
||||
authorize :export
|
||||
|
||||
export = Export.where(category: 'users', export_type: 'subscriptions')
|
||||
.where('created_at > ?', Subscription.maximum('updated_at'))
|
||||
.last
|
||||
export = ExportService.last_export('users/subscription')
|
||||
if export.nil? || !FileTest.exist?(export.file)
|
||||
@export = Export.new(category: 'users', export_type: 'subscriptions', user: current_user)
|
||||
if @export.save
|
||||
@ -85,7 +75,7 @@ class API::MembersController < API::ApiController
|
||||
render json: @export.errors, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
send_file File.join(Rails.root, export.file),
|
||||
send_file Rails.root.join(export.file),
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
disposition: 'attachment'
|
||||
end
|
||||
@ -95,9 +85,7 @@ class API::MembersController < API::ApiController
|
||||
def export_reservations
|
||||
authorize :export
|
||||
|
||||
export = Export.where(category: 'users', export_type: 'reservations')
|
||||
.where('created_at > ?', Reservation.maximum('updated_at'))
|
||||
.last
|
||||
export = ExportService.last_export('users/reservations')
|
||||
if export.nil? || !FileTest.exist?(export.file)
|
||||
@export = Export.new(category: 'users', export_type: 'reservations', user: current_user)
|
||||
if @export.save
|
||||
@ -106,7 +94,7 @@ class API::MembersController < API::ApiController
|
||||
render json: @export.errors, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
send_file File.join(Rails.root, export.file),
|
||||
send_file Rails.root.join(export.file),
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
disposition: 'attachment'
|
||||
end
|
||||
@ -115,17 +103,7 @@ class API::MembersController < API::ApiController
|
||||
def export_members
|
||||
authorize :export
|
||||
|
||||
last_update = [
|
||||
User.members.maximum('updated_at'),
|
||||
Profile.where(user_id: User.members).maximum('updated_at'),
|
||||
InvoicingProfile.where(user_id: User.members).maximum('updated_at'),
|
||||
StatisticProfile.where(user_id: User.members).maximum('updated_at'),
|
||||
Subscription.maximum('updated_at') || DateTime.current
|
||||
].max
|
||||
|
||||
export = Export.where(category: 'users', export_type: 'members')
|
||||
.where('created_at > ?', last_update)
|
||||
.last
|
||||
export = ExportService.last_export('users/members')
|
||||
if export.nil? || !FileTest.exist?(export.file)
|
||||
@export = Export.new(category: 'users', export_type: 'members', user: current_user)
|
||||
if @export.save
|
||||
@ -134,7 +112,7 @@ class API::MembersController < API::ApiController
|
||||
render json: @export.errors, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
send_file File.join(Rails.root, export.file),
|
||||
send_file Rails.root.join(export.file),
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
disposition: 'attachment'
|
||||
end
|
||||
@ -158,8 +136,8 @@ class API::MembersController < API::ApiController
|
||||
else
|
||||
render json: @member.errors, status: :unprocessable_entity
|
||||
end
|
||||
rescue DuplicateIndexError => error
|
||||
render json: { error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: error.message) },
|
||||
rescue DuplicateIndexError => e
|
||||
render json: { error: t('members.please_input_the_authentication_code_sent_to_the_address', EMAIL: e.message) },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
@ -176,7 +154,6 @@ class API::MembersController < API::ApiController
|
||||
query = Members::ListService.list(query_params)
|
||||
@max_members = query.except(:offset, :limit, :order).count
|
||||
@members = query.to_a
|
||||
|
||||
end
|
||||
|
||||
def search
|
||||
@ -196,7 +173,7 @@ class API::MembersController < API::ApiController
|
||||
render json: { tours: [params[:tour]] }
|
||||
else
|
||||
tours = "#{@member.profile.tours} #{params[:tour]}"
|
||||
@member.profile.update_attributes(tours: tours.strip)
|
||||
@member.profile.update(tours: tours.strip)
|
||||
|
||||
render json: { tours: @member.profile.tours.split }
|
||||
end
|
||||
@ -205,31 +182,8 @@ class API::MembersController < API::ApiController
|
||||
def update_role
|
||||
authorize @member
|
||||
|
||||
# we do not allow dismissing a user to a lower role
|
||||
if params[:role] == 'member'
|
||||
render 403 and return if @member.role == 'admin' || @member.role == 'manager'
|
||||
elsif params[:role] == 'manager'
|
||||
render 403 and return if @member.role == 'admin'
|
||||
end
|
||||
|
||||
# do nothing if the role does not change
|
||||
render json: @member and return if params[:role] == @member.role
|
||||
|
||||
ex_role = @member.role.to_sym
|
||||
@member.remove_role ex_role
|
||||
@member.add_role params[:role]
|
||||
|
||||
# if the new role is 'admin', then change the group to the admins group
|
||||
@member.update_attributes(group_id: Group.find_by(slug: 'admins').id) if params[:role] == 'admin'
|
||||
|
||||
NotificationCenter.call type: 'notify_user_role_update',
|
||||
receiver: @member,
|
||||
attached_object: @member
|
||||
|
||||
NotificationCenter.call type: 'notify_admins_role_update',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: @member,
|
||||
meta_data: { ex_role: ex_role }
|
||||
service = Members::MembersService.new(@member)
|
||||
service.update_role(params[:role], params[:group_id])
|
||||
|
||||
render json: @member
|
||||
end
|
||||
@ -265,12 +219,14 @@ class API::MembersController < API::ApiController
|
||||
profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
user_avatar_attributes: %i[id attachment destroy]],
|
||||
{ user_avatar_attributes: %i[id attachment destroy] }],
|
||||
invoicing_profile_attributes: [
|
||||
:id, :organization,
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name, address_attributes: %i[id address]],
|
||||
user_profile_custom_fields_attributes: %i[id value invoicing_profile_id profile_custom_field_id]
|
||||
{
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name, { address_attributes: %i[id address] }],
|
||||
user_profile_custom_fields_attributes: %i[id value invoicing_profile_id profile_custom_field_id]
|
||||
}
|
||||
],
|
||||
statistic_profile_attributes: %i[id gender birthday])
|
||||
|
||||
@ -280,14 +236,16 @@ class API::MembersController < API::ApiController
|
||||
profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
user_avatar_attributes: %i[id attachment destroy]],
|
||||
{ user_avatar_attributes: %i[id attachment destroy] }],
|
||||
invoicing_profile_attributes: [
|
||||
:id, :organization,
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name, address_attributes: %i[id address]],
|
||||
user_profile_custom_fields_attributes: %i[id value invoicing_profile_id profile_custom_field_id]
|
||||
{
|
||||
address_attributes: %i[id address],
|
||||
organization_attributes: [:id, :name, { address_attributes: %i[id address] }],
|
||||
user_profile_custom_fields_attributes: %i[id value invoicing_profile_id profile_custom_field_id]
|
||||
}
|
||||
],
|
||||
statistic_profile_attributes: [:id, :gender, :birthday, training_ids: []])
|
||||
statistic_profile_attributes: [:id, :gender, :birthday, { training_ids: [] }])
|
||||
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { serialize } from 'object-to-formdata';
|
||||
import { User, UserIndexFilter } from '../models/user';
|
||||
import { User, UserIndexFilter, UserRole } from '../models/user';
|
||||
|
||||
export default class MemberAPI {
|
||||
static async list (filters: UserIndexFilter): Promise<Array<User>> {
|
||||
@ -45,6 +45,11 @@ export default class MemberAPI {
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async updateRole (user: User, role: UserRole, groupId?: number): Promise<User> {
|
||||
const res: AxiosResponse<User> = await apiClient.patch(`/api/members/${user.id}/update_role`, { role, group_id: groupId });
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async current (): Promise<User> {
|
||||
const res: AxiosResponse<User> = await apiClient.get('/api/members/current');
|
||||
return res?.data;
|
||||
|
@ -208,12 +208,12 @@ const PaymentSchedulesTable: React.FC<PaymentSchedulesTableProps> = ({ paymentSc
|
||||
</StripeElements>
|
||||
);
|
||||
case 'payzen':
|
||||
case null:
|
||||
return (
|
||||
<div>
|
||||
{renderPaymentSchedulesTable()}
|
||||
</div>
|
||||
);
|
||||
case null:
|
||||
default:
|
||||
console.error(`[PaymentSchedulesTable] Unimplemented gateway: ${gateway.value}`);
|
||||
return <div />;
|
||||
|
@ -117,10 +117,10 @@ export const PlansList: React.FC<PlansListProps> = ({ onError, onPlanSelection,
|
||||
};
|
||||
|
||||
/**
|
||||
* When called with a category ID, returns the name of the requested plan-category
|
||||
* When called with a category ID, returns the requested plan-category
|
||||
*/
|
||||
const categoryName = (categoryId: number): string => {
|
||||
return planCategories.find(c => c.id === categoryId)?.name;
|
||||
const findCategory = (categoryId: number): PlanCategory => {
|
||||
return planCategories.find(c => c.id === categoryId);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -193,10 +193,13 @@ export const PlansList: React.FC<PlansListProps> = ({ onError, onPlanSelection,
|
||||
return (
|
||||
<div className="list-of-categories">
|
||||
{Array.from(plans).sort(compareCategories).map(([categoryId, plansByCategory]) => {
|
||||
const category = findCategory(categoryId);
|
||||
const categoryPlans = plansByCategory.filter(filterPlan);
|
||||
const isShown = !!categoryId && categoryPlans.length > 0;
|
||||
return (
|
||||
<div key={categoryId} className={`plans-per-category ${categoryId ? 'with-category' : 'no-category'}`}>
|
||||
{!!categoryId && categoryPlans.length > 0 && <h3 className="category-title">{ categoryName(categoryId) }</h3>}
|
||||
{isShown && <h3 className="category-title">{ category.name }</h3>}
|
||||
{isShown && <p className="category-description" dangerouslySetInnerHTML={{ __html: category.description }} />}
|
||||
{renderPlans(categoryPlans)}
|
||||
</div>
|
||||
);
|
||||
@ -232,7 +235,7 @@ export const PlansList: React.FC<PlansListProps> = ({ onError, onPlanSelection,
|
||||
{plans && Array.from(filteredPlans()).map(([groupId, plansByGroup]) => {
|
||||
return (
|
||||
<div key={groupId} className="plans-per-group">
|
||||
<h2 className="group-title">{ groupName(groupId) }</h2>
|
||||
{plansByGroup.size > 0 && <h2 className="group-title">{ groupName(groupId) }</h2>}
|
||||
{plansByGroup && renderPlansByCategory(plansByGroup)}
|
||||
</div>
|
||||
);
|
||||
|
@ -0,0 +1,129 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { FabModal, ModalSize } from '../base/fab-modal';
|
||||
import { User, UserRole } from '../../models/user';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import MemberAPI from '../../api/member';
|
||||
import { FormSelect } from '../form/form-select';
|
||||
import { Group } from '../../models/group';
|
||||
import GroupAPI from '../../api/group';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface ChangeRoleModalProps {
|
||||
isOpen: boolean,
|
||||
toggleModal: () => void,
|
||||
user: User,
|
||||
onError: (message: string) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
}
|
||||
|
||||
interface RoleFormData {
|
||||
role: UserRole,
|
||||
groupId?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Option format, expected by react-select
|
||||
* @see https://github.com/JedWatson/react-select
|
||||
*/
|
||||
type selectRoleOption = { value: UserRole, label: string, isDisabled: boolean };
|
||||
type selectGroupOption = { value: number, label: string };
|
||||
|
||||
/**
|
||||
* This modal dialog allows to change the current role of the given user
|
||||
*/
|
||||
export const ChangeRoleModal: React.FC<ChangeRoleModalProps> = ({ isOpen, toggleModal, user, onSuccess, onError }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
const { control, handleSubmit } = useForm<RoleFormData>({ defaultValues: { groupId: user.group_id } });
|
||||
|
||||
const [groups, setGroups] = useState<Array<Group>>([]);
|
||||
const [selectedRole, setSelectedRole] = useState<UserRole>(user.role);
|
||||
|
||||
useEffect(() => {
|
||||
GroupAPI.index({ disabled: false, admins: false }).then(setGroups).catch(onError);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Handle the form submission: update the role on the API
|
||||
*/
|
||||
const onSubmit = (data: RoleFormData) => {
|
||||
MemberAPI.updateRole(user, data.role, data.groupId).then(res => {
|
||||
onSuccess(
|
||||
t(
|
||||
'app.admin.change_role_modal.role_changed',
|
||||
{ OLD: t(`app.admin.change_role_modal.${user.role}`), NEW: t(`app.admin.change_role_modal.${res.role}`) }
|
||||
)
|
||||
);
|
||||
toggleModal();
|
||||
}).catch(err => onError(t('app.admin.change_role_modal.error_while_changing_role') + err));
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user changes the selected role in the dropdown selection list
|
||||
*/
|
||||
const onRoleSelect = (data: UserRole) => {
|
||||
setSelectedRole(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the various available roles for the select input
|
||||
*/
|
||||
const buildRolesOptions = (): Array<selectRoleOption> => {
|
||||
return [
|
||||
{ value: 'admin' as UserRole, label: t('app.admin.change_role_modal.admin'), isDisabled: user.role === 'admin' },
|
||||
{ value: 'manager' as UserRole, label: t('app.admin.change_role_modal.manager'), isDisabled: user.role === 'manager' },
|
||||
{ value: 'member' as UserRole, label: t('app.admin.change_role_modal.member'), isDisabled: user.role === 'member' }
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the various available groups for the select input
|
||||
*/
|
||||
const buildGroupsOptions = (): Array<selectGroupOption> => {
|
||||
return groups.map(group => {
|
||||
return { value: group.id, label: group.name };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FabModal isOpen={isOpen}
|
||||
toggleModal={toggleModal}
|
||||
title={t('app.admin.change_role_modal.change_role')}
|
||||
width={ModalSize.medium}
|
||||
onConfirmSendFormId="user-role-form"
|
||||
confirmButton={t('app.admin.change_role_modal.confirm')}
|
||||
closeButton>
|
||||
<HtmlTranslate trKey={'app.admin.change_role_modal.warning_role_change'} />
|
||||
<form onSubmit={handleSubmit(onSubmit)} id="user-role-form">
|
||||
<FormSelect options={buildRolesOptions()}
|
||||
control={control}
|
||||
id="role"
|
||||
label={t('app.admin.change_role_modal.new_role')}
|
||||
rules={{ required: true }}
|
||||
onChange={onRoleSelect} />
|
||||
{selectedRole !== 'admin' &&
|
||||
<FormSelect options={buildGroupsOptions()}
|
||||
control={control}
|
||||
id="groupId"
|
||||
label={t('app.admin.change_role_modal.new_group')}
|
||||
tooltip={t('app.admin.change_role_modal.new_group_help')}
|
||||
rules={{ required: true }} />}
|
||||
</form>
|
||||
</FabModal>
|
||||
);
|
||||
};
|
||||
|
||||
const ChangeRoleModalWrapper: React.FC<ChangeRoleModalProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<ChangeRoleModal {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('changeRoleModal', react2angular(ChangeRoleModalWrapper, ['isOpen', 'toggleModal', 'user', 'onError', 'onSuccess']));
|
@ -59,7 +59,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
// regular expression to validate the input fields
|
||||
const phoneRegex = /^((00|\+)\d{2,3})?\d{4,14}$/;
|
||||
const phoneRegex = /^((00|\+)\d{2,3})?[\d -]{4,14}$/;
|
||||
const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/;
|
||||
|
||||
const { handleSubmit, register, control, formState, setValue, reset } = useForm<User>({ defaultValues: { ...user } });
|
||||
|
@ -724,6 +724,9 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
// modal dialog to take a new subscription
|
||||
$scope.isOpenSubscribeModal = false;
|
||||
|
||||
// modal dialog to change the user's role
|
||||
$scope.isOpenChangeRoleModal = false;
|
||||
|
||||
/**
|
||||
* Open a modal dialog asking for confirmation to change the role of the given user
|
||||
* @returns {*}
|
||||
@ -800,6 +803,17 @@ Application.Controllers.controller('EditMemberController', ['$scope', '$state',
|
||||
$scope.$apply();
|
||||
}, 50);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens/closes the modal dialog to change the user's role
|
||||
*/
|
||||
$scope.toggleChangeRoleModal = () => {
|
||||
setTimeout(() => {
|
||||
$scope.isOpenChangeRoleModal = !$scope.isOpenChangeRoleModal;
|
||||
$scope.$apply();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered if the subscription was successfully extended
|
||||
*/
|
||||
|
@ -483,6 +483,8 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
* @param reservation {Reservation}
|
||||
*/
|
||||
$scope.reservationCanModify = function (reservation) {
|
||||
if (AuthService.isAuthorized(['admin', 'manager'])) return true;
|
||||
|
||||
const slotStart = moment(reservation.slots_reservations_attributes[0].slot_attributes.start_at);
|
||||
const now = moment();
|
||||
|
||||
@ -498,6 +500,8 @@ Application.Controllers.controller('ShowEventController', ['$scope', '$state', '
|
||||
* @param reservation {Reservation}
|
||||
*/
|
||||
$scope.reservationCanCancel = function(reservation) {
|
||||
if (AuthService.isAuthorized(['admin', 'manager'])) return true;
|
||||
|
||||
const slotStart = moment(reservation.slots_reservations_attributes[0].slot_attributes.start_at);
|
||||
const now = moment();
|
||||
return $scope.enableBookingCancel && slotStart.diff(now, "hours") >= $scope.cancelBookingDelay;
|
||||
|
@ -42,6 +42,7 @@
|
||||
@import "modules/form/abstract-form-item";
|
||||
@import "modules/form/form-input";
|
||||
@import "modules/form/form-rich-text";
|
||||
@import "modules/form/form-select";
|
||||
@import "modules/form/form-switch";
|
||||
@import "modules/form/form-checklist";
|
||||
@import "modules/form/form-file-upload";
|
||||
|
@ -0,0 +1,9 @@
|
||||
.form-select {
|
||||
.rs__menu .rs__menu-list {
|
||||
.rs__option {
|
||||
&--is-disabled {
|
||||
color: var(--gray-hard-lightest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -21,9 +21,10 @@
|
||||
|
||||
<div class="col-md-3">
|
||||
<section class="heading-actions wrapper">
|
||||
<div class="btn btn-lg btn-block btn-default promote-member m-t-xs" ng-click="changeUserRole()" ng-show="isAuthorized('admin')">
|
||||
<div class="btn btn-lg btn-block btn-default promote-member m-t-xs" ng-click="toggleChangeRoleModal()" ng-show="isAuthorized('admin')">
|
||||
<img src="/rank-icon.svg" alt="role icon" /><span class="m-l" translate>{{ 'app.admin.members_edit.change_role' }}</span>
|
||||
</div>
|
||||
<change-role-modal is-open="isOpenChangeRoleModal" toggle-modal="toggleChangeRoleModal" user="user" on-success="onSuccess" onError="onError" />
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
48
app/services/export_service.rb
Normal file
48
app/services/export_service.rb
Normal file
@ -0,0 +1,48 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Provides helper methods for Exports resources and properties
|
||||
class ExportService
|
||||
class << self
|
||||
# Check if the last export of the provided type is still accurate or if it must be regenerated
|
||||
def last_export(type)
|
||||
case type
|
||||
when 'users/members'
|
||||
last_export_members
|
||||
when 'users/reservations'
|
||||
last_export_reservations
|
||||
when 'users/subscription'
|
||||
last_export_subscriptions
|
||||
else
|
||||
raise TypeError "unknown export type: #{type}"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def last_export_subscriptions
|
||||
Export.where(category: 'users', export_type: 'subscriptions')
|
||||
.where('created_at > ?', Subscription.maximum('updated_at'))
|
||||
.last
|
||||
end
|
||||
|
||||
def last_export_reservations
|
||||
Export.where(category: 'users', export_type: 'reservations')
|
||||
.where('created_at > ?', Reservation.maximum('updated_at'))
|
||||
.last
|
||||
end
|
||||
|
||||
def last_export_members
|
||||
last_update = [
|
||||
User.members.maximum('updated_at'),
|
||||
Profile.where(user_id: User.members).maximum('updated_at'),
|
||||
InvoicingProfile.where(user_id: User.members).maximum('updated_at'),
|
||||
StatisticProfile.where(user_id: User.members).maximum('updated_at'),
|
||||
Subscription.maximum('updated_at') || DateTime.current
|
||||
].max
|
||||
|
||||
Export.where(category: 'users', export_type: 'members')
|
||||
.where('created_at > ?', last_update)
|
||||
.last
|
||||
end
|
||||
end
|
||||
end
|
@ -9,19 +9,19 @@ class Members::MembersService
|
||||
end
|
||||
|
||||
def update(params)
|
||||
if params[:group_id] && @member.group_id != params[:group_id].to_i && !@member.subscribed_plan.nil?
|
||||
if subscriber_group_change?(params)
|
||||
# here a group change is requested but unprocessable, handle the exception
|
||||
@member.errors.add(:group_id, I18n.t('members.unable_to_change_the_group_while_a_subscription_is_running'))
|
||||
return false
|
||||
end
|
||||
|
||||
if params[:group_id] && params[:group_id].to_i != Group.find_by(slug: 'admins').id && @member.admin?
|
||||
if admin_group_change?(params)
|
||||
# an admin cannot change his group
|
||||
@member.errors.add(:group_id, I18n.t('members.admins_cant_change_group'))
|
||||
return false
|
||||
end
|
||||
|
||||
group_changed = params[:group_id] && @member.group_id != params[:group_id].to_i
|
||||
group_changed = user_group_change?(params)
|
||||
ex_group = @member.group
|
||||
|
||||
user_validation_required = Setting.get('user_validation_required')
|
||||
@ -80,7 +80,7 @@ class Members::MembersService
|
||||
end
|
||||
|
||||
def validate(is_valid)
|
||||
is_updated = member.update(validated_at: is_valid ? Time.now : nil)
|
||||
is_updated = member.update(validated_at: is_valid ? DateTime.current : nil)
|
||||
if is_updated
|
||||
if is_valid
|
||||
NotificationCenter.call type: 'notify_user_is_validated',
|
||||
@ -107,6 +107,44 @@ class Members::MembersService
|
||||
params
|
||||
end
|
||||
|
||||
def self.last_registered(limit)
|
||||
query = User.active.with_role(:member)
|
||||
.includes(:statistic_profile, profile: [:user_avatar])
|
||||
.where('is_allow_contact = true AND confirmed_at IS NOT NULL')
|
||||
.order('created_at desc')
|
||||
.limit(limit)
|
||||
|
||||
# remove unmerged profiles from list
|
||||
members = query.to_a
|
||||
members.delete_if(&:need_completion?)
|
||||
|
||||
[query, members]
|
||||
end
|
||||
|
||||
def update_role(new_role, new_group_id = Group.first.id)
|
||||
# do nothing if the role does not change
|
||||
return if new_role == @member.role
|
||||
|
||||
# update role
|
||||
ex_role = @member.role.to_sym
|
||||
@member.remove_role ex_role
|
||||
@member.add_role new_role
|
||||
|
||||
# if the new role is 'admin', then change the group to the admins group, otherwise to change to the provided group
|
||||
group_id = new_role == 'admin' ? Group.find_by(slug: 'admins').id : new_group_id
|
||||
@member.update(group_id: group_id)
|
||||
|
||||
# notify
|
||||
NotificationCenter.call type: 'notify_user_role_update',
|
||||
receiver: @member,
|
||||
attached_object: @member
|
||||
|
||||
NotificationCenter.call type: 'notify_admins_role_update',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: @member,
|
||||
meta_data: { ex_role: ex_role }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_user_profile_complete(previous_state)
|
||||
@ -133,4 +171,16 @@ class Members::MembersService
|
||||
params[:password]
|
||||
end
|
||||
end
|
||||
|
||||
def subscriber_group_change?(params)
|
||||
params[:group_id] && @member.group_id != params[:group_id].to_i && !@member.subscribed_plan.nil?
|
||||
end
|
||||
|
||||
def admin_group_change?(params)
|
||||
params[:group_id] && params[:group_id].to_i != Group.find_by(slug: 'admins').id && @member.admin?
|
||||
end
|
||||
|
||||
def user_group_change?(params)
|
||||
@member.group_id && params[:group_id] && @member.group_id != params[:group_id].to_i
|
||||
end
|
||||
end
|
||||
|
@ -12,5 +12,15 @@ class Statistics::BuilderService
|
||||
Statistics::Builders::MembersBuilderService.build(options)
|
||||
Statistics::Builders::ProjectsBuilderService.build(options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_options
|
||||
yesterday = 1.day.ago
|
||||
{
|
||||
start_date: yesterday.beginning_of_day,
|
||||
end_date: yesterday.end_of_day
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -24,7 +24,7 @@ class WalletService
|
||||
NotificationCenter.call type: 'notify_admin_user_wallet_is_credited',
|
||||
receiver: User.admins_and_managers,
|
||||
attached_object: transaction
|
||||
return transaction
|
||||
transaction
|
||||
end
|
||||
end
|
||||
raise ActiveRecord::Rollback
|
||||
@ -43,7 +43,7 @@ class WalletService
|
||||
amount: amount
|
||||
)
|
||||
|
||||
return transaction if transaction.save
|
||||
transaction if transaction.save
|
||||
end
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
@ -77,7 +77,6 @@ class WalletService
|
||||
# Compute the amount decreased from the user's wallet, if applicable
|
||||
# @param payment {Invoice|PaymentSchedule|Order}
|
||||
# @param user {User} the customer
|
||||
# @param coupon {Coupon|String} Coupon object or code
|
||||
##
|
||||
def self.wallet_amount_debit(payment, user)
|
||||
total = if payment.is_a? PaymentSchedule
|
||||
|
@ -49,10 +49,10 @@ json.array!(@availabilities) do |availability|
|
||||
json.borderColor space_slot_border_color(availability)
|
||||
when 'training'
|
||||
json.training_id availability.availability.trainings.first.id
|
||||
json.borderColor trainings_events_border_color(availability)
|
||||
json.borderColor trainings_events_border_color(availability.availability)
|
||||
when 'event'
|
||||
json.event_id availability.availability.event.id
|
||||
json.borderColor trainings_events_border_color(availability)
|
||||
json.borderColor trainings_events_border_color(availability.availability)
|
||||
else
|
||||
json.title 'Unknown slot'
|
||||
end
|
||||
|
@ -985,15 +985,20 @@ en:
|
||||
to_complete: "To complete"
|
||||
refuse_documents: "Refusing the documents"
|
||||
refuse_documents_info: "After verification, you may notify the member that the evidence submitted is not acceptable. You can specify the reasons for your refusal and indicate the actions to be taken. The member will be notified by e-mail."
|
||||
#edit a member
|
||||
members_edit:
|
||||
change_role_modal:
|
||||
change_role: "Change role"
|
||||
warning_role_change: "<p><strong>Warning:</strong> changing the role of a user is not a harmless operation. Is not currently possible to dismiss a user to a lower privileged role.</p><ul><li><strong>Members</strong> can only book reservations for themselves, paying by card or wallet.</li><li><strong>Managers</strong> can book reservations for themselves, paying by card or wallet, and for other members and managers, by collecting payments at the checkout.</li><li><strong>Administrators</strong> can only book reservations for members and managers, by collecting payments at the checkout. Moreover, they can change every settings of the application.</li></ul>"
|
||||
warning_role_change: "<p><strong>Warning:</strong> changing the role of a user is not a harmless operation.</p><ul><li><strong>Members</strong> can only book reservations for themselves, paying by card or wallet.</li><li><strong>Managers</strong> can book reservations for themselves, paying by card or wallet, and for other members and managers, by collecting payments at the checkout.</li><li><strong>Administrators</strong> can only book reservations for members and managers, by collecting payments at the checkout. Moreover, they can change every settings of the application.</li></ul>"
|
||||
new_role: "New role"
|
||||
admin: "Administrator"
|
||||
manager: "Manager"
|
||||
member: "Member"
|
||||
new_group: "New group"
|
||||
new_group_help: "Members and managers must be placed in a group."
|
||||
confirm: "Change role"
|
||||
role_changed: "Role successfully changed from {OLD} to {NEW}."
|
||||
error_while_changing_role: "An error occurred while changing the role. Please try again later."
|
||||
#edit a member
|
||||
members_edit:
|
||||
subscription: "Subscription"
|
||||
duration: "Duration:"
|
||||
expires_at: "Expires at:"
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
### User's manual
|
||||
The following guide describes what you can do and how to use Fab-manager.
|
||||
- [Français](fr/guide_utilisation_fab_manager_v5.0.pdf)
|
||||
- [Français](http://guide-fr.fab.mn/)
|
||||
|
||||
### System administrator
|
||||
The following guides are designed for the people that perform software maintenance.
|
||||
@ -40,7 +40,7 @@ The following guides are designed for the people that perform software maintenan
|
||||
- [ElasticSearch](elastic_upgrade.md)
|
||||
|
||||
### Translator's documentation
|
||||
If you intend to translate Fab-manager to a new, or an already supported language, you'll find here the information you need.
|
||||
If you intend to translate Fab-manager to a new, or an already supported language, you'll find here the information you need.
|
||||
- [Guide for translators](translation_readme.md)
|
||||
|
||||
### Developer's documentation
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fab-manager",
|
||||
"version": "5.4.17",
|
||||
"version": "5.4.21",
|
||||
"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",
|
||||
|
@ -1,927 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module Reservations; end
|
||||
|
||||
class Reservations::CreateTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user_without_subscription = User.members.without_subscription.first
|
||||
@user_with_subscription = User.members.with_subscription.second
|
||||
end
|
||||
|
||||
test 'user without subscription reserves a machine with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
subscriptions_count = Subscription.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_without_subscription_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal subscriptions_count, Subscription.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal machine.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
|
||||
test 'user without subscription reserves a machine with error' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
notifications_count = Notification.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_without_subscription_error') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method(error: :card_declined),
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 200, response.status, "API does not return the expected status. #{response.body}"
|
||||
assert_equal Mime[:json], response.content_type
|
||||
|
||||
# Check the error was handled
|
||||
assert_match /Your card was declined/, response.body
|
||||
|
||||
# Check the subscription wasn't taken
|
||||
assert_equal reservations_count, Reservation.count
|
||||
assert_equal invoice_count, Invoice.count
|
||||
assert_equal invoice_items_count, InvoiceItem.count
|
||||
assert_equal notifications_count, Notification.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
end
|
||||
|
||||
test 'user without subscription reserves a training with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
training = Training.first
|
||||
availability = training.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_training_without_subscription_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal invoice_item.amount, training.amount_by_group(@user_without_subscription.group_id).amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
|
||||
test 'user with subscription reserves a machine with success' do
|
||||
login_as(@user_with_subscription, scope: :user)
|
||||
|
||||
plan = @user_with_subscription.subscribed_plan
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_with_subscription_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
},
|
||||
{
|
||||
slot_id: availability.slots.last.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||
assert_equal users_credit_count + 1, UsersCredit.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_with_subscription.subscriptions.count
|
||||
assert_not_nil @user_with_subscription.subscribed_plan
|
||||
assert_equal plan.id, @user_with_subscription.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 2, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_items = InvoiceItem.last(2)
|
||||
machine_price = machine.prices.find_by(group_id: @user_with_subscription.group_id, plan_id: plan.id).amount
|
||||
|
||||
assert(invoice_items.any? { |inv| inv.amount.zero? })
|
||||
assert(invoice_items.any? { |inv| inv.amount == machine_price })
|
||||
assert(invoice_items.all?(&:check_footprint))
|
||||
|
||||
# users_credits assertions
|
||||
users_credit = UsersCredit.last
|
||||
|
||||
assert_equal @user_with_subscription, users_credit.user
|
||||
assert_equal [reservation.slots.count, plan.machine_credits.find_by(creditable_id: machine.id).hours].min, users_credit.hours_used
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
|
||||
test 'user with subscription reserves the FIRST training with success' do
|
||||
login_as(@user_with_subscription, scope: :user)
|
||||
plan = @user_with_subscription.subscribed_plan
|
||||
plan.update!(is_rolling: true)
|
||||
|
||||
training = Training.joins(credits: :plan).where(credits: { plan: plan }).first
|
||||
availability = training.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_training_with_subscription_success') do
|
||||
post '/api/local_payment/confirm_payment',
|
||||
params: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_with_subscription.subscriptions.count
|
||||
assert_not_nil @user_with_subscription.subscribed_plan
|
||||
assert_equal plan.id, @user_with_subscription.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal 0, invoice_item.amount # amount is 0 because this training is a credited training with that plan
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# check that user subscription were extended
|
||||
assert_equal reservation.slots.first.start_at + plan.duration, @user_with_subscription.subscription.expired_at
|
||||
end
|
||||
|
||||
test 'user reserves a machine and pay by wallet with success' do
|
||||
@vlonchamp = User.find_by(username: 'vlonchamp')
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_and_pay_wallet_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
customer_id: @vlonchamp.id,
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
@vlonchamp.wallet.reload
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @vlonchamp.subscriptions.count
|
||||
assert_nil @vlonchamp.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal machine.prices.find_by(group_id: @vlonchamp.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# wallet
|
||||
assert_equal 0, @vlonchamp.wallet.amount
|
||||
assert_equal 2, @vlonchamp.wallet.wallet_transactions.count
|
||||
transaction = @vlonchamp.wallet.wallet_transactions.last
|
||||
assert_equal 'debit', transaction.transaction_type
|
||||
assert_equal 10, transaction.amount
|
||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||
end
|
||||
|
||||
test 'user reserves a training and a subscription by wallet with success' do
|
||||
@vlonchamp = User.find_by(username: 'vlonchamp')
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
training = Training.first
|
||||
availability = training.availabilities.first
|
||||
plan = Plan.find_by(group_id: @vlonchamp.group.id, type: 'Plan', base_name: 'Mensuel tarif réduit')
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_training_and_plan_by_pay_wallet_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
@vlonchamp.wallet.reload
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @vlonchamp.subscriptions.count
|
||||
assert_not_nil @vlonchamp.subscribed_plan
|
||||
assert_equal plan.id, @vlonchamp.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 2, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert_equal invoice.total, 2000
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# wallet
|
||||
assert_equal 0, @vlonchamp.wallet.amount
|
||||
assert_equal 2, @vlonchamp.wallet.wallet_transactions.count
|
||||
transaction = @vlonchamp.wallet.wallet_transactions.last
|
||||
assert_equal 'debit', transaction.transaction_type
|
||||
assert_equal 10, transaction.amount
|
||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||
end
|
||||
|
||||
test 'user reserves a machine and a subscription using a coupon with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
plan = Plan.find(4)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
subscriptions_count = Subscription.count
|
||||
users_credit_count = UsersCredit.count
|
||||
|
||||
VCR.use_cassette('reservations_machine_and_plan_using_coupon_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
],
|
||||
coupon_code: 'SUNNYFABLAB'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal subscriptions_count + 1, Subscription.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_without_subscription.subscriptions.count
|
||||
assert_not_nil @user_without_subscription.subscribed_plan
|
||||
assert_equal plan.id, @user_without_subscription.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 2, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# invoice_items assertions
|
||||
## reservation
|
||||
reservation_item = invoice.invoice_items.find_by(object: reservation)
|
||||
|
||||
assert_not_nil reservation_item
|
||||
assert_equal reservation_item.amount, machine.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: plan.id).amount
|
||||
assert reservation_item.check_footprint
|
||||
## subscription
|
||||
subscription_item = invoice.invoice_items.find_by(object_type: Subscription.name)
|
||||
|
||||
assert_not_nil subscription_item
|
||||
|
||||
subscription = subscription_item.object
|
||||
|
||||
assert_equal subscription_item.amount, plan.amount
|
||||
assert_equal subscription.plan_id, plan.id
|
||||
assert subscription_item.check_footprint
|
||||
|
||||
VCR.use_cassette('reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe') do
|
||||
stp_intent = invoice.payment_gateway_object.gateway_object.retrieve
|
||||
assert_equal stp_intent.amount, invoice.total
|
||||
end
|
||||
|
||||
# notifications
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
assert_not_empty Notification.where(attached_object: subscription)
|
||||
end
|
||||
|
||||
test 'user reserves a training with an expired coupon with error' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
training = Training.find(1)
|
||||
availability = training.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
notifications_count = Notification.count
|
||||
|
||||
VCR.use_cassette('reservations_training_with_expired_coupon_error') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
customer_id: @user_without_subscription.id,
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
card_token: stripe_payment_method,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
coupon_code: 'XMAS10'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 422, response.status
|
||||
assert_equal reservations_count, Reservation.count
|
||||
assert_equal invoice_count, Invoice.count
|
||||
assert_equal invoice_items_count, InvoiceItem.count
|
||||
assert_equal notifications_count, Notification.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
end
|
||||
|
||||
|
||||
test 'user reserves a training and a subscription with payment schedule' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
subscriptions_count = Subscription.count
|
||||
users_credit_count = UsersCredit.count
|
||||
payment_schedule_count = PaymentSchedule.count
|
||||
payment_schedule_items_count = PaymentScheduleItem.count
|
||||
|
||||
training = Training.find(1)
|
||||
availability = training.availabilities.first
|
||||
plan = Plan.find_by(group_id: @user_without_subscription.group.id, type: 'Plan', base_name: 'Abonnement mensualisable')
|
||||
|
||||
VCR.use_cassette('reservations_training_subscription_with_payment_schedule') do
|
||||
post '/api/stripe/setup_subscription',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
],
|
||||
payment_schedule: true,
|
||||
payment_method: 'cart'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 201, response.status, response.body
|
||||
assert_equal Mime[:json], response.content_type
|
||||
|
||||
# Check the response
|
||||
sub = json_response(response.body)
|
||||
assert_not_nil sub[:id]
|
||||
end
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 201, response.status, response.body
|
||||
assert_equal Mime[:json], response.content_type
|
||||
assert_equal reservations_count + 1, Reservation.count, 'missing the reservation'
|
||||
assert_equal invoice_count, Invoice.count, "an invoice was generated but it shouldn't"
|
||||
assert_equal invoice_items_count, InvoiceItem.count, "some invoice items were generated but they shouldn't"
|
||||
assert_equal users_credit_count, UsersCredit.count, "user's credits count has changed but it shouldn't"
|
||||
assert_equal subscriptions_count + 1, Subscription.count, 'missing the subscription'
|
||||
assert_equal payment_schedule_count + 1, PaymentSchedule.count, 'missing the payment schedule'
|
||||
assert_equal payment_schedule_items_count + 12, PaymentScheduleItem.count, 'missing some payment schedule items'
|
||||
|
||||
# get the objects
|
||||
reservation = Reservation.last
|
||||
payment_schedule = PaymentSchedule.last
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_without_subscription.subscriptions.count
|
||||
assert_not_nil @user_without_subscription.subscribed_plan, "user's subscribed plan was not found"
|
||||
assert_not_nil @user_without_subscription.subscription, "user's subscription was not found"
|
||||
assert_equal plan.id, @user_without_subscription.subscribed_plan.id, "user's plan does not match"
|
||||
|
||||
# reservation assertions
|
||||
assert reservation.original_payment_schedule
|
||||
assert_equal payment_schedule.main_object.object, reservation
|
||||
|
||||
# Check the answer
|
||||
result = json_response(response.body)
|
||||
assert_equal payment_schedule.id, result[:id], 'payment schedule id does not match'
|
||||
subscription = payment_schedule.payment_schedule_objects.find { |pso| pso.object_type == Subscription.name }.object
|
||||
assert_equal plan.id, subscription.plan_id, 'subscribed plan does not match'
|
||||
end
|
||||
|
||||
test 'user reserves a machine and renew a subscription with payment schedule and coupon and wallet' do
|
||||
user = User.find_by(username: 'lseguin')
|
||||
login_as(user, scope: :user)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
subscriptions_count = Subscription.count
|
||||
user_subscriptions_count = user.subscriptions.count
|
||||
payment_schedule_count = PaymentSchedule.count
|
||||
payment_schedule_items_count = PaymentScheduleItem.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
machine = Machine.find(1)
|
||||
availability = machine.availabilities.last
|
||||
plan = Plan.find_by(group_id: user.group.id, type: 'Plan', base_name: 'Abonnement mensualisable')
|
||||
|
||||
VCR.use_cassette('reservations_machine_subscription_with_payment_schedule_coupon_wallet') do
|
||||
post '/api/stripe/setup_subscription',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
],
|
||||
payment_schedule: true,
|
||||
payment_method: 'card',
|
||||
coupon_code: 'GIME3EUR'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 201, response.status, response.body
|
||||
assert_equal Mime[:json], response.content_type
|
||||
|
||||
# Check the response
|
||||
res = json_response(response.body)
|
||||
assert_not_nil res[:id]
|
||||
end
|
||||
|
||||
assert_equal reservations_count + 1, Reservation.count, 'missing the reservation'
|
||||
assert_equal invoice_count, Invoice.count, "an invoice was generated but it shouldn't"
|
||||
assert_equal invoice_items_count, InvoiceItem.count, "some invoice items were generated but they shouldn't"
|
||||
assert_equal 0, UsersCredit.count, "user's credits were not reset"
|
||||
assert_equal subscriptions_count + 1, Subscription.count, 'missing the subscription'
|
||||
assert_equal payment_schedule_count + 1, PaymentSchedule.count, 'missing the payment schedule'
|
||||
assert_equal payment_schedule_items_count + 12, PaymentScheduleItem.count, 'missing some payment schedule items'
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count, 'missing the wallet transaction'
|
||||
|
||||
# get the objects
|
||||
reservation = Reservation.last
|
||||
subscription = Subscription.last
|
||||
payment_schedule = PaymentSchedule.last
|
||||
|
||||
# subscription assertions
|
||||
assert_equal user_subscriptions_count + 1, user.subscriptions.count
|
||||
assert_equal user, subscription.user
|
||||
assert_not_nil user.subscribed_plan, "user's subscribed plan was not found"
|
||||
assert_not_nil user.subscription, "user's subscription was not found"
|
||||
assert_equal plan.id, user.subscribed_plan.id, "user's plan does not match"
|
||||
|
||||
# reservation assertions
|
||||
assert reservation.original_payment_schedule
|
||||
assert_equal payment_schedule.main_object.object, reservation
|
||||
|
||||
# payment schedule assertions
|
||||
assert_not_nil payment_schedule.reference
|
||||
assert_equal 'card', payment_schedule.payment_method
|
||||
assert_equal 2, payment_schedule.payment_gateway_objects.count
|
||||
assert_not_nil payment_schedule.gateway_payment_mean
|
||||
assert_not_nil payment_schedule.wallet_transaction
|
||||
assert_equal payment_schedule.ordered_items.first.amount, payment_schedule.wallet_amount
|
||||
assert_equal Coupon.find_by(code: 'GIME3EUR').id, payment_schedule.coupon_id
|
||||
assert_equal 'test', payment_schedule.environment
|
||||
assert payment_schedule.check_footprint
|
||||
assert_equal user.invoicing_profile.id, payment_schedule.invoicing_profile_id
|
||||
assert_equal payment_schedule.invoicing_profile_id, payment_schedule.operator_profile_id
|
||||
|
||||
# Check the answer
|
||||
result = json_response(response.body)
|
||||
assert_equal payment_schedule.id, result[:id], 'payment schedule id does not match'
|
||||
subscription = payment_schedule.payment_schedule_objects.find { |pso| pso.object_type == Subscription.name }.object
|
||||
assert_equal plan.id, subscription.plan_id, 'subscribed plan does not match'
|
||||
end
|
||||
|
||||
test 'user reserves a space with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
space = Space.first
|
||||
availability = space.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
subscriptions_count = Subscription.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_space_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: space.id,
|
||||
reservable_type: space.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal subscriptions_count, Subscription.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal space.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
refute invoice.payment_gateway_object.blank?
|
||||
refute invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
end
|
277
test/integration/reservations/pay_with_wallet_test.rb
Normal file
277
test/integration/reservations/pay_with_wallet_test.rb
Normal file
@ -0,0 +1,277 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module Reservations; end
|
||||
|
||||
class Reservations::PayWithWalletTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@vlonchamp = User.find_by(username: 'vlonchamp')
|
||||
end
|
||||
|
||||
test 'user reserves a machine and pay by wallet with success' do
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_and_pay_wallet_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
customer_id: @vlonchamp.id,
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
@vlonchamp.wallet.reload
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @vlonchamp.subscriptions.count
|
||||
assert_nil @vlonchamp.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal machine.prices.find_by(group_id: @vlonchamp.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# wallet
|
||||
assert_equal 0, @vlonchamp.wallet.amount
|
||||
assert_equal 2, @vlonchamp.wallet.wallet_transactions.count
|
||||
transaction = @vlonchamp.wallet.wallet_transactions.last
|
||||
assert_equal 'debit', transaction.transaction_type
|
||||
assert_equal 10, transaction.amount
|
||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||
end
|
||||
|
||||
test 'user reserves a training and a subscription by wallet with success' do
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
training = Training.first
|
||||
availability = training.availabilities.first
|
||||
plan = Plan.find_by(group_id: @vlonchamp.group.id, type: 'Plan', base_name: 'Mensuel tarif réduit')
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_training_and_plan_by_pay_wallet_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
@vlonchamp.wallet.reload
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @vlonchamp.subscriptions.count
|
||||
assert_not_nil @vlonchamp.subscribed_plan
|
||||
assert_equal plan.id, @vlonchamp.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 2, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert_equal invoice.total, 2000
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# wallet
|
||||
assert_equal 0, @vlonchamp.wallet.amount
|
||||
assert_equal 2, @vlonchamp.wallet.wallet_transactions.count
|
||||
transaction = @vlonchamp.wallet.wallet_transactions.last
|
||||
assert_equal 'debit', transaction.transaction_type
|
||||
assert_equal 10, transaction.amount
|
||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||
end
|
||||
|
||||
test 'user reserves a machine and renew a subscription with payment schedule and coupon and wallet' do
|
||||
user = User.find_by(username: 'lseguin')
|
||||
login_as(user, scope: :user)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
subscriptions_count = Subscription.count
|
||||
user_subscriptions_count = user.subscriptions.count
|
||||
payment_schedule_count = PaymentSchedule.count
|
||||
payment_schedule_items_count = PaymentScheduleItem.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
machine = Machine.find(1)
|
||||
availability = machine.availabilities.last
|
||||
plan = Plan.find_by(group_id: user.group.id, type: 'Plan', base_name: 'Abonnement mensualisable')
|
||||
|
||||
VCR.use_cassette('reservations_machine_subscription_with_payment_schedule_coupon_wallet') do
|
||||
post '/api/stripe/setup_subscription',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
],
|
||||
payment_schedule: true,
|
||||
payment_method: 'card',
|
||||
coupon_code: 'GIME3EUR'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 201, response.status, response.body
|
||||
assert_equal Mime[:json], response.content_type
|
||||
|
||||
# Check the response
|
||||
res = json_response(response.body)
|
||||
assert_not_nil res[:id]
|
||||
end
|
||||
|
||||
assert_equal reservations_count + 1, Reservation.count, 'missing the reservation'
|
||||
assert_equal invoice_count, Invoice.count, "an invoice was generated but it shouldn't"
|
||||
assert_equal invoice_items_count, InvoiceItem.count, "some invoice items were generated but they shouldn't"
|
||||
assert_equal 0, UsersCredit.count, "user's credits were not reset"
|
||||
assert_equal subscriptions_count + 1, Subscription.count, 'missing the subscription'
|
||||
assert_equal payment_schedule_count + 1, PaymentSchedule.count, 'missing the payment schedule'
|
||||
assert_equal payment_schedule_items_count + 12, PaymentScheduleItem.count, 'missing some payment schedule items'
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count, 'missing the wallet transaction'
|
||||
|
||||
# get the objects
|
||||
reservation = Reservation.last
|
||||
subscription = Subscription.last
|
||||
payment_schedule = PaymentSchedule.last
|
||||
|
||||
# subscription assertions
|
||||
assert_equal user_subscriptions_count + 1, user.subscriptions.count
|
||||
assert_equal user, subscription.user
|
||||
assert_not_nil user.subscribed_plan, "user's subscribed plan was not found"
|
||||
assert_not_nil user.subscription, "user's subscription was not found"
|
||||
assert_equal plan.id, user.subscribed_plan.id, "user's plan does not match"
|
||||
|
||||
# reservation assertions
|
||||
assert reservation.original_payment_schedule
|
||||
assert_equal payment_schedule.main_object.object, reservation
|
||||
|
||||
# payment schedule assertions
|
||||
assert_not_nil payment_schedule.reference
|
||||
assert_equal 'card', payment_schedule.payment_method
|
||||
assert_equal 2, payment_schedule.payment_gateway_objects.count
|
||||
assert_not_nil payment_schedule.gateway_payment_mean
|
||||
assert_not_nil payment_schedule.wallet_transaction
|
||||
assert_equal CouponService.new.apply(payment_schedule.ordered_items.first.amount, payment_schedule.coupon, user.id),
|
||||
payment_schedule.wallet_amount
|
||||
assert_equal Coupon.find_by(code: 'GIME3EUR').id, payment_schedule.coupon_id
|
||||
assert_equal 'test', payment_schedule.environment
|
||||
assert payment_schedule.check_footprint
|
||||
assert_equal user.invoicing_profile.id, payment_schedule.invoicing_profile_id
|
||||
assert_equal payment_schedule.invoicing_profile_id, payment_schedule.operator_profile_id
|
||||
|
||||
# Check the answer
|
||||
result = json_response(response.body)
|
||||
assert_equal payment_schedule.id, result[:id], 'payment schedule id does not match'
|
||||
subscription = payment_schedule.payment_schedule_objects.find { |pso| pso.object_type == Subscription.name }.object
|
||||
assert_equal plan.id, subscription.plan_id, 'subscribed plan does not match'
|
||||
end
|
||||
end
|
232
test/integration/reservations/reserve_machine_test.rb
Normal file
232
test/integration/reservations/reserve_machine_test.rb
Normal file
@ -0,0 +1,232 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module Reservations; end
|
||||
|
||||
class Reservations::ReserveMachineTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user_without_subscription = User.members.without_subscription.first
|
||||
end
|
||||
|
||||
test 'user without subscription reserves a machine with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
subscriptions_count = Subscription.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_without_subscription_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal subscriptions_count, Subscription.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal machine.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
|
||||
test 'user without subscription reserves a machine with error' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
notifications_count = Notification.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_without_subscription_error') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method(error: :card_declined),
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 200, response.status, "API does not return the expected status. #{response.body}"
|
||||
assert_equal Mime[:json], response.content_type
|
||||
|
||||
# Check the error was handled
|
||||
assert_match(/Your card was declined/, response.body)
|
||||
|
||||
# Check the subscription wasn't taken
|
||||
assert_equal reservations_count, Reservation.count
|
||||
assert_equal invoice_count, Invoice.count
|
||||
assert_equal invoice_items_count, InvoiceItem.count
|
||||
assert_equal notifications_count, Notification.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
end
|
||||
|
||||
test 'user reserves a machine and a subscription using a coupon with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
plan = Plan.find(4)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
subscriptions_count = Subscription.count
|
||||
users_credit_count = UsersCredit.count
|
||||
|
||||
VCR.use_cassette('reservations_machine_and_plan_using_coupon_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
],
|
||||
coupon_code: 'SUNNYFABLAB'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal subscriptions_count + 1, Subscription.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_without_subscription.subscriptions.count
|
||||
assert_not_nil @user_without_subscription.subscribed_plan
|
||||
assert_equal plan.id, @user_without_subscription.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 2, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# invoice_items assertions
|
||||
## reservation
|
||||
reservation_item = invoice.invoice_items.find_by(object: reservation)
|
||||
|
||||
assert_not_nil reservation_item
|
||||
assert_equal reservation_item.amount, machine.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: plan.id).amount
|
||||
assert reservation_item.check_footprint
|
||||
## subscription
|
||||
subscription_item = invoice.invoice_items.find_by(object_type: Subscription.name)
|
||||
|
||||
assert_not_nil subscription_item
|
||||
|
||||
subscription = subscription_item.object
|
||||
|
||||
assert_equal subscription_item.amount, plan.amount
|
||||
assert_equal subscription.plan_id, plan.id
|
||||
assert subscription_item.check_footprint
|
||||
|
||||
VCR.use_cassette('reservations_machine_and_plan_using_coupon_retrieve_invoice_from_stripe') do
|
||||
stp_intent = invoice.payment_gateway_object.gateway_object.retrieve
|
||||
assert_equal stp_intent.amount, invoice.total
|
||||
end
|
||||
|
||||
# notifications
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
assert_not_empty Notification.where(attached_object: subscription)
|
||||
end
|
||||
end
|
83
test/integration/reservations/reserve_space_test.rb
Normal file
83
test/integration/reservations/reserve_space_test.rb
Normal file
@ -0,0 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module Reservations; end
|
||||
|
||||
class Reservations::ReserveSpaceTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user_without_subscription = User.members.without_subscription.first
|
||||
end
|
||||
|
||||
test 'user reserves a space with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
space = Space.first
|
||||
availability = space.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
subscriptions_count = Subscription.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_space_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: space.id,
|
||||
reservable_type: space.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal subscriptions_count, Subscription.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal space.prices.find_by(group_id: @user_without_subscription.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
end
|
211
test/integration/reservations/reserve_training_test.rb
Normal file
211
test/integration/reservations/reserve_training_test.rb
Normal file
@ -0,0 +1,211 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module Reservations; end
|
||||
|
||||
class Reservations::ReserveTrainingTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user_without_subscription = User.members.without_subscription.first
|
||||
end
|
||||
|
||||
test 'user without subscription reserves a training with success' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
training = Training.first
|
||||
availability = training.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_training_without_subscription_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal invoice_item.amount, training.amount_by_group(@user_without_subscription.group_id).amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
|
||||
test 'user reserves a training with an expired coupon with error' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
training = Training.find(1)
|
||||
availability = training.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
notifications_count = Notification.count
|
||||
|
||||
VCR.use_cassette('reservations_training_with_expired_coupon_error') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
customer_id: @user_without_subscription.id,
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
card_token: stripe_payment_method,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
coupon_code: 'XMAS10'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 422, response.status
|
||||
assert_equal reservations_count, Reservation.count
|
||||
assert_equal invoice_count, Invoice.count
|
||||
assert_equal invoice_items_count, InvoiceItem.count
|
||||
assert_equal notifications_count, Notification.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @user_without_subscription.subscriptions.count
|
||||
assert_nil @user_without_subscription.subscribed_plan
|
||||
end
|
||||
|
||||
test 'user reserves a training and a subscription with payment schedule' do
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
subscriptions_count = Subscription.count
|
||||
users_credit_count = UsersCredit.count
|
||||
payment_schedule_count = PaymentSchedule.count
|
||||
payment_schedule_items_count = PaymentScheduleItem.count
|
||||
|
||||
training = Training.find(1)
|
||||
availability = training.availabilities.first
|
||||
plan = Plan.find_by(group_id: @user_without_subscription.group.id, type: 'Plan', base_name: 'Abonnement mensualisable')
|
||||
|
||||
VCR.use_cassette('reservations_training_subscription_with_payment_schedule') do
|
||||
post '/api/stripe/setup_subscription',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
subscription: {
|
||||
plan_id: plan.id
|
||||
}
|
||||
}
|
||||
],
|
||||
payment_schedule: true,
|
||||
payment_method: 'cart'
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 201, response.status, response.body
|
||||
assert_equal Mime[:json], response.content_type
|
||||
|
||||
# Check the response
|
||||
sub = json_response(response.body)
|
||||
assert_not_nil sub[:id]
|
||||
end
|
||||
|
||||
# Check response format & status
|
||||
assert_equal 201, response.status, response.body
|
||||
assert_equal Mime[:json], response.content_type
|
||||
assert_equal reservations_count + 1, Reservation.count, 'missing the reservation'
|
||||
assert_equal invoice_count, Invoice.count, "an invoice was generated but it shouldn't"
|
||||
assert_equal invoice_items_count, InvoiceItem.count, "some invoice items were generated but they shouldn't"
|
||||
assert_equal users_credit_count, UsersCredit.count, "user's credits count has changed but it shouldn't"
|
||||
assert_equal subscriptions_count + 1, Subscription.count, 'missing the subscription'
|
||||
assert_equal payment_schedule_count + 1, PaymentSchedule.count, 'missing the payment schedule'
|
||||
assert_equal payment_schedule_items_count + 12, PaymentScheduleItem.count, 'missing some payment schedule items'
|
||||
|
||||
# get the objects
|
||||
reservation = Reservation.last
|
||||
payment_schedule = PaymentSchedule.last
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_without_subscription.subscriptions.count
|
||||
assert_not_nil @user_without_subscription.subscribed_plan, "user's subscribed plan was not found"
|
||||
assert_not_nil @user_without_subscription.subscription, "user's subscription was not found"
|
||||
assert_equal plan.id, @user_without_subscription.subscribed_plan.id, "user's plan does not match"
|
||||
|
||||
# reservation assertions
|
||||
assert reservation.original_payment_schedule
|
||||
assert_equal payment_schedule.main_object.object, reservation
|
||||
|
||||
# Check the answer
|
||||
result = json_response(response.body)
|
||||
assert_equal payment_schedule.id, result[:id], 'payment schedule id does not match'
|
||||
subscription = payment_schedule.payment_schedule_objects.find { |pso| pso.object_type == Subscription.name }.object
|
||||
assert_equal plan.id, subscription.plan_id, 'subscribed plan does not match'
|
||||
end
|
||||
end
|
249
test/integration/reservations/with_subscription_test.rb
Normal file
249
test/integration/reservations/with_subscription_test.rb
Normal file
@ -0,0 +1,249 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'test_helper'
|
||||
|
||||
module Reservations; end
|
||||
|
||||
class Reservations::WithSubscriptionTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
@user_with_subscription = User.members.with_subscription.second
|
||||
end
|
||||
|
||||
test 'user with subscription reserves a machine with success' do
|
||||
login_as(@user_with_subscription, scope: :user)
|
||||
|
||||
plan = @user_with_subscription.subscribed_plan
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_with_subscription_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
},
|
||||
{
|
||||
slot_id: availability.slots.last.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 2, InvoiceItem.count
|
||||
assert_equal users_credit_count + 1, UsersCredit.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_with_subscription.subscriptions.count
|
||||
assert_not_nil @user_with_subscription.subscribed_plan
|
||||
assert_equal plan.id, @user_with_subscription.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 2, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_items = InvoiceItem.last(2)
|
||||
machine_price = machine.prices.find_by(group_id: @user_with_subscription.group_id, plan_id: plan.id).amount
|
||||
|
||||
assert(invoice_items.any? { |inv| inv.amount.zero? })
|
||||
assert(invoice_items.any? { |inv| inv.amount == machine_price })
|
||||
assert(invoice_items.all?(&:check_footprint))
|
||||
|
||||
# users_credits assertions
|
||||
users_credit = UsersCredit.last
|
||||
|
||||
assert_equal @user_with_subscription, users_credit.user
|
||||
assert_equal [reservation.slots.count, plan.machine_credits.find_by(creditable_id: machine.id).hours].min, users_credit.hours_used
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
end
|
||||
|
||||
test 'user with subscription reserves the FIRST training with success' do
|
||||
login_as(@user_with_subscription, scope: :user)
|
||||
plan = @user_with_subscription.subscribed_plan
|
||||
plan.update!(is_rolling: true)
|
||||
|
||||
training = Training.joins(credits: :plan).where(credits: { plan: plan }).first
|
||||
availability = training.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_training_with_subscription_success') do
|
||||
post '/api/local_payment/confirm_payment',
|
||||
params: {
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: training.id,
|
||||
reservable_type: training.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 1, @user_with_subscription.subscriptions.count
|
||||
assert_not_nil @user_with_subscription.subscribed_plan
|
||||
assert_equal plan.id, @user_with_subscription.subscribed_plan.id
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal 0, invoice_item.amount # amount is 0 because this training is a credited training with that plan
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# check that user subscription were extended
|
||||
assert_equal reservation.slots.first.start_at + plan.duration, @user_with_subscription.subscription.expired_at
|
||||
end
|
||||
|
||||
test 'user reserves a machine and pay by wallet with success' do
|
||||
@vlonchamp = User.find_by(username: 'vlonchamp')
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
invoice_items_count = InvoiceItem.count
|
||||
users_credit_count = UsersCredit.count
|
||||
wallet_transactions_count = WalletTransaction.count
|
||||
|
||||
VCR.use_cassette('reservations_create_for_machine_and_pay_wallet_success') do
|
||||
post '/api/stripe/confirm_payment',
|
||||
params: {
|
||||
payment_method_id: stripe_payment_method,
|
||||
cart_items: {
|
||||
customer_id: @vlonchamp.id,
|
||||
items: [
|
||||
{
|
||||
reservation: {
|
||||
reservable_id: machine.id,
|
||||
reservable_type: machine.class.name,
|
||||
slots_reservations_attributes: [
|
||||
{
|
||||
slot_id: availability.slots.first.id
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json, headers: default_headers
|
||||
end
|
||||
|
||||
@vlonchamp.wallet.reload
|
||||
|
||||
# general assertions
|
||||
assert_equal 201, response.status
|
||||
assert_equal reservations_count + 1, Reservation.count
|
||||
assert_equal invoice_count + 1, Invoice.count
|
||||
assert_equal invoice_items_count + 1, InvoiceItem.count
|
||||
assert_equal users_credit_count, UsersCredit.count
|
||||
assert_equal wallet_transactions_count + 1, WalletTransaction.count
|
||||
|
||||
# subscription assertions
|
||||
assert_equal 0, @vlonchamp.subscriptions.count
|
||||
assert_nil @vlonchamp.subscribed_plan
|
||||
|
||||
# reservation assertions
|
||||
reservation = Reservation.last
|
||||
|
||||
assert reservation.original_invoice
|
||||
assert_equal 1, reservation.original_invoice.invoice_items.count
|
||||
|
||||
# invoice_items assertions
|
||||
invoice_item = InvoiceItem.last
|
||||
|
||||
assert_equal machine.prices.find_by(group_id: @vlonchamp.group_id, plan_id: nil).amount, invoice_item.amount
|
||||
assert invoice_item.check_footprint
|
||||
|
||||
# invoice assertions
|
||||
item = InvoiceItem.find_by(object: reservation)
|
||||
invoice = item.invoice
|
||||
assert_invoice_pdf invoice
|
||||
assert_not_nil invoice.debug_footprint
|
||||
|
||||
assert_not invoice.payment_gateway_object.blank?
|
||||
assert_not invoice.total.blank?
|
||||
assert invoice.check_footprint
|
||||
|
||||
# notification
|
||||
assert_not_empty Notification.where(attached_object: reservation)
|
||||
|
||||
# wallet
|
||||
assert_equal 0, @vlonchamp.wallet.amount
|
||||
assert_equal 2, @vlonchamp.wallet.wallet_transactions.count
|
||||
transaction = @vlonchamp.wallet.wallet_transactions.last
|
||||
assert_equal 'debit', transaction.transaction_type
|
||||
assert_equal 10, transaction.amount
|
||||
assert_equal invoice.wallet_amount / 100.0, transaction.amount
|
||||
end
|
||||
end
|
@ -9,6 +9,10 @@ class StatisticServiceTest < ActionDispatch::IntegrationTest
|
||||
login_as(@admin, scope: :user)
|
||||
end
|
||||
|
||||
test 'build default stats' do
|
||||
::Statistics::BuilderService.generate_statistic
|
||||
end
|
||||
|
||||
test 'build stats' do
|
||||
# Create a reservation to generate an invoice
|
||||
machine = Machine.find(1)
|
||||
|
Loading…
Reference in New Issue
Block a user