1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-30 19:52:20 +01:00

Merge branch 'dev' for release 5.3.10

This commit is contained in:
Du Peng 2022-04-12 17:45:33 +02:00
commit b503c9fb34
39 changed files with 100 additions and 30 deletions

View File

@ -1,5 +1,15 @@
# Changelog Fab-manager
## v5.3.10 2022 April 12
- Updated generate invoice reference method
- Set invoice reference is required
- Fix a bug: unable to show machine availability slot for admin
- Fix a bug: unable to confirm modification of reservation for client
- Fix a bug: unable to show deleted user in reservation slot
- Fix a bug: race condition on invoice after payment (concerning payment schedules) https://app.clickup.com/t/25zpmn1
- Fix a bug: form maxlength count to exclude spaces and newline
## v5.3.9 2022 April 01
- Optimise sql query, avoid to N+1

View File

@ -499,7 +499,7 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$tran
slotId: $scope.events.modifiable.slot_id,
borderColor: $scope.events.modifiable.borderColor,
user: angular.copy($scope.events.modifiable.user),
title: $scope.currentUser.id === $scope.events.modifiable.user.id ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available')
title: (!$scope.events.modifiable.user || $scope.currentUser.id === $scope.events.modifiable.user.id) ? _t('app.logged.machines_reserve.i_ve_reserved') : _t('app.logged.machines_reserve.not_available')
};
$scope.events.modifiable.backgroundColor = 'white';

View File

@ -564,7 +564,12 @@ Application.Directives.directive('cart', ['$rootScope', '$uibModal', 'dialogs',
return dialogs.confirm({
templateUrl: '/shared/confirm_modify_slot_modal.html',
resolve: {
object () { return $scope.slot; }
object () {
if ($scope.slot.user && !$scope.slot.user.name) {
$scope.slot.user.name = _t('app.shared.confirm_modify_slot_modal.deleted_user');
}
return $scope.slot;
}
}
}
, function (type) {

View File

@ -80,7 +80,8 @@
<div class="widget-content no-bg auto wrapper" ng-class="{'reservations-locked': availability.lock}">
<ul class="list-unstyled" ng-if="reservations.length > 0">
<li ng-repeat="r in reservations" class="m-b-xs" ng-class="{'reservation-canceled':r.canceled_at}">
{{r.user.name}}
<span ng-if="r.user.name">{{r.user.name}}</span>
<span translate ng-if="!r.user.name">{{ 'app.admin.calendar.deleted_user' }}</span>
- <span class="label reservation-time">{{ r.start_at | amDateFormat:'LLL' }} - {{ r.end_at | amDateFormat:'LT' }}</span>
- <span class="label label-success text-white">{{r.reservable.name}}</span>
<span class="btn btn-warning btn-xs" ng-click="cancelBooking(r)" ng-if="!r.canceled_at"><i class="fa fa-times red"></i></span>

View File

@ -19,6 +19,7 @@
<div class="model">
<h4 translate>{{ 'app.admin.invoices.model' }}</h4>
<input type="text" class="form-control" ng-model="model">
<span class="help-block error" ng-show="model.trim() === ''" translate>{{ 'app.admin.invoices.invoice_reference_is_required' }}</span>
</div>
<div class="help">
<h4 translate>{{ 'app.admin.invoices.documentation' }}</h4>
@ -28,7 +29,7 @@
</div>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="ok()" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-warning" ng-click="ok()" ng-disabled="model.trim() === ''" translate>{{ 'app.shared.buttons.confirm' }}</button>
<button class="btn btn-default" ng-click="cancel()" translate>{{ 'app.shared.buttons.cancel' }}</button>
</div>
</div>

View File

@ -7,6 +7,7 @@
name="plan[base_name]"
class="form-control"
ng-maxlength="24"
ng-trim="false"
ng-model="plan.base_name"
required="true"/>
<span class="help-block error" ng-show="planForm['plan[base_name]'].$dirty && planForm['plan[base_name]'].$error.required" translate>{{ 'app.shared.plan.name_is_required' }}</span>

View File

@ -93,6 +93,13 @@
</tr>
</tbody>
</table>
<ul>
<li ng-repeat="(key, errors) in planForm.$error track by $index"> <strong>{{ key }}</strong> errors
<ul>
<li ng-repeat="e in errors">{{ e.$name }} has an error: <strong>{{ key }}</strong>.</li>
</ul>
</li>
</ul>
<div class="panel-footer no-padder">
<input type="submit" value="{{ 'app.shared.buttons.confirm_changes' | translate }}" class="r-b btn-valid btn btn-warning btn-block p-lg btn-lg text-u-c" ng-disabled="planForm.$invalid"/>

View File

@ -13,6 +13,7 @@
ng-required="true"
ng-minlength="minLength"
ng-maxlength="maxLength"
ng-trim="false"
ng-readonly="readOnly">
</div>
</div>

View File

@ -4,7 +4,7 @@
<div class="modal-body">
<form name="trainingForm" novalidate>
<div class="form-group" ng-class="{'has-error': trainingForm['training[description]'].$dirty && trainingForm['training[description]'].$invalid }">
<textarea name="training[description]" class="form-control" placeholder="{{ 'app.admin.trainings.describe_the_training_in_a_few_words' | translate }}" ng-model="training.description" ng-maxlength="255"></textarea>
<textarea name="training[description]" class="form-control" placeholder="{{ 'app.admin.trainings.describe_the_training_in_a_few_words' | translate }}" ng-model="training.description" ng-maxlength="255" ng-trim="false"></textarea>
<span class="help-block" ng-show="trainingForm['training[description]'].$dirty && trainingForm['training[description]'].$error.maxlength" translate>{{ 'app.admin.trainings.description_is_limited_to_255_characters' }}</span>
</div>
</form>

View File

@ -3,8 +3,8 @@
<h1 translate>{{ 'app.shared.confirm_modify_slot_modal.change_the_slot' }}</h1>
</div>
<div class="modal-body">
<p ng-show="currentUser.id === object.user.id" translate>{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_your_booking_slot_initially_planned_at' }} </p>
<p ng-show="currentUser.id !== object.user.id" translate translate-values="{NAME: object.user.name}">{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_NAME_s_booking_slot_initially_planned_at' }}</p>
<p ng-show="!object.user" translate>{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_your_booking_slot_initially_planned_at' }} </p>
<p ng-show="object.user && currentUser.id !== object.user.id" translate translate-values="{NAME: object.user.name}">{{ 'app.shared.confirm_modify_slot_modal.do_you_want_to_change_NAME_s_booking_slot_initially_planned_at' }}</p>
<p><strong>{{object.start | amDateFormat: 'LL'}} : {{object.start | amDateFormat:'LT'}} - {{object.end | amDateFormat:'LT'}}</strong></p>
</div>
<div class="modal-footer">

View File

@ -18,7 +18,8 @@ class Availabilities::StatusService
slot.id = s.id
slot.is_reserved = true
slot.title = "#{slot.machine.name} - #{@show_name ? r.user&.profile&.full_name : I18n.t('availabilities.not_available')}"
user_name = r.user ? r.user&.profile&.full_name : I18n.t('availabilities.deleted_user');
slot.title = "#{slot.machine.name} - #{@show_name ? user_name : I18n.t('availabilities.deleted_user')}"
slot.can_modify = true if %w[admin manager].include?(@current_user_role)
slot.reservations.push r

View File

@ -6,7 +6,7 @@ class PaymentDocumentService
def generate_reference(document, date: DateTime.current)
pattern = Setting.get('invoice_reference')
reference = replace_invoice_number_pattern(pattern)
reference = replace_invoice_number_pattern(pattern, document.created_at)
reference = replace_date_pattern(reference, date)
if document.is_a? Avoir
@ -51,7 +51,7 @@ class PaymentDocumentService
pad_and_truncate(number_of_invoices('global'), match.to_s.length)
end
reference = replace_invoice_number_pattern(reference)
reference = replace_invoice_number_pattern(reference, invoice.created_at)
replace_date_pattern(reference, invoice.created_at)
end
@ -71,26 +71,25 @@ class PaymentDocumentService
# Returns the number of current invoices in the given range around the current date.
# If range is invalid or not specified, the total number of invoices is returned.
# @param range {String} 'day', 'month', 'year'
# @param date {Date} the ending date
# @return {Integer}
##
def number_of_invoices(range)
def number_of_invoices(range, date = DateTime.current)
case range.to_s
when 'day'
start = DateTime.current.beginning_of_day
ending = DateTime.current.end_of_day
start = date.beginning_of_day
when 'month'
start = DateTime.current.beginning_of_month
ending = DateTime.current.end_of_month
start = date.beginning_of_month
when 'year'
start = DateTime.current.beginning_of_year
ending = DateTime.current.end_of_year
start = date.beginning_of_year
else
return get_max_id(Invoice) + get_max_id(PaymentSchedule)
end
return Invoice.count + PaymentSchedule.count unless defined? start && defined? ending
ending = date
return Invoice.count + PaymentSchedule.count unless defined? start
Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start, end_date: ending).length +
PaymentSchedule.where('created_at >= :start_date AND created_at < :end_date', start_date: start, end_date: ending).length
Invoice.where('created_at >= :start_date AND created_at <= :end_date', start_date: start, end_date: ending).length +
PaymentSchedule.where('created_at >= :start_date AND created_at <= :end_date', start_date: start, end_date: ending).length
end
##
@ -125,20 +124,20 @@ class PaymentDocumentService
# Replace the document number elements in the provided pattern with counts from the database
# @param reference {string}
##
def replace_invoice_number_pattern(reference)
def replace_invoice_number_pattern(reference, date)
copy = reference.dup
# document number per year (yy..yy)
copy.gsub!(/y+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('year'), match.to_s.length)
pad_and_truncate(number_of_invoices('year', date), match.to_s.length)
end
# document number per month (mm..mm)
copy.gsub!(/m+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('month'), match.to_s.length)
pad_and_truncate(number_of_invoices('month', date), match.to_s.length)
end
# document number per day (dd..dd)
copy.gsub!(/d+(?![^\[]*\])/) do |match|
pad_and_truncate(number_of_invoices('day'), match.to_s.length)
pad_and_truncate(number_of_invoices('day', date), match.to_s.length)
end
copy

View File

@ -18,8 +18,8 @@ json.array!(@slots) do |slot|
# the user who booked the slot, if the slot was reserved
if (%w[admin manager].include? @current_user_role) && slot.reservation
json.user do
json.id slot.reservation.user.id
json.name slot.reservation.user.profile.full_name
json.id slot.reservation.user&.id
json.name slot.reservation.user&.profile&.full_name
end
end
json.tag_ids slot.availability.tag_ids

View File

@ -19,8 +19,8 @@ json.array!(@slots) do |slot|
# the user who booked the slot, if the slot was reserved
if (%w[admin manager].include? @current_user_role) && slot.reservation
json.user do
json.id slot.reservation.user.id
json.name slot.reservation.user.profile.full_name
json.id slot.reservation.user&.id
json.name slot.reservation.user&.profile&.full_name
end
end
json.tag_ids slot.availability.tag_ids

View File

@ -2,7 +2,7 @@
json.id reservation.id
json.user_id reservation.statistic_profile.user_id
json.user_full_name reservation.user.profile.full_name
json.user_full_name reservation.user&.profile&.full_name
json.message reservation.message
json.slots_attributes reservation.slots do |s|
json.id s.id

View File

@ -109,6 +109,7 @@ de:
slots: "Slots"
slots_of: "von"
minutes: "Minuten"
deleted_user: "Deleted user"
#import external iCal calendar
icalendar:
icalendar_import: "iCalendar Import"
@ -508,6 +509,7 @@ de:
important_notes: "Wichtige Hinweise"
address_and_legal_information: "Adresse und rechtliche Informationen"
invoice_reference: "Rechnungsbezug"
invoice_reference_is_required: "Invoice reference is required."
text: "Text"
year: "Jahr"
month: "Monat"

View File

@ -109,6 +109,7 @@ en:
slots: "slots"
slots_of: "of"
minutes: "minutes"
deleted_user: "Deleted user"
# import external iCal calendar
icalendar:
icalendar_import: "iCalendar import"
@ -508,6 +509,7 @@ en:
important_notes: "Important notes"
address_and_legal_information: "Address and legal information"
invoice_reference: "Invoice reference"
invoice_reference_is_required: "Invoice reference is required."
text: "text"
year: "Year"
month: "Month"

View File

@ -109,6 +109,7 @@ es:
slots: "slots"
slots_of: "of"
minutes: "minutes"
deleted_user: "Deleted user"
#import external iCal calendar
icalendar:
icalendar_import: "iCalendar import"
@ -508,6 +509,7 @@ es:
important_notes: "Notas importantes"
address_and_legal_information: "Dirección e información legal"
invoice_reference: "Referencia de factura"
invoice_reference_is_required: "Invoice reference is required."
text: "text"
year: "Año"
month: "Mes"

View File

@ -109,6 +109,7 @@ fr:
slots: "créneaux"
slots_of: "de"
minutes: "minutes"
deleted_user: "Utilisateur supprimé"
#import external iCal calendar
icalendar:
icalendar_import: "Import iCalendar"
@ -508,6 +509,7 @@ fr:
important_notes: "Informations importantes"
address_and_legal_information: "Adresse et informations légales"
invoice_reference: "Référence facture"
invoice_reference_is_required: "La référence facture est requis."
text: "texte"
year: "Année"
month: "Mois"

View File

@ -109,6 +109,7 @@
slots: "reservasjoner"
slots_of: "av"
minutes: "minutter"
deleted_user: "Deleted user"
#import external iCal calendar
icalendar:
icalendar_import: "iCalendar import"
@ -508,6 +509,7 @@
important_notes: "Viktige merknader"
address_and_legal_information: "Adresse og juridisk informasjon"
invoice_reference: "Fakturareferanse"
invoice_reference_is_required: "Invoice reference is required."
text: "tekst"
year: "År"
month: "Måned"

View File

@ -109,6 +109,7 @@ pt:
slots: "slots"
slots_of: "do"
minutes: "minutos"
deleted_user: "Deleted user"
#import external iCal calendar
icalendar:
icalendar_import: "importar iCalendar"
@ -508,6 +509,7 @@ pt:
important_notes: "Notas importantes"
address_and_legal_information: "Endereço e informações legais"
invoice_reference: "Referencia de fatura"
invoice_reference_is_required: "Invoice reference is required."
text: "texto"
year: "Ano"
month: "Mês"

View File

@ -109,6 +109,7 @@ zu:
slots: "crwdns20302:0crwdne20302:0"
slots_of: "crwdns20304:0crwdne20304:0"
minutes: "crwdns20288:0crwdne20288:0"
deleted_user: "Deleted user"
#import external iCal calendar
icalendar:
icalendar_import: "crwdns6855:0crwdne6855:0"
@ -508,6 +509,7 @@ zu:
important_notes: "crwdns7385:0crwdne7385:0"
address_and_legal_information: "crwdns7387:0crwdne7387:0"
invoice_reference: "crwdns7389:0crwdne7389:0"
invoice_reference_is_required: "Invoice reference is required."
text: "crwdns21060:0crwdne21060:0"
year: "crwdns7391:0crwdne7391:0"
month: "crwdns7393:0crwdne7393:0"

View File

@ -283,6 +283,7 @@ de:
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Möchten Sie den {NAME}-Buchungsplatz ändern? Ursprünglich geplant um:"
cancel_this_reservation: "Reservierung stornieren"
i_want_to_change_date: "Ich möchte das Datum ändern"
deleted_user: "Deleted user"
#user public profile
public_profile:
last_activity_html: "Letzte Aktivität <br><strong>am {DATE}</strong>"

View File

@ -283,6 +283,7 @@ en:
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Do you want to change {NAME}'s booking slot, initially planned at:"
cancel_this_reservation: "Cancel this reservation"
i_want_to_change_date: "I want to change date"
deleted_user: "deleted user"
#user public profile
public_profile:
last_activity_html: "Last activity <br><strong>on {DATE}</strong>"

View File

@ -283,6 +283,7 @@ es:
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Desea cambiar la reserva de {NAME} , efectuada inicialmente el:"
cancel_this_reservation: "Cancelar reserva"
i_want_to_change_date: "Quiero cambiar la fecha"
deleted_user: "deleted user"
#user public profile
public_profile:
last_activity_html: "Last activity <br><strong>on {DATE}</strong>"

View File

@ -283,6 +283,7 @@ fr:
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Souhaitez-vous changer le créneau de réservation de {NAME}, initialement prévu au :"
cancel_this_reservation: "Annuler cette réservation"
i_want_to_change_date: "Je veux changer de date"
deleted_user: "l'utilisateur supprimé"
#user public profile
public_profile:
last_activity_html: "Dernière activité <br><strong>le {DATE}</strong>"

View File

@ -283,6 +283,7 @@
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Vil du endre {NAME} sin bestillingsplass, som opprinnelig er planlagt på:"
cancel_this_reservation: "Avbryt reservasjon"
i_want_to_change_date: "Jeg vil endre dato"
deleted_user: "deleted user"
#user public profile
public_profile:
last_activity_html: "Siste aktivitet <br><strong>{DATE}</strong>"

View File

@ -283,6 +283,7 @@ pt:
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "Você deseja alterar o agendamento do usuário {NAME}, inicialmente marcado para:"
cancel_this_reservation: "Cancelar essa reserva"
i_want_to_change_date: "Eu quero alterar a data"
deleted_user: "deleted user"
#user public profile
public_profile:
last_activity_html: "Última atividade <br><strong>em {DATE}</strong>"

View File

@ -283,6 +283,7 @@ zu:
do_you_want_to_change_NAME_s_booking_slot_initially_planned_at: "crwdns9825:0{NAME}crwdne9825:0"
cancel_this_reservation: "crwdns9827:0crwdne9827:0"
i_want_to_change_date: "crwdns9829:0crwdne9829:0"
deleted_user: "deleted user"
#user public profile
public_profile:
last_activity_html: "crwdns9843:0{DATE}crwdne9843:0"

View File

@ -53,6 +53,7 @@ de:
i_ve_reserved: "Ich reservierte"
length_must_be_slot_multiple: "muss mindestens %{MIN} Minuten nach dem Startdatum liegen"
must_be_associated_with_at_least_1_machine: "muss mindestens einer Maschine zugeordnet sein"
deleted_user: "Deleted user"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "Die Gruppe kann während eines Abonnements nicht geändert werden"

View File

@ -53,6 +53,7 @@ en:
i_ve_reserved: "I've reserved"
length_must_be_slot_multiple: "must be at least %{MIN} minutes after the start date"
must_be_associated_with_at_least_1_machine: "must be associated with at least 1 machine"
deleted_user: "Deleted user"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "Unable to change the group while a subscription is running"

View File

@ -53,6 +53,7 @@ es:
i_ve_reserved: "He reservado"
length_must_be_slot_multiple: "Debe ser al menos %{MIN} minutos después de la fecha de inicio"
must_be_associated_with_at_least_1_machine: "debe estar asociado con al menos 1 máquina"
deleted_user: "Deleted user"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "No se puede cambiar de grupo mientras haya una suscripción en curso"

View File

@ -53,6 +53,7 @@ fr:
i_ve_reserved: "J'ai réservé"
length_must_be_slot_multiple: "doit être au moins %{MIN} minutes après la date de début"
must_be_associated_with_at_least_1_machine: "doit être associé avec au moins 1 machine"
deleted_user: "Utilisateur supprimé"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "Impossible de changer le groupe tant qu'un abonnement est en cours"

View File

@ -53,6 +53,7 @@
i_ve_reserved: "Jeg har reservert"
length_must_be_slot_multiple: "må være minst %{MIN} minutter etter startdatoen"
must_be_associated_with_at_least_1_machine: "må være tilknyttet minst 1 maskin"
deleted_user: "Deleted user"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "Kan ikke endre gruppen mens et abonnement kjører"

View File

@ -53,6 +53,7 @@ pt:
i_ve_reserved: "Eu reservei"
length_must_be_slot_multiple: "deve ser pelo menos %{MIN} minutos após a data de início"
must_be_associated_with_at_least_1_machine: "deve estar associada a pelo menos uma máquina"
deleted_user: "Deleted user"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "Não é possível alterar o grupo enquanto uma assinatura está sendo executada"

View File

@ -53,6 +53,7 @@ zu:
i_ve_reserved: "crwdns3269:0crwdne3269:0"
length_must_be_slot_multiple: "crwdns3271:0%{MIN}crwdne3271:0"
must_be_associated_with_at_least_1_machine: "crwdns3273:0crwdne3273:0"
deleted_user: "Deleted user"
#members management
members:
unable_to_change_the_group_while_a_subscription_is_running: "crwdns3275:0crwdne3275:0"

View File

@ -120,6 +120,8 @@ class Stripe::Service < Payment::Service
pgo = PaymentGatewayObject.find_or_initialize_by(item: payment_schedule_item)
pgo.gateway_object = stp_invoice
pgo.save!
elsif stp_invoice.status == 'draft'
return # Could be that the stripe invoice does not yet reflect the payment made by the member, because we called that service just after payment is made. We call return here and PaymentScheduleItemWorker will anyway call that method every hour
else
notify_payment_schedule_item_error(payment_schedule_item)
payment_schedule_item.update_attributes(state: 'error')

View File

@ -126,5 +126,19 @@ namespace :fablab do
)
puts '-> Done'
end
desc 'Regenerate the invoices (invoices & avoirs) reference'
task :regenerate_invoices_reference, %i[year month] => :environment do |_task, args|
year = args.year || Time.current.year
month = args.month || Time.current.month
start_date = Time.zone.local(year.to_i, month.to_i, 1)
end_date = start_date.next_month
puts "-> Start regenerate the invoices reference between #{I18n.l start_date, format: :long} and " \
"#{I18n.l end_date - 1.minute, format: :long}"
invoices = Invoice.where('created_at >= :start_date AND created_at < :end_date', start_date: start_date, end_date: end_date)
.order(created_at: :asc)
invoices.each(&:update_reference)
puts '-> Done'
end
end
end

View File

@ -1,6 +1,6 @@
{
"name": "fab-manager",
"version": "5.3.9",
"version": "5.3.10",
"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",