mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-19 13:54:25 +01:00
(feat) prevent generate invoices at 0
This commit is contained in:
parent
ed4295b847
commit
e139067954
@ -0,0 +1,117 @@
|
||||
import { IApplication } from '../../models/application';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SubmitHandler, useForm, useWatch } from 'react-hook-form';
|
||||
import { SettingName, SettingValue } from '../../models/setting';
|
||||
import { useEffect } from 'react';
|
||||
import SettingAPI from '../../api/setting';
|
||||
import SettingLib from '../../lib/setting';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import { FormSwitch } from '../form/form-switch';
|
||||
import { FabButton } from '../base/fab-button';
|
||||
import { Loader } from '../base/loader';
|
||||
import { react2angular } from 'react2angular';
|
||||
import FormatLib from '../../lib/format';
|
||||
import { FormInput } from '../form/form-input';
|
||||
import { UnsavedFormAlert } from '../form/unsaved-form-alert';
|
||||
import { UIRouter } from '@uirouter/angularjs';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
const invoiceSettings: SettingName[] = ['invoice_prefix', 'payment_schedule_prefix', 'prevent_invoices_zero'];
|
||||
|
||||
interface InvoicesSettingsPanelProps {
|
||||
onError: (message: string) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
uiRouter: UIRouter
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoices settings display and edition
|
||||
*/
|
||||
export const InvoicesSettingsPanel: React.FC<InvoicesSettingsPanelProps> = ({ onError, onSuccess, uiRouter }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
const { control, register, handleSubmit, reset, formState } = useForm<Record<SettingName, SettingValue>>();
|
||||
const invoicePrefix = useWatch({ control, name: 'invoice_prefix' });
|
||||
const schedulePrefix = useWatch({ control, name: 'payment_schedule_prefix' });
|
||||
|
||||
const example = {
|
||||
id: Math.ceil(Math.random() * 100),
|
||||
idSchedule: Math.ceil(Math.random() * 100),
|
||||
date: FormatLib.dateFilename(new Date())
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
SettingAPI.query(invoiceSettings)
|
||||
.then(settings => {
|
||||
const data = SettingLib.bulkMapToObject(settings);
|
||||
reset(data);
|
||||
})
|
||||
.catch(onError);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Callback triggered when the form is submitted: save the settings
|
||||
*/
|
||||
const onSubmit: SubmitHandler<Record<SettingName, SettingValue>> = (data) => {
|
||||
SettingAPI.bulkUpdate(SettingLib.objectToBulkMap(data)).then(() => {
|
||||
onSuccess(t('app.admin.invoices_settings_panel.update_success'));
|
||||
}, reason => {
|
||||
onError(reason);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='invoices-settings-panel'>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<UnsavedFormAlert uiRouter={uiRouter} formState={formState} />
|
||||
<div className="setting-section">
|
||||
<p className="section-title">{t('app.admin.invoices_settings_panel.disable_invoices_zero')}</p>
|
||||
<FormSwitch control={control}
|
||||
label={t('app.admin.invoices_settings_panel.disable_invoices_zero_label', { AMOUNT: FormatLib.price(0) })}
|
||||
id="prevent_invoices_zero" />
|
||||
</div>
|
||||
<div className="setting-section">
|
||||
<p className="section-title" role="heading">{t('app.admin.invoices_settings_panel.filename')}</p>
|
||||
<FabAlert level="warning">
|
||||
<HtmlTranslate trKey="app.admin.invoices_settings_panel.filename_info" />
|
||||
</FabAlert>
|
||||
<FormInput register={register} id="invoice_prefix" label={t('app.admin.invoices_settings_panel.prefix')} />
|
||||
<div className="example">
|
||||
<span className="title" role="heading">{t('app.admin.invoices_settings_panel.example')}</span>
|
||||
<p className="content">
|
||||
{invoicePrefix}-{example.id}_{example.date}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="setting-section">
|
||||
<p className="section-title" role="heading">{t('app.admin.invoices_settings_panel.schedule_filename')}</p>
|
||||
<FabAlert level="warning">
|
||||
<HtmlTranslate trKey="app.admin.invoices_settings_panel.schedule_filename_info" />
|
||||
</FabAlert>
|
||||
<FormInput register={register} id="payment_schedule_prefix" label={t('app.admin.invoices_settings_panel.prefix')} />
|
||||
<div className="example">
|
||||
<span className="title" role="heading">{t('app.admin.invoices_settings_panel.example')}</span>
|
||||
<p className="content">
|
||||
{schedulePrefix}-{example.idSchedule}_{example.date}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="actions">
|
||||
<FabButton type='submit' className='save-btn'>{t('app.admin.invoices_settings_panel.save')}</FabButton>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const InvoicesSettingspanelWrapper: React.FC<InvoicesSettingsPanelProps> = (props) => {
|
||||
return (
|
||||
<Loader>
|
||||
<InvoicesSettingsPanel {...props} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('invoicesSettingsPanel', react2angular(InvoicesSettingspanelWrapper, ['onError', 'onSuccess', 'uiRouter']));
|
@ -17,8 +17,8 @@
|
||||
/**
|
||||
* Controller used in the admin invoices listing page
|
||||
*/
|
||||
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', 'stripeSecretKey', '_t', 'Member', 'uiTourService', 'Payment', 'onlinePaymentStatus',
|
||||
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, stripeSecretKey, _t, Member, uiTourService, Payment, onlinePaymentStatus) {
|
||||
Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'Invoice', 'AccountingPeriod', 'AuthService', 'invoices', 'closedPeriods', '$uibModal', 'growl', '$filter', 'Setting', 'settings', 'stripeSecretKey', '_t', 'Member', 'uiTourService', 'Payment', 'onlinePaymentStatus', '$uiRouter',
|
||||
function ($scope, $state, Invoice, AccountingPeriod, AuthService, invoices, closedPeriods, $uibModal, growl, $filter, Setting, settings, stripeSecretKey, _t, Member, uiTourService, Payment, onlinePaymentStatus, $uiRouter) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// number of invoices loaded each time we click on 'load more...'
|
||||
@ -53,22 +53,6 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
// Default invoices ordering/sorting
|
||||
$scope.orderInvoice = '-date';
|
||||
|
||||
// Invoice PDF filename settings (and example)
|
||||
$scope.file = {
|
||||
prefix: settings.invoice_prefix,
|
||||
nextId: 40,
|
||||
date: moment().format('DDMMYYYY'),
|
||||
templateUrl: '/admin/invoices/settings/editPrefix.html'
|
||||
};
|
||||
|
||||
// Payment Schedule PDF filename settings (and example)
|
||||
$scope.scheduleFile = {
|
||||
prefix: settings.payment_schedule_prefix,
|
||||
nextId: 11,
|
||||
date: moment().format('DDMMYYYY'),
|
||||
templateUrl: '/admin/invoices/settings/editSchedulePrefix.html'
|
||||
};
|
||||
|
||||
// Invoices parameters
|
||||
$scope.invoice = {
|
||||
logo: null,
|
||||
@ -131,6 +115,9 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
// Is shown the modal dialog to select a payment gateway
|
||||
$scope.openSelectGatewayModal = false;
|
||||
|
||||
// the following item is used by the UnsavedFormAlert component to detect a page change
|
||||
$scope.uiRouter = $uiRouter;
|
||||
|
||||
/**
|
||||
* Return the VAT rate applicable to the machine reservations
|
||||
* @return {number}
|
||||
@ -506,70 +493,6 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a modal dialog allowing the user to edit the prefix of the invoice file name
|
||||
*/
|
||||
$scope.openEditPrefix = function () {
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: $scope.file.templateUrl,
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
model () { return $scope.file.prefix; }
|
||||
},
|
||||
controller: ['$scope', '$uibModalInstance', 'model', function ($scope, $uibModalInstance, model) {
|
||||
$scope.model = model;
|
||||
$scope.ok = function () { $uibModalInstance.close($scope.model); };
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
}]
|
||||
});
|
||||
|
||||
return modalInstance.result.then(function (model) {
|
||||
Setting.update({ name: 'invoice_prefix' }, { value: model }, function (data) {
|
||||
$scope.file.prefix = model;
|
||||
return growl.success(_t('app.admin.invoices.prefix_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
if (error.status === 304) return;
|
||||
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_prefix'));
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open a modal dialog allowing the user to edit the prefix of the payment schedules file names
|
||||
*/
|
||||
$scope.openEditSchedulePrefix = function () {
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: $scope.scheduleFile.templateUrl,
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
model () { return $scope.scheduleFile.prefix; }
|
||||
},
|
||||
controller: ['$scope', '$uibModalInstance', 'model', function ($scope, $uibModalInstance, model) {
|
||||
$scope.model = model;
|
||||
$scope.ok = function () { $uibModalInstance.close($scope.model); };
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
}]
|
||||
});
|
||||
|
||||
modalInstance.result.then(function (model) {
|
||||
Setting.update({ name: 'payment_schedule_prefix' }, { value: model }, function (data) {
|
||||
$scope.scheduleFile.prefix = model;
|
||||
return growl.success(_t('app.admin.invoices.prefix_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
if (error.status === 304) return;
|
||||
|
||||
growl.error(_t('app.admin.invoices.an_error_occurred_while_saving_the_prefix'));
|
||||
console.error(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback to save the value of the text zone when editing is done
|
||||
*/
|
||||
|
@ -35,6 +35,13 @@ export default class FormatLib {
|
||||
return Intl.DateTimeFormat().format(moment(date).toDate());
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a date formatted for use within a filename
|
||||
*/
|
||||
static dateFilename = (date: Date|TDateISO|TDateISODate): string => {
|
||||
return moment(date).format('DDMMYYYY');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the formatted localized time for the given date
|
||||
*/
|
||||
|
@ -64,7 +64,8 @@ export const invoicesSettings = [
|
||||
'invoice_text',
|
||||
'invoice_legals',
|
||||
'invoice_prefix',
|
||||
'payment_schedule_prefix'
|
||||
'payment_schedule_prefix',
|
||||
'prevent_invoices_zero'
|
||||
] as const;
|
||||
|
||||
export const bookingSettings = [
|
||||
|
@ -952,7 +952,7 @@ angular.module('application.router', ['ui.router'])
|
||||
return Setting.query({
|
||||
names: "['invoice_legals', 'invoice_text', 'invoice_VAT-rate', 'invoice_VAT-rate_Machine', 'invoice_VAT-rate_Training', 'invoice_VAT-rate_Space', " +
|
||||
"'invoice_VAT-rate_Event', 'invoice_VAT-rate_Subscription', 'invoice_VAT-rate_Product', 'invoice_VAT-active', 'invoice_order-nb', 'invoice_code-value', " +
|
||||
"'invoice_code-active', 'invoice_reference', 'invoice_logo', 'payment_gateway', 'payment_schedule_prefix', " +
|
||||
"'invoice_code-active', 'invoice_reference', 'invoice_logo', 'payment_gateway', 'payment_schedule_prefix', 'invoicing_module', " +
|
||||
"'feature_tour_display', 'online_payment_module', 'stripe_public_key', 'stripe_currency', 'invoice_prefix']"
|
||||
}).$promise;
|
||||
}],
|
||||
|
@ -54,6 +54,7 @@
|
||||
@import "modules/form/form-file-upload";
|
||||
@import "modules/form/form-image-upload";
|
||||
@import "modules/group/change-group";
|
||||
@import "modules/invoices/invoices-settings-panel";
|
||||
@import "modules/layout/header-page";
|
||||
@import "modules/machines/machine-card";
|
||||
@import "modules/machines/machine-form";
|
||||
|
@ -63,11 +63,10 @@
|
||||
}
|
||||
|
||||
.invoice-placeholder {
|
||||
width: 80%;
|
||||
width: 95%;
|
||||
max-width: 800px;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
margin-top: 2em;
|
||||
margin: 2em auto auto;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #aeaeae #979797 #7b7b7b;
|
||||
|
@ -0,0 +1,51 @@
|
||||
.invoices-settings-panel {
|
||||
margin: 24px 0 0 0;
|
||||
background-color: var(--gray-soft-light);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 32px;
|
||||
|
||||
.setting-section {
|
||||
background-color: white;
|
||||
border: 1px solid var(--gray-soft-dark);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: 32px;
|
||||
padding: 24px;
|
||||
|
||||
.section-title {
|
||||
font-family: var(--font-text);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.example {
|
||||
background-color: var(--gray-soft-light);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 8px;
|
||||
.title {
|
||||
font-family: var(--font-text);
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.content {
|
||||
font-family: var(--font-text);
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
.save-btn {
|
||||
background-color: var(--main);
|
||||
color: var(--main-text-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--main-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,105 +2,99 @@
|
||||
<i class="fa fa-warning m-r"></i>
|
||||
<span translate>{{ 'app.admin.invoices.warning_invoices_disabled' }}</span>
|
||||
</div>
|
||||
<form class="invoice-placeholder">
|
||||
<div class="invoice-logo">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
|
||||
<img base-sixty-four-image="invoice.logo" ng-if="invoice.logo && invoice.logo.base64">
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'app.admin.invoices.change_logo' | translate }}
|
||||
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
|
||||
<div class="row">
|
||||
<invoices-settings-panel class="col-md-5" on-error="onError" on-success="onSuccess" ui-router="uiRouter"></invoices-settings-panel>
|
||||
<section class="col-md-7">
|
||||
<div class="alert alert-warning p-md m-t-md" role="alert">
|
||||
<span ng-bind-html="'app.admin.invoices.edit_setting_info_html' | translate"></span>
|
||||
</div>
|
||||
<form class="invoice-placeholder">
|
||||
<div class="invoice-logo">
|
||||
<img src="data:image/png;base64," data-src="holder.js/100%x100%/text:/font:'Font Awesome 5 Free'/icon" bs-holder ng-if="!invoice.logo" class="img-responsive">
|
||||
<img base-sixty-four-image="invoice.logo" ng-if="invoice.logo && invoice.logo.base64">
|
||||
<div class="tools-box">
|
||||
<div class="btn-group">
|
||||
<div class="btn btn-default btn-file">
|
||||
<i class="fa fa-edit"></i> {{ 'app.admin.invoices.change_logo' | translate }}
|
||||
<input type="file" accept="image/png,image/jpeg,image/x-png,image/pjpeg" name="invoice[logo][attachment]" ng-model="invoice.logo" base-sixty-four-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="invoice-buyer-infos">
|
||||
<strong translate>{{ 'app.admin.invoices.john_smith' }}</strong>
|
||||
<div translate>{{ 'app.admin.invoices.john_smith_at_example_com' }}</div>
|
||||
</div>
|
||||
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'app.admin.invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
|
||||
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'app.admin.invoices.code_' | translate }} {{invoice.code.model}}</div>
|
||||
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'app.admin.invoices.code_disabled' }}</div>
|
||||
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'app.admin.invoices.order_num' | translate }} {{mkNumber()}}</div>
|
||||
<div class="invoice-date">{{ 'app.admin.invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
|
||||
<div class="invoice-object">
|
||||
{{ 'app.admin.invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
|
||||
</div>
|
||||
<div class="invoice-data">
|
||||
{{ 'app.admin.invoices.order_summary' | translate }}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'app.admin.invoices.details' }}</th>
|
||||
<th class="right" translate>{{ 'app.admin.invoices.amount' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'app.admin.invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
<div class="invoice-buyer-infos">
|
||||
<strong translate>{{ 'app.admin.invoices.john_smith' }}</strong>
|
||||
<div translate>{{ 'app.admin.invoices.john_smith_at_example_com' }}</div>
|
||||
</div>
|
||||
<div class="invoice-reference invoice-editable" ng-click="openEditReference()">{{ 'app.admin.invoices.invoice_reference_' | translate }} {{mkReference()}}</div>
|
||||
<div class="invoice-code invoice-editable" ng-show="invoice.code.active" ng-click="openEditCode()">{{ 'app.admin.invoices.code_' | translate }} {{invoice.code.model}}</div>
|
||||
<div class="invoice-code invoice-activable" ng-show="!invoice.code.active" ng-click="openEditCode()" translate>{{ 'app.admin.invoices.code_disabled' }}</div>
|
||||
<div class="invoice-order invoice-editable" ng-click="openEditInvoiceNb()"> {{ 'app.admin.invoices.order_num' | translate }} {{mkNumber()}}</div>
|
||||
<div class="invoice-date">{{ 'app.admin.invoices.invoice_issued_on_DATE_at_TIME' | translate:{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT')} }}</div>
|
||||
<div class="invoice-object">
|
||||
{{ 'app.admin.invoices.object_reservation_of_john_smith_on_DATE_at_TIME' | translate:{DATE:(inOneWeek | amDateFormat:'L'), TIME:(inOneWeek | amDateFormat:'LT')} }}
|
||||
</div>
|
||||
<div class="invoice-data">
|
||||
{{ 'app.admin.invoices.order_summary' | translate }}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th translate>{{ 'app.admin.invoices.details' }}</th>
|
||||
<th class="right" translate>{{ 'app.admin.invoices.amount' }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 'app.admin.invoices.machine_booking-3D_printer' | translate }} {{inOneWeek | amDateFormat:'LLL'}} - {{inOneWeekAndOneHour | amDateFormat:'LT'}}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
|
||||
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
|
||||
<td ng-show="!invoice.VAT.active" translate>{{ 'app.admin.invoices.total_amount' }}</td>
|
||||
<td ng-show="invoice.VAT.active" translate>{{ 'app.admin.invoices.total_including_all_taxes' }}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-total" ng-class="{'bold vat-line':invoice.VAT.active}">
|
||||
<td ng-show="!invoice.VAT.active" translate>{{ 'app.admin.invoices.total_amount' }}</td>
|
||||
<td ng-show="invoice.VAT.active" translate>{{ 'app.admin.invoices.total_including_all_taxes' }}</td>
|
||||
<td class="right">{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
|
||||
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.VAT_disabled' }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr class="invoice-vat invoice-activable" ng-click="openEditVAT()" ng-show="!invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.VAT_disabled' }}</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
|
||||
<td translate translate-values="{RATE:getMachineExampleRate(), AMOUNT:(30.0 | currency)}">{{ 'app.admin.invoices.including_VAT' }}</td>
|
||||
<td>{{30-(30/(getMachineExampleRate()/100+1)) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.including_total_excluding_taxes' }}</td>
|
||||
<td>{{30/(getMachineExampleRate()/100+1) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.including_amount_payed_on_ordering' }}</td>
|
||||
<td>{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-vat invoice-editable vat-line italic" ng-click="openEditVAT()" ng-show="invoice.VAT.active">
|
||||
<td translate translate-values="{RATE:getMachineExampleRate(), AMOUNT:(30.0 | currency)}">{{ 'app.admin.invoices.including_VAT' }}</td>
|
||||
<td>{{30-(30/(getMachineExampleRate()/100+1)) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-ht vat-line italic" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.including_total_excluding_taxes' }}</td>
|
||||
<td>{{30/(getMachineExampleRate()/100+1) | currency}}</td>
|
||||
</tr>
|
||||
<tr class="invoice-payed vat-line bold" ng-show="invoice.VAT.active">
|
||||
<td translate>{{ 'app.admin.invoices.including_amount_payed_on_ordering' }}</td>
|
||||
<td>{{30.0 | currency}}</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
|
||||
{{ 'app.admin.invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
|
||||
</p>
|
||||
</div>
|
||||
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
|
||||
options='{
|
||||
"placeholder": "{{ "app.admin.invoices.important_notes" | translate }}",
|
||||
"buttons": ["underline"]
|
||||
}'
|
||||
ng-blur="textEditEnd($event)">
|
||||
</div>
|
||||
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
|
||||
options='{
|
||||
"placeholder": "{{ "app.admin.invoices.address_and_legal_information" | translate }}",
|
||||
"buttons": ["bold", "underline"]
|
||||
}'
|
||||
ng-blur="legalsEditEnd($event)">
|
||||
</div>
|
||||
</form>
|
||||
<div class="invoice-file">
|
||||
<h3 class="m-l" translate>{{ 'app.admin.invoices.filename' }}</h3>
|
||||
<i class="fa fa-file-pdf-o" aria-hidden="true"></i>
|
||||
<span class="filename"><span class="prefix" ng-click="openEditPrefix()">{{file.prefix}}</span>-{{file.nextId}}_{{file.date}}.pdf</span>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="invoice-payment" translate translate-values="{DATE:(today | amDateFormat:'L'), TIME:(today | amDateFormat:'LT'), AMOUNT:(30.0 | currency)}">
|
||||
{{ 'app.admin.invoices.settlement_by_debit_card_on_DATE_at_TIME_for_an_amount_of_AMOUNT' }}
|
||||
</p>
|
||||
</div>
|
||||
<div medium-editor class="invoice-text invoice-editable" ng-model="invoice.text.content"
|
||||
options='{
|
||||
"placeholder": "{{ "app.admin.invoices.important_notes" | translate }}",
|
||||
"buttons": ["underline"]
|
||||
}'
|
||||
ng-blur="textEditEnd($event)">
|
||||
</div>
|
||||
<div medium-editor class="invoice-legals invoice-editable" ng-model="invoice.legals.content"
|
||||
options='{
|
||||
"placeholder": "{{ "app.admin.invoices.address_and_legal_information" | translate }}",
|
||||
"buttons": ["bold", "underline"]
|
||||
}'
|
||||
ng-blur="legalsEditEnd($event)">
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<div class="schedule-file">
|
||||
<h3 class="m-l" translate>{{ 'app.admin.invoices.schedule_filename' }}</h3>
|
||||
<span class="fa-stack">
|
||||
<i class="fa fa-file-pdf" style="top: 0; left: 0" aria-hidden="true"></i>
|
||||
<i class="fa fa-file-pdf" style="top: 10px; left: 10px" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span class="filename"><span class="prefix" ng-click="openEditSchedulePrefix()">{{scheduleFile.prefix}}</span>-{{scheduleFile.nextId}}_{{scheduleFile.date}}.pdf</span>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/ng-template" id="addYear.html">
|
||||
<table class="invoice-element-legend">
|
||||
|
@ -1,20 +0,0 @@
|
||||
<div class="custom-invoice">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>{{ 'app.admin.invoices.filename' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="alert alert-info m-h-md" translate>
|
||||
{{ 'app.admin.invoices.prefix_info' }}
|
||||
</p>
|
||||
<div>
|
||||
<div class="model">
|
||||
<label for="prefix" translate>{{ 'app.admin.invoices.prefix' }}</label>
|
||||
<input type="text" id="prefix" class="form-control" ng-model="model">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
@ -1,20 +0,0 @@
|
||||
<div class="custom-invoice">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" translate>{{ 'app.admin.invoices.filename' }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="alert alert-info m-h-md" translate>
|
||||
{{ 'app.admin.invoices.schedule_prefix_info' }}
|
||||
</p>
|
||||
<div>
|
||||
<div class="model">
|
||||
<label for="prefix" translate>{{ 'app.admin.invoices.prefix' }}</label>
|
||||
<input type="text" id="prefix" class="form-control" ng-model="model">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
|
||||
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
@ -42,7 +42,7 @@ class CartItem::Reservation < CartItem::BaseItem
|
||||
|
||||
def valid?(all_items)
|
||||
pending_subscription = all_items.find { |i| i.is_a?(CartItem::Subscription) }
|
||||
|
||||
|
||||
reservation_deadline_minutes = Setting.get('reservation_deadline').to_i
|
||||
reservation_deadline = reservation_deadline_minutes.minutes.since
|
||||
|
||||
@ -65,7 +65,7 @@ class CartItem::Reservation < CartItem::BaseItem
|
||||
end
|
||||
|
||||
if slot_db.start_at < reservation_deadline && !@operator.privileged?
|
||||
@errors[:slot] = 'cannot reserve a slot ' + reservation_deadline_minutes.to_s + ' minutes prior to its start'
|
||||
@errors[:slot] = "cannot reserve a slot #{reservation_deadline_minutes} minutes prior to its start"
|
||||
return false
|
||||
end
|
||||
|
||||
|
@ -163,7 +163,8 @@ class Setting < ApplicationRecord
|
||||
store_withdrawal_instructions
|
||||
store_hidden
|
||||
advanced_accounting
|
||||
external_id] }
|
||||
external_id
|
||||
prevent_invoices_zero] }
|
||||
# WARNING: when adding a new key, you may also want to add it in:
|
||||
# - config/locales/en.yml#settings
|
||||
# - app/frontend/src/javascript/models/setting.ts#SettingName
|
||||
|
@ -49,18 +49,8 @@ class ShoppingCart
|
||||
# Build the dataset for the current ShoppingCart and save it into the database.
|
||||
# Data integrity is guaranteed: all goes right or nothing is saved.
|
||||
def build_and_save(payment_id, payment_type)
|
||||
user_validation_required = Setting.get('user_validation_required')
|
||||
user_validation_required_list = Setting.get('user_validation_required_list')
|
||||
unless @operator.privileged?
|
||||
if user_validation_required && user_validation_required_list.present?
|
||||
list = user_validation_required_list.split(',')
|
||||
errors = []
|
||||
items.each do |item|
|
||||
errors.push("User validation is required to reserve #{item.type}") if list.include?(item.type) && !@customer.validated_at?
|
||||
end
|
||||
return { success: nil, payment: nil, errors: errors } unless errors.empty?
|
||||
end
|
||||
end
|
||||
validation_errors = check_user_validation(items)
|
||||
return { success: nil, payment: nil, errors: validation_errors } if validation_errors
|
||||
|
||||
price = total
|
||||
objects = []
|
||||
@ -74,6 +64,8 @@ class ShoppingCart
|
||||
|
||||
payment = create_payment_document(price, objects, payment_id, payment_type)
|
||||
WalletService.debit_user_wallet(payment, @customer)
|
||||
next if Setting.get('prevent_invoices_zero') && price[:total].zero?
|
||||
|
||||
payment.save
|
||||
payment.post_save(payment_id, payment_type)
|
||||
end
|
||||
@ -164,4 +156,20 @@ class ShoppingCart
|
||||
PrepaidPackService.update_user_minutes(@customer, reservation)
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the current cart needs the user to have been validated, and if the condition is satisfied.
|
||||
# Return an array of errors, if any; false otherwise
|
||||
def check_user_validation(items)
|
||||
user_validation_required = Setting.get('user_validation_required')
|
||||
user_validation_required_list = Setting.get('user_validation_required_list')
|
||||
if !@operator.privileged? && (user_validation_required && user_validation_required_list.present?)
|
||||
list = user_validation_required_list.split(',')
|
||||
errors = []
|
||||
items.each do |item|
|
||||
errors.push("User validation is required to reserve #{item.type}") if list.include?(item.type) && !@customer.validated_at?
|
||||
end
|
||||
return errors unless errors.empty?
|
||||
end
|
||||
false
|
||||
end
|
||||
end
|
||||
|
@ -54,7 +54,7 @@ module Payments::PaymentConcern
|
||||
)
|
||||
invoice.wallet_amount = order.wallet_amount
|
||||
invoice.wallet_transaction_id = order.wallet_transaction_id
|
||||
invoice.save
|
||||
invoice.save unless Setting.get('prevent_invoices_zero') && order.total.zero?
|
||||
order.update(invoice_id: invoice.id)
|
||||
end
|
||||
end
|
||||
|
@ -6,7 +6,7 @@ module Statistics::Concerns::ComputeConcern
|
||||
|
||||
class_methods do
|
||||
def calcul_ca(invoice)
|
||||
return nil unless invoice
|
||||
return 0 unless invoice
|
||||
|
||||
ca = 0
|
||||
# sum each items in the invoice (+ for invoices/- for refunds)
|
||||
|
@ -11,8 +11,8 @@ json.is_online_card invoice.paid_by_card?
|
||||
json.date invoice.is_a?(Avoir) ? invoice.avoir_date : invoice.created_at
|
||||
json.chained_footprint invoice.check_footprint
|
||||
json.main_object do
|
||||
json.type invoice.main_item.object_type
|
||||
json.id invoice.main_item.object_id
|
||||
json.type invoice.invoice_items.find(&:main).object_type
|
||||
json.id invoice.invoice_items.find(&:main).object_id
|
||||
end
|
||||
json.items invoice.invoice_items do |item|
|
||||
json.id item.id
|
||||
|
@ -672,6 +672,7 @@ en:
|
||||
no_invoices_for_now: "No invoices for now."
|
||||
payment_schedules: "Payment schedules"
|
||||
invoicing_settings: "Invoicing settings"
|
||||
edit_setting_info_html: "<strong>Information</strong><p>Hover over the invoice elements below, all items that light up in yellow are editable.</p>"
|
||||
warning_invoices_disabled: "Warning: invoices are not enabled. No invoices will be generated by Fab-manager. Nevertheless, you must correctly fill the information below, especially VAT."
|
||||
change_logo: "Change logo"
|
||||
john_smith: "John Smith"
|
||||
@ -1803,7 +1804,7 @@ en:
|
||||
failed_to_remove: "An error occurred, unable to delete the report"
|
||||
local_payment_form:
|
||||
about_to_cash: "You're about to confirm the cashing by an external payment mean. Please do not click on the button below until you have fully cashed the requested payment."
|
||||
about_to_confirm: "You're about to confirm your {ITEM, select, subscription{subscription} other{reservation}}."
|
||||
about_to_confirm: "You're about to confirm your {ITEM, select, subscription{subscription} reservation{reservation} other{order}}."
|
||||
payment_method: "Payment method"
|
||||
method_card: "Online by card"
|
||||
method_check: "By check"
|
||||
@ -2229,3 +2230,14 @@ en:
|
||||
store_hidden: "Hide the store"
|
||||
save: "Save"
|
||||
update_success: "The settings were successfully updated"
|
||||
invoices_settings_panel:
|
||||
disable_invoices_zero: "Disable the invoices at 0"
|
||||
disable_invoices_zero_label: "Do not generate invoices at {AMOUNT}"
|
||||
filename: "Edit the file name"
|
||||
filename_info: "<strong>Information</strong><p>The invoices are generated as PDF files, named with the following prefix.</p>"
|
||||
schedule_filename: "Edit the payment schedule file name"
|
||||
schedule_filename_info: "<strong>Information</strong><p>The payment shedules are generated as PDF files, named with the following prefix.</p>"
|
||||
prefix: "Prefix"
|
||||
example: "Example"
|
||||
save: "Save"
|
||||
update_success: "The settings were successfully updated"
|
||||
|
@ -672,6 +672,7 @@ fr:
|
||||
no_invoices_for_now: "Aucune facture pour le moment."
|
||||
payment_schedules: "Échéanciers de paiement"
|
||||
invoicing_settings: "Paramètres de facturation"
|
||||
edit_setting_info_html: "<strong>Information</strong><p>Survoler les éléments de la facture ci-dessous, tous les éléments qui s’éclairent en jaune sont modifiables.</p>"
|
||||
warning_invoices_disabled: "Attention : les factures ne sont pas activées. Aucune facture ne sera générée par Fab-manager. Vous devez néanmoins remplir correctement les informations ci-dessous, particulièrement la TVA."
|
||||
change_logo: "Changer le logo"
|
||||
john_smith: "Jean Dupont"
|
||||
|
@ -634,3 +634,4 @@ en:
|
||||
store_hidden: "Store hidden to the public"
|
||||
advanced_accounting: "Advanced accounting"
|
||||
external_id: "external identifier"
|
||||
prevent_invoices_zero: "prevent building invoices at 0"
|
||||
|
18
test/fixtures/history_values.yml
vendored
18
test/fixtures/history_values.yml
vendored
@ -905,3 +905,21 @@ history_value_95:
|
||||
updated_at: 2022-12-09 14:00:14.512000000 Z
|
||||
footprint:
|
||||
invoicing_profile_id: 1
|
||||
|
||||
history_value_96:
|
||||
id: 96
|
||||
setting_id: 95
|
||||
value: 'false'
|
||||
created_at: 2022-12-22 14:45:07.891240000 Z
|
||||
updated_at: 2022-12-22 14:45:07.891240000 Z
|
||||
footprint:
|
||||
invoicing_profile_id: 1
|
||||
|
||||
history_value_97:
|
||||
id: 97
|
||||
setting_id: 96
|
||||
value: '0'
|
||||
created_at: 2022-11-29 21:02:47.354751000 Z
|
||||
updated_at: 2022-11-29 21:02:47.354751000 Z
|
||||
footprint:
|
||||
invoicing_profile_id: 1
|
||||
|
12
test/fixtures/settings.yml
vendored
12
test/fixtures/settings.yml
vendored
@ -556,3 +556,15 @@ setting_94:
|
||||
name: accounting_VAT_journal_code
|
||||
created_at: 2022-12-09 14:00:14.512000000 Z
|
||||
updated_at: 2022-12-09 14:00:14.512000000 Z
|
||||
|
||||
setting_95:
|
||||
id: 95
|
||||
name: prevent_invoices_zero
|
||||
created_at: 2022-12-22 14:45:07.891240000 Z
|
||||
updated_at: 2022-12-22 14:45:07.891240000 Z
|
||||
|
||||
setting_96:
|
||||
id: 96
|
||||
name: reservation_deadline
|
||||
created_at: 2022-11-29 21:02:47.354751000 Z
|
||||
updated_at: 2022-11-29 21:02:47.354751000 Z
|
||||
|
@ -726,5 +726,17 @@ export const settings: Array<Setting> = [
|
||||
value: 'true',
|
||||
last_update: '2022-12-20T16:02:01+0100',
|
||||
localized: 'identifiant externe'
|
||||
},
|
||||
{
|
||||
name: 'prevent_invoices_zero',
|
||||
value: 'false',
|
||||
last_update: '2022-12-22T14:45:07+0100',
|
||||
localized: 'éviter la génération de factures à 0'
|
||||
},
|
||||
{
|
||||
name: 'reservation_deadline',
|
||||
value: '0',
|
||||
last_update: '2022-11-29T21:02:47-0300',
|
||||
localized: "Empêcher la réservation avant qu'elle ne commence"
|
||||
}
|
||||
];
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { InvoicesSettingsPanel } from '../../../../app/frontend/src/javascript/components/invoices/invoices-settings-panel';
|
||||
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
describe('InvoicesSettingsPanel', () => {
|
||||
const onError = jest.fn();
|
||||
const onSuccess = jest.fn();
|
||||
|
||||
test('render InvoicesSettingsPanel', async () => {
|
||||
render(<InvoicesSettingsPanel onError={onError} onSuccess={onSuccess} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(/app.admin.invoices_settings_panel.disable_invoices_zero_label/)).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getAllByLabelText(/app.admin.invoices_settings_panel.prefix/)).toHaveLength(2);
|
||||
expect(screen.getByRole('heading', { name: /app.admin.invoices_settings_panel.filename/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: /app.admin.invoices_settings_panel.schedule_filename/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /app.admin.invoices_settings_panel.save/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('update filename example', async () => {
|
||||
render(<InvoicesSettingsPanel onError={onError} onSuccess={onSuccess} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByLabelText(/app.admin.invoices_settings_panel.prefix/)).toHaveLength(2);
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/app.admin.invoices_settings_panel.prefix/, { selector: 'input#invoice_prefix' }), { target: { value: 'Test Example' } });
|
||||
expect(screen.getByRole('heading', { name: /app.admin.invoices_settings_panel.filename/ }).parentNode.querySelector('.example > .content'))
|
||||
.toHaveTextContent(/^Test Example/);
|
||||
});
|
||||
|
||||
test('update schedule filename example', async () => {
|
||||
render(<InvoicesSettingsPanel onError={onError} onSuccess={onSuccess} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getAllByLabelText(/app.admin.invoices_settings_panel.prefix/)).toHaveLength(2);
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/app.admin.invoices_settings_panel.prefix/, { selector: 'input#payment_schedule_prefix' }), { target: { value: 'Schedule Test' } });
|
||||
expect(screen.getByRole('heading', { name: /app.admin.invoices_settings_panel.schedule_filename/ }).parentNode.querySelector('.example > .content'))
|
||||
.toHaveTextContent(/^Schedule Test/);
|
||||
});
|
||||
});
|
@ -12,8 +12,8 @@ class Reservations::PayWithPrepaidPackTest < ActionDispatch::IntegrationTest
|
||||
test 'user reserves a machine and pay by prepaid pack with success' do
|
||||
login_as(@acamus, scope: :user)
|
||||
|
||||
machine = Machine.first
|
||||
availability = machine.availabilities.first
|
||||
machine = Machine.find(1)
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
|
@ -13,7 +13,7 @@ class Reservations::PayWithWalletTest < ActionDispatch::IntegrationTest
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
|
@ -13,7 +13,7 @@ class Reservations::ReserveMachineTest < ActionDispatch::IntegrationTest
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
@ -85,7 +85,7 @@ class Reservations::ReserveMachineTest < ActionDispatch::IntegrationTest
|
||||
login_as(@user_without_subscription, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
@ -137,7 +137,7 @@ class Reservations::ReserveMachineTest < ActionDispatch::IntegrationTest
|
||||
|
||||
machine = Machine.find(6)
|
||||
plan = Plan.find(4)
|
||||
availability = machine.availabilities.first
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
|
@ -14,7 +14,7 @@ class Reservations::WithSubscriptionTest < ActionDispatch::IntegrationTest
|
||||
|
||||
plan = @user_with_subscription.subscribed_plan
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
@ -168,7 +168,7 @@ class Reservations::WithSubscriptionTest < ActionDispatch::IntegrationTest
|
||||
login_as(@vlonchamp, scope: :user)
|
||||
|
||||
machine = Machine.find(6)
|
||||
availability = machine.availabilities.first
|
||||
availability = Availability.find(4)
|
||||
|
||||
reservations_count = Reservation.count
|
||||
invoice_count = Invoice.count
|
||||
|
Loading…
x
Reference in New Issue
Block a user