mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-18 07:52:23 +01:00
Merge branch 'dev' for release 5.6.1
This commit is contained in:
commit
5ab2989a61
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,5 +1,17 @@
|
||||
# Changelog Fab-manager
|
||||
|
||||
## v5.6.1 2023 January 6
|
||||
|
||||
- Fix a bug: allow decimal values for VAT rates
|
||||
- Fix a bug: canceled reservations/slots not shown as it in the reservations dashboard
|
||||
- Fix a bug: no main item on some invoices
|
||||
- Fix a bug: unable to build accounting lines if no invoices
|
||||
- Fix a bug: unable to apply rounding correction on accounting lines
|
||||
- Fix a bug: empty object for some invoice item
|
||||
- Fix a bug: unable to filter Show only slots with reservations in public calendar for admin
|
||||
- Fix a security issue: updated json5 to 1.0.2 to fix [CVE-2022-46175](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-46175)
|
||||
- [TODO DEPLOY] `rails fablab:fix_invoice_items` => run this script BEFORE running the migrations
|
||||
|
||||
## v5.6.0 2023 January 5
|
||||
|
||||
- Ability to group machines by categories
|
||||
|
@ -89,18 +89,25 @@ const ReservationsPanel: React.FC<SpaceReservationsProps> = ({ userId, onError,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if all slots of the given reservation are canceled
|
||||
*/
|
||||
const isCancelled = (reservation: Reservation): boolean => {
|
||||
return reservation.slots_reservations_attributes.map(sr => sr.canceled_at).every(ca => ca != null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the reservation in a user-friendly way
|
||||
*/
|
||||
const renderReservation = (reservation: Reservation, state: 'past' | 'futur'): ReactNode => {
|
||||
return (
|
||||
<li key={reservation.id} className="reservation">
|
||||
<a className={`reservation-title ${details[reservation.id] ? 'clicked' : ''}`} onClick={toggleDetails(reservation.id)}>
|
||||
<a className={`reservation-title ${details[reservation.id] ? 'clicked' : ''} ${isCancelled(reservation) ? 'canceled' : ''}`} onClick={toggleDetails(reservation.id)}>
|
||||
{reservation.reservable.name} - {FormatLib.date(reservation.slots_reservations_attributes[0].slot_attributes.start_at)}
|
||||
</a>
|
||||
{details[reservation.id] && <FabPopover title={t('app.logged.dashboard.reservations.reservations_panel.slots_details')}>
|
||||
{reservation.slots_reservations_attributes.filter(s => filterSlot(s, state)).map(
|
||||
slotReservation => <span key={slotReservation.id} className="slot-details">
|
||||
slotReservation => <span key={slotReservation.id} className={`slot-details ${slotReservation.canceled_at ? 'canceled' : ''}`}>
|
||||
{FormatLib.date(slotReservation.slot_attributes.start_at)}, {FormatLib.time(slotReservation.slot_attributes.start_at)} - {FormatLib.time(slotReservation.slot_attributes.end_at)}
|
||||
</span>
|
||||
)}
|
||||
|
@ -115,6 +115,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
rules={{ required: true }}
|
||||
tooltip={t('app.admin.vat_settings_modal.VAT_rate_help')}
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
@ -132,6 +133,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate_Product"
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate_product')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
@ -143,6 +145,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate_Event"
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate_event')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
@ -154,6 +157,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate_Machine"
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate_machine')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
@ -165,6 +169,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate_Subscription"
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate_subscription')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
@ -176,6 +181,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate_Space"
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate_space')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
@ -187,6 +193,7 @@ export const VatSettingsModal: React.FC<VatSettingsModalProps> = ({ isOpen, togg
|
||||
<FormInput register={register}
|
||||
id="invoice_VAT-rate_Training"
|
||||
type='number'
|
||||
step={0.001}
|
||||
label={t('app.admin.vat_settings_modal.VAT_rate_training')}
|
||||
addOn={<ClockCounterClockwise size={24}/>}
|
||||
addOnAriaLabel={t('app.admin.vat_settings_modal.show_history')}
|
||||
|
@ -59,7 +59,8 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
spaces: isSelectAll('spaces', scope),
|
||||
externals: isSelectAll('externals', scope),
|
||||
evt: filter.evt,
|
||||
dispo: filter.dispo
|
||||
dispo: filter.dispo,
|
||||
reserved: filter.reserved
|
||||
});
|
||||
scope.machinesGroupByCategory.forEach(c => c.checked = _.every(c.machines, 'checked'));
|
||||
// remove all
|
||||
@ -319,7 +320,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
const t = $scope.trainings.filter(t => t.checked).map(t => t.id);
|
||||
const m = $scope.machines.filter(m => m.checked).map(m => m.id);
|
||||
const s = $scope.spaces.filter(s => s.checked).map(s => s.id);
|
||||
return { t, m, s, evt: $scope.filter.evt, dispo: $scope.filter.dispo };
|
||||
return { t, m, s, evt: $scope.filter.evt, dispo: $scope.filter.dispo, reserved: $scope.filter.reserved };
|
||||
};
|
||||
|
||||
const availabilitySourceUrl = () => `/api/availabilities/public?${$.param(getFilter())}`;
|
||||
|
@ -8,9 +8,16 @@
|
||||
&.clicked {
|
||||
color: var(--secondary-dark);
|
||||
}
|
||||
&.canceled {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
.slot-details {
|
||||
display: block;
|
||||
|
||||
&.canceled {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
.fab-popover {
|
||||
|
@ -132,7 +132,12 @@ class Invoice < PaymentDocument
|
||||
end
|
||||
|
||||
def main_item
|
||||
invoice_items.where(main: true).first
|
||||
main = invoice_items.where(main: true).first
|
||||
if main.nil?
|
||||
main = invoice_items.order(id: :asc).first
|
||||
main&.update(main: true)
|
||||
end
|
||||
main
|
||||
end
|
||||
|
||||
def other_items
|
||||
|
@ -169,6 +169,6 @@ class Accounting::AccountingService
|
||||
|
||||
diff = debit_sum - credit_sum
|
||||
fixable_line = lines.filter { |l| l[:line_type] == 'payment' }.last
|
||||
fixable_line.credit += diff
|
||||
fixable_line[:credit] += diff
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# From this migration, ths object_type and object_id columns in InvoiceItem won't be able to be null anymore
|
||||
# This will prevent issues while building the accounting data, and ensure data integrity
|
||||
class AddNotNullToInvoiceItemsObject < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
change_column_null :invoice_items, :object_type, false
|
||||
change_column_null :invoice_items, :object_id, false
|
||||
end
|
||||
end
|
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2022_12_27_141529) do
|
||||
ActiveRecord::Schema.define(version: 2023_01_06_081943) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "fuzzystrmatch"
|
||||
@ -331,8 +331,8 @@ ActiveRecord::Schema.define(version: 2022_12_27_141529) do
|
||||
t.text "description"
|
||||
t.integer "invoice_item_id"
|
||||
t.string "footprint"
|
||||
t.string "object_type"
|
||||
t.bigint "object_id"
|
||||
t.string "object_type", null: false
|
||||
t.bigint "object_id", null: false
|
||||
t.boolean "main"
|
||||
t.index ["invoice_id"], name: "index_invoice_items_on_invoice_id"
|
||||
t.index ["object_type", "object_id"], name: "index_invoice_items_on_object_type_and_object_id"
|
||||
|
64
lib/tasks/fablab/fix_invoice_items.rake
Normal file
64
lib/tasks/fablab/fix_invoice_items.rake
Normal file
@ -0,0 +1,64 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'integrity/archive_helper'
|
||||
|
||||
# This take will ensure data integrity for invoices_items.
|
||||
# Due a an unknown bug, some invoice items may not contains the reference to their object.
|
||||
# This task will re-associate these items with their reservation/subscription/etc
|
||||
namespace :fablab do
|
||||
desc 'Associate the invoice_items w/o object'
|
||||
task fix_invoice_items: :environment do |_task, _args|
|
||||
next unless InvoiceItem.where(object_type: nil)
|
||||
.or(InvoiceItem.where(object_id: nil))
|
||||
.count
|
||||
.positive?
|
||||
|
||||
include ActionView::Helpers::NumberHelper
|
||||
|
||||
# check the footprints and save the archives
|
||||
Integrity::ArchiveHelper.check_footprints
|
||||
ActiveRecord::Base.transaction do
|
||||
periods = Integrity::ArchiveHelper.backup_and_remove_periods
|
||||
|
||||
# fix invoice items data
|
||||
InvoiceItem.where(object_type: nil)
|
||||
.or(InvoiceItem.where(object_id: nil))
|
||||
.find_each do |ii|
|
||||
invoice = ii.invoice
|
||||
other_items = invoice.invoice_items.where.not(id: ii.id)
|
||||
puts "\e[4;33mFound an invalid InvoiceItem\e[0m"
|
||||
puts '=============================================='
|
||||
puts "Invoice #{invoice.id} (# #{invoice.reference})"
|
||||
puts "Total: #{number_to_currency(invoice.total / 100.0)}"
|
||||
puts "Customer: #{invoice.invoicing_profile.full_name} (#{invoice.invoicing_profile.email})"
|
||||
puts "Operator: #{invoice.operator_profile&.user&.profile&.full_name} (#{invoice.operator_profile&.user&.email})"
|
||||
puts "Date: #{invoice.created_at}"
|
||||
puts '=============================================='
|
||||
puts "Concerned item: #{ii.id}"
|
||||
puts "Item subject: #{ii.description}."
|
||||
other_items.find_each do |oii|
|
||||
puts '=============================================='
|
||||
puts "Other item: #{oii.description} (#{oii.id})"
|
||||
puts "Other item object: #{oii.object_type} #{oii.object_id}"
|
||||
puts "Other item slots: #{oii.object.try(:slots)&.map { |s| "#{s.start_at} - #{s.end_at}" }}"
|
||||
print "\e[1;34m[ ? ]\e[0m Associate the item with #{oii.object_type} #{oii.object_id} ? (y/N) > "
|
||||
confirm = $stdin.gets.chomp
|
||||
ii.update(object_id: oii.object_id, object_type: oii.object_type) if confirm == 'y'
|
||||
end
|
||||
ii.reload
|
||||
if ii.object_id.nil? || ii.object_type.nil?
|
||||
puts "\n\e[0;31mERROR\e[0m: InvoiceItem(#{ii.id}) was not associated with an object. Please open a rails console " \
|
||||
"to manually fix the issue using `InvoiceItem.find(#{ii.id}.update(object_id: XXX, object_type: 'XXX')`.\n"
|
||||
end
|
||||
end
|
||||
|
||||
# chain records
|
||||
puts 'Chaining all record. This may take a while...'
|
||||
InvoiceItem.order(:id).all.each(&:chain_record)
|
||||
Invoice.order(:id).all.each(&:chain_record)
|
||||
|
||||
# re-create all archives from the memory dump
|
||||
Integrity::ArchiveHelper.restore_periods(periods)
|
||||
end
|
||||
end
|
||||
end
|
@ -12,9 +12,7 @@ namespace :fablab do
|
||||
|
||||
desc 'add missing VAT rate to history'
|
||||
task :add_vat_rate, %i[rate date] => :environment do |_task, args|
|
||||
unless args.rate && args.date
|
||||
raise 'Missing argument. Usage exemple: rails fablab:setup:add_vat_rate[20,2014-01-01]. Use 0 to disable'
|
||||
end
|
||||
raise 'Missing argument. Usage exemple: rails fablab:setup:add_vat_rate[20,2014-01-01]. Use 0 to disable' unless args.rate && args.date
|
||||
|
||||
if args.rate == '0'
|
||||
setting = Setting.find_by(name: 'invoice_VAT-active')
|
||||
@ -129,7 +127,7 @@ namespace :fablab do
|
||||
|
||||
desc 'generate acconting lines'
|
||||
task build_accounting_lines: :environment do
|
||||
start_date = Invoice.order(created_at: :asc).first&.created_at
|
||||
start_date = Invoice.order(created_at: :asc).first&.created_at || DateTime.current
|
||||
end_date = DateTime.current
|
||||
AccountingLine.where(date: start_date..end_date).destroy_all
|
||||
Accounting::AccountingService.new.build(start_date&.beginning_of_day, end_date.end_of_day)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fab-manager",
|
||||
"version": "5.6.0",
|
||||
"version": "5.6.1",
|
||||
"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",
|
||||
|
@ -26,6 +26,7 @@ describe('VatSettingsModal', () => {
|
||||
expect(screen.getByLabelText(/app.admin.vat_settings_modal.enable_VAT/)).toBeChecked();
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: /app.admin.vat_settings_modal.advanced/, hidden: true }));
|
||||
expect(screen.getByLabelText(/app.admin.vat_settings_modal.VAT_rate/, { selector: '#invoice_VAT-rate' })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/app.admin.vat_settings_modal.VAT_rate_product/)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/app.admin.vat_settings_modal.VAT_rate_event/)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/app.admin.vat_settings_modal.VAT_rate_machine/)).toBeInTheDocument();
|
||||
@ -44,4 +45,14 @@ describe('VatSettingsModal', () => {
|
||||
expect(screen.getByRole('heading', { name: /app.admin.setting_history_modal.title/, hidden: true })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('input 3 decimals rate', async () => {
|
||||
render(<VatSettingsModal isOpen={true} toggleModal={toggleModal} onError={onError} onSuccess={onSuccess} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(/app.admin.vat_settings_modal.enable_VAT/)).toBeChecked();
|
||||
});
|
||||
const input = screen.getByLabelText(/app.admin.vat_settings_modal.VAT_rate/, { selector: '#invoice_VAT-rate' });
|
||||
fireEvent.change(input, { target: { value: 14.976 } });
|
||||
expect(input).toHaveValue(14.976);
|
||||
});
|
||||
});
|
||||
|
13
yarn.lock
13
yarn.lock
@ -7696,9 +7696,9 @@ json-stable-stringify-without-jsonify@^1.0.1:
|
||||
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||
|
||||
json5@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
|
||||
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
|
||||
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
@ -8059,16 +8059,11 @@ minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatc
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0:
|
||||
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
|
||||
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
|
||||
|
||||
minimist@^1.2.5, minimist@^1.2.6:
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
mkdirp@^0.5.5:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
|
||||
|
Loading…
x
Reference in New Issue
Block a user