1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-20 14:54:15 +01:00

(feat) ability to disable the store

This commit is contained in:
Sylvain 2022-09-27 11:14:27 +02:00
parent afc391f73e
commit d6fbd90c7c
12 changed files with 208 additions and 98 deletions

View File

@ -34,3 +34,5 @@ Style/AndOr:
EnforcedStyle: conditionals
Style/FormatString:
EnforcedStyle: sprintf
Style/FormatStringToken:
EnforcedStyle: template

View File

@ -1,6 +1,8 @@
# Changelog Fab-manager
- Script to download translations from Crowdin
- Fablab's store module
- [TODO DEPLOY] `rails db:seed`
## v5.4.17 2022 September 06

View File

@ -53,7 +53,7 @@ Application.Controllers.controller('MainNavController', ['$scope', 'settingsProm
linkIcon: 'tags',
class: 'reserve-event-link'
},
{
$scope.$root.modules.store && {
state: 'app.public.store',
linkText: 'app.public.common.fablab_store',
linkIcon: 'cart-plus',
@ -106,7 +106,7 @@ Application.Controllers.controller('MainNavController', ['$scope', 'settingsProm
linkIcon: 'tags',
authorizedRoles: ['admin', 'manager']
},
{
$scope.$root.modules.store && {
state: 'app.admin.store.products',
linkText: 'app.public.common.manage_the_store',
linkIcon: 'cart-plus',

View File

@ -129,7 +129,8 @@ export const modulesSettings = [
'machines_module',
'online_payment_module',
'public_agenda_module',
'invoicing_module'
'invoicing_module',
'store_module'
] as const;
export const stripeSettings = [

View File

@ -27,7 +27,7 @@ angular.module('application.router', ['ui.router'])
logoFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-file' }).$promise; }],
logoBlackFile: ['CustomAsset', function (CustomAsset) { return CustomAsset.get({ name: 'logo-black-file' }).$promise; }],
sharedTranslations: ['Translations', function (Translations) { return Translations.query(['app.shared', 'app.public.common']).$promise; }],
modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['machines_module', 'spaces_module', 'plans_module', 'invoicing_module', 'wallet_module', 'statistics_module', 'trainings_module', 'public_agenda_module']" }).$promise; }],
modulesPromise: ['Setting', function (Setting) { return Setting.query({ names: "['machines_module', 'spaces_module', 'plans_module', 'invoicing_module', 'wallet_module', 'statistics_module', 'trainings_module', 'public_agenda_module', 'store_module']" }).$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['public_registrations']" }).$promise; }]
},
onEnter: ['$rootScope', 'logoFile', 'logoBlackFile', 'modulesPromise', 'CSRF', function ($rootScope, logoFile, logoBlackFile, modulesPromise, CSRF) {
@ -41,6 +41,7 @@ angular.module('application.router', ['ui.router'])
spaces: (modulesPromise.spaces_module === 'true'),
plans: (modulesPromise.plans_module === 'true'),
trainings: (modulesPromise.trainings_module === 'true'),
store: (modulesPromise.store_module === 'true'),
invoicing: (modulesPromise.invoicing_module === 'true'),
wallet: (modulesPromise.wallet_module === 'true'),
publicAgenda: (modulesPromise.public_agenda_module === 'true'),
@ -346,6 +347,7 @@ angular.module('application.router', ['ui.router'])
// machines
.state('app.public.machines_list', {
url: '/machines',
abstract: !Fablab.machinesModule,
views: {
'main@': {
templateUrl: '/machines/index.html',
@ -359,6 +361,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.admin.machines_new', {
url: '/machines/new',
abstract: !Fablab.machinesModule,
views: {
'main@': {
templateUrl: '/machines/new.html',
@ -368,6 +371,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.public.machines_show', {
url: '/machines/:id',
abstract: !Fablab.machinesModule,
views: {
'main@': {
templateUrl: '/machines/show.html',
@ -380,6 +384,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.logged.machines_reserve', {
url: '/machines/:id/reserve',
abstract: !Fablab.machinesModule,
views: {
'main@': {
templateUrl: '/machines/reserve.html',
@ -401,6 +406,7 @@ angular.module('application.router', ['ui.router'])
})
.state('app.admin.machines_edit', {
url: '/machines/:id/edit',
abstract: !Fablab.machinesModule,
views: {
'main@': {
templateUrl: '/machines/edit.html',
@ -621,6 +627,7 @@ angular.module('application.router', ['ui.router'])
// store
.state('app.public.store', {
url: '/store/:categoryTypeUrl/:category?{machines:string}{keywords:string}{is_active:string}{page:string}{sort:string}',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/store/index.html',
@ -641,6 +648,7 @@ angular.module('application.router', ['ui.router'])
// show product
.state('app.public.product_show', {
url: '/store/p/:slug',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/products/show.html',
@ -652,6 +660,7 @@ angular.module('application.router', ['ui.router'])
// cart
.state('app.public.store_cart', {
url: '/store/cart',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/cart/index.html',
@ -934,6 +943,7 @@ angular.module('application.router', ['ui.router'])
// show order
.state('app.admin.order_show', {
url: '/admin/store/orders/:id',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/orders/show.html',
@ -1164,7 +1174,8 @@ angular.module('application.router', ['ui.router'])
"'link_name', 'home_content', 'home_css', 'phone_required', 'upcoming_events_shown', 'public_agenda_module'," +
"'renew_pack_threshold', 'pack_only_for_subscription', 'overlapping_categories', 'public_registrations'," +
"'extended_prices_in_same_day', 'recaptcha_site_key', 'recaptcha_secret_key', 'user_validation_required', " +
"'user_validation_required_list', 'machines_module', 'user_change_group', 'show_username_in_admin_list']"
"'user_validation_required_list', 'machines_module', 'user_change_group', 'show_username_in_admin_list', " +
"'store_module']"
}).$promise;
}],
privacyDraftsPromise: ['Setting', function (Setting) { return Setting.get({ name: 'privacy_draft', history: true }).$promise; }],
@ -1182,6 +1193,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin.store.settings', {
url: '/settings',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/store/index.html',
@ -1192,6 +1204,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin.store.products', {
url: '/products?{categories:string}{machines:string}{keywords:string}{stock_type:string}{stock_from:string}{stock_to:string}{is_active:string}{page:string}{sort:string}',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/store/index.html',
@ -1213,6 +1226,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin.store.products_new', {
url: '/products/new',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/store/product_new.html',
@ -1223,6 +1237,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin.store.products_edit', {
url: '/products/:id/edit',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/store/product_edit.html',
@ -1233,6 +1248,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin.store.categories', {
url: '/categories',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/store/index.html',
@ -1243,6 +1259,7 @@ angular.module('application.router', ['ui.router'])
.state('app.admin.store.orders', {
url: '/orders',
abstract: !Fablab.storeModule,
views: {
'main@': {
templateUrl: '/admin/store/index.html',

View File

@ -452,6 +452,15 @@
on-error="onError"
class-name="'m-l'"></boolean-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.store' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.store_info_html' | translate"></p>
<boolean-setting name="'store_module'"
label="'app.admin.settings.enable_store' | translate"
on-success="onSuccess"
on-error="onError"
class-name="'m-l'"></boolean-setting>
</div>
<div class="row">
<h3 class="m-l" translate>{{ 'app.admin.settings.invoicing' }}</h3>
<p class="alert alert-warning m-h-md" ng-bind-html="'app.admin.settings.invoicing_info_html' | translate"></p>

View File

@ -1,11 +1,12 @@
# frozen_string_literal: true
# Setting is a configuration element of the platform. Only administrators are allowed to modify Settings
# For some settings, changing them will involve some callback actions (like rebuilding the stylesheets if the theme color Setting is changed).
# For some settings, changing them will involve some callback actions (like rebuilding the stylesheets
# if the theme color Setting has changed).
# A full history of the previous values is kept in database with the date and the author of the change
# after_update callback is handled by SettingService
class Setting < ApplicationRecord
has_many :history_values
has_many :history_values, dependent: :destroy
# The following list contains all the settings that can be customized from the Fab-manager's UI.
# A few of them that are system settings, that should not be updated manually (uuid, origin...).
validates :name, inclusion:
@ -151,7 +152,8 @@ class Setting < ApplicationRecord
user_change_group
user_validation_required
user_validation_required_list
show_username_in_admin_list] }
show_username_in_admin_list
store_module] }
# 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
@ -188,6 +190,7 @@ class Setting < ApplicationRecord
previous_value&.created_at
end
# @deprecated, prefer Setting.set() instead
def value=(val)
admin = User.admins.first
save && history_values.create(invoicing_profile: admin.invoicing_profile, value: val)
@ -223,6 +226,6 @@ class Setting < ApplicationRecord
# Check if the given setting was set
##
def self.set?(name)
find_by(name: name)&.value.nil? ? false : true
!find_by(name: name)&.value.nil?
end
end

View File

@ -42,7 +42,7 @@ class SettingPolicy < ApplicationPolicy
payment_gateway payzen_endpoint payzen_public_key public_agenda_module renew_pack_threshold statistics_module
pack_only_for_subscription overlapping_categories public_registrations facebook twitter viadeo linkedin instagram
youtube vimeo dailymotion github echosciences pinterest lastfm flickr machines_module user_change_group
user_validation_required user_validation_required_list]
user_validation_required user_validation_required_list store_module]
end
##
@ -50,7 +50,7 @@ class SettingPolicy < ApplicationPolicy
# This blacklist is automatically generated from the public_whitelist above.
##
def self.public_blacklist
Setting.validators.detect { |v| v.class == ActiveModel::Validations::InclusionValidator && v.attributes.include?(:name) }
Setting.validators.detect { |v| v.instance_of?(ActiveModel::Validations::InclusionValidator) && v.attributes.include?(:name) }
.options[:in] - SettingPolicy.public_whitelist
end
end

View File

@ -36,6 +36,7 @@
Fablab.plansModule = ('<%= Setting.get('plans_module') %>' === 'true');
Fablab.spacesModule = ('<%= Setting.get('spaces_module') %>' === 'true');
Fablab.trainingsModule = ('<%= Setting.get('trainings_module') %>' === 'true');
Fablab.storeModule = ('<%= Setting.get('store_module') %>' === 'true');
Fablab.walletModule = ('<%= Setting.get('wallet_module') %>' === 'true');
Fablab.publicAgendaModule = ('<%= Setting.get('public_agenda_module') %>' === 'true');
Fablab.statisticsModule = ('<%= Setting.get('statistics_module') %>' === 'true');

View File

@ -1427,6 +1427,10 @@ en:
trainings_info_html: "<p>Trainings are fully integrated Fab-manager's agenda. If enabled, your members will be able to book and pay trainings.</p><p>Trainings provides a way to prevent members to book some machines, if they do have not taken the prerequisite course.</p>"
enable_trainings: "Enable the trainings"
trainings_module: "trainings module"
store: "Store"
store_info_html: "You can enable the store module that provides an easy way to <strong>sell various products and consumables</strong> to your members. This module also allows you to <strong>manage stocks</strong> and track orders."
enable_store: "Enable the store"
store_module: "store module"
invoicing: "Invoicing"
invoicing_info_html: "<p>You can fully disable the invoicing module.</p><p>This is useful if you have your own invoicing system, and you don't want Fab-manager generates and sends invoices to the members.</p><p><strong>Warning:</strong> even if you disable the invoicing module, you must to configure the VAT to prevent errors in accounting and prices. Do it from the « Invoices > Invoicing settings » section.</p>"
enable_invoicing: "Enable invoicing"

View File

@ -600,3 +600,4 @@ en:
machines_module: "Machines module"
user_change_group: "Allow users to change their group"
show_username_in_admin_list: "Show the username in the admin's members list"
store_module: "Store module"

View File

@ -1,3 +1,8 @@
# frozen_string_literal: true
# This file fills the database with some initial data.
# Some of them are just some placeholders to prevent having an empty palce when starting fab-manager first.
# Other data are required default values, for various settings.
if StatisticIndex.count.zero?
StatisticIndex.create!([
@ -86,7 +91,7 @@ Group.create! name: I18n.t('group.admins'), slug: 'admins' unless Group.find_by(
# Create the default admin if none exists yet
if Role.where(name: 'admin').joins(:users).count.zero?
admin = User.new(username: 'admin', email: ENV['ADMIN_EMAIL'], password: ENV['ADMIN_PASSWORD'],
admin = User.new(username: 'admin', email: ENV.fetch('ADMIN_EMAIL'), password: ENV.fetch('ADMIN_PASSWORD'),
password_confirmation: Rails.application.secrets.admin_password, group_id: Group.find_by(slug: 'admins').id,
profile_attributes: { first_name: 'admin', last_name: 'admin', phone: '0123456789' },
statistic_profile_attributes: { gender: true, birthday: Date.current })
@ -111,27 +116,48 @@ end
if Licence.count.zero?
Licence.create!([
{ name: 'Attribution (BY)', description: 'Le titulaire des droits autorise toute exploitation de lœuvre, y compris à' \
' des fins commerciales, ainsi que la création dœuvres dérivées, dont la distribution est également autorisé sans ' \
'restriction, à condition de lattribuer à son lauteur en citant son nom. Cette licence est recommandée pour la ' \
'diffusion et lutilisation maximale des œuvres.' },
{ name: 'Attribution + Pas de modification (BY ND)', description: 'Le titulaire des droits autorise toute utilisation' \
' de lœuvre originale (y compris à des fins commerciales), mais nautorise pas la création dœuvres dérivées.' },
{ name: "Attribution + Pas d'Utilisation Commerciale + Pas de Modification (BY NC ND)", description: 'Le titulaire ' \
'des droits autorise lutilisation de lœuvre originale à des fins non commerciales, mais nautorise pas la ' \
'création dœuvres dérivés.' },
{ name: "Attribution + Pas d'Utilisation Commerciale (BY NC)", description: 'Le titulaire des droits autorise ' \
'lexploitation de lœuvre, ainsi que la création dœuvres dérivées, à condition quil ne sagisse pas dune ' \
'utilisation commerciale (les utilisations commerciales restant soumises à son autorisation).' },
{ name: "Attribution + Pas d'Utilisation Commerciale + Partage dans les mêmes conditions (BY NC SA)", description:
'Le titulaire des droits autorise lexploitation de lœuvre originale à des fins non commerciales, ainsi que la ' \
'création dœuvres dérivées, à condition quelles soient distribuées sous une licence identique à celle qui régit ' \
'lœuvre originale.' },
{ name: 'Attribution + Partage dans les mêmes conditions (BY SA)', description: 'Le titulaire des droits autorise ' \
'toute utilisation de lœuvre originale (y compris à des fins commerciales) ainsi que la création dœuvres dérivées' \
', à condition quelles soient distribuées sous une licence identique à celle qui régit lœuvre originale. Cette' \
'licence est souvent comparée aux licences « copyleft » des logiciels libres. Cest la licence utilisée par ' \
'Wikipedia.' }
{
name: 'Attribution (BY)',
description:
'Le titulaire des droits autorise toute exploitation de lœuvre, y compris à des ' \
'fins commerciales, ainsi que la création dœuvres dérivées, dont la distribution est également autorisé sans ' \
'restriction, à condition de lattribuer à son lauteur en citant son nom. Cette licence est recommandée pour la ' \
'diffusion et lutilisation maximale des œuvres.'
},
{
name: 'Attribution + Pas de modification (BY ND)',
description:
'Le titulaire des droits autorise toute utilisation de lœuvre originale (y compris à des fins commerciales), ' \
'mais nautorise pas la création dœuvres dérivées.'
},
{
name: "Attribution + Pas d'Utilisation Commerciale + Pas de Modification (BY NC ND)",
description:
'Le titulaire des droits autorise lutilisation de lœuvre originale à des fins non commerciales, ' \
'mais nautorise pas la création dœuvres dérivés.'
},
{
name: "Attribution + Pas d'Utilisation Commerciale (BY NC)",
description:
'Le titulaire des droits autorise lexploitation de lœuvre, ainsi que la création dœuvres dérivées, ' \
'à condition quil ne sagisse pas dune utilisation commerciale (les utilisations commerciales ' \
'restant soumises à son autorisation).'
},
{
name: "Attribution + Pas d'Utilisation Commerciale + Partage dans les mêmes conditions (BY NC SA)",
description:
'Le titulaire des droits autorise lexploitation de lœuvre originale à des fins non commerciales, ainsi que la ' \
'création dœuvres dérivées, à condition quelles soient distribuées sous une licence identique à celle qui ' \
'régit lœuvre originale.'
},
{
name: 'Attribution + Partage dans les mêmes conditions (BY SA)',
description:
'Le titulaire des droits autorise toute utilisation de lœuvre originale (y compris à des fins commerciales) ' \
'ainsi que la création dœuvres dérivées, à condition quelles soient distribuées sous une licence identique ' \
'à celle qui régit lœuvre originale. Cette licence est souvent comparée aux licences « copyleft » des logiciels ' \
'libres. Cest la licence utilisée par Wikipedia.'
}
])
end
@ -149,75 +175,117 @@ end
if Training.count.zero?
Training.create!([
{ name: 'Formation Imprimante 3D', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' \
'eiusmod tempor incididunt ut labore et dolore magna aliqua.' },
{ name: 'Formation Laser / Vinyle', description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris' \
' nisi ut aliquip ex ea commodo consequat.' },
{ name: 'Formation Petite fraiseuse numerique', description: 'Duis aute irure dolor in reprehenderit in voluptate ' \
'velit esse cillum dolore eu fugiat nulla pariatur.' },
{ name: 'Formation Shopbot Grande Fraiseuse', description: 'Excepteur sint occaecat cupidatat non proident, sunt in ' \
'culpa qui officia deserunt mollit anim id est laborum.' },
{ name: 'Formation logiciel 2D', description: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' \
'accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi ' \
'architecto beatae vitae dicta sunt explicabo.' }
{
name: 'Formation Imprimante 3D',
description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ' \
'eiusmod tempor incididunt ut labore et dolore magna aliqua.'
},
{
name: 'Formation Laser / Vinyle',
description: 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris ' \
'nisi ut aliquip ex ea commodo consequat.'
},
{
name: 'Formation Petite fraiseuse numerique',
description: 'Duis aute irure dolor in reprehenderit in voluptate ' \
'velit esse cillum dolore eu fugiat nulla pariatur.'
},
{
name: 'Formation Shopbot Grande Fraiseuse',
description: 'Excepteur sint occaecat cupidatat non proident, sunt in ' \
'culpa qui officia deserunt mollit anim id est laborum.'
},
{
name: 'Formation logiciel 2D',
description: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem ' \
'accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis ' \
'et quasi architecto beatae vitae dicta sunt explicabo.'
}
])
TrainingsPricing.all.each do |p|
p.update_columns(amount: (rand * 50 + 5).floor * 100)
p.update(amount: ((rand * 50) + 5).floor * 100)
end
end
if Machine.count.zero?
Machine.create!([
{ name: 'Découpeuse laser', description: "Préparation à l'utilisation de l'EPILOG Legend 36EXT\r\nInformations" \
" générales \r\n Pour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf" \
" avec des \"lignes de coupe\" d'une épaisseur inférieur à 0,01 mm et la machine s'occupera du reste!\r\n La " \
'gravure est basée sur le spectre noir et blanc. Les nuances sont obtenues par différentes profondeurs de gravure ' \
"correspondant aux niveaux de gris de votre image. Il suffit pour cela d'apporter une image scannée ou un fichier " \
"photo en noir et blanc pour pouvoir reproduire celle-ci sur votre support! \r\nQuels types de matériaux pouvons " \
"nous graver/découper?\r\n Du bois au tissu, du plexiglass au cuir, cette machine permet de découper et graver " \
"la plupart des matériaux sauf les métaux. La gravure est néanmoins possible sur les métaux recouverts d'une couche" \
" de peinture ou les aluminiums anodisés. \r\n Concernant l'épaisseur des matériaux découpés, il est " \
"préférable de ne pas dépasser 5 mm pour le bois et 6 mm pour le plexiglass.\r\n", spec: "Puissance: 40W\r\nSurface" \
" de travail: 914x609 mm \r\nEpaisseur maximale de la matière: 305mm\r\nSource laser: tube laser type CO2\r\n" \
'Contrôles de vitesse et de puissance: ces deux paramètres sont ajustables en fonction du matériau (de 1% à 100%) .' \
"\r\n", slug: 'decoupeuse-laser' },
{ name: 'Découpeuse vinyle', description: "Préparation à l'utilisation de la Roland CAMM-1 GX24\r\nInformations " \
"générales \r\n Envie de réaliser un tee shirt personnalisé ? Un sticker à l'effigie votre groupe " \
"préféré ? Un masque pour la réalisation d'un circuit imprimé? Pour cela, il suffit simplement de venir avec votre" \
" fichier vectorisé (ne pas oublier de vectoriser les textes) type illustrator svg ou dxf.\r\n \r\nMatériaux " \
"utilisés:\r\n Cette machine permet de découper principalement du vinyle,vinyle réfléchissant, flex.\r\n",
{
name: 'Découpeuse laser',
description:
"Préparation à l'utilisation de l'EPILOG Legend 36EXT\r\nInformations générales \r\n " \
"Pour la découpe, il suffit d'apporter votre fichier vectorisé type illustrator, svg ou dxf avec des " \
"\"lignes de coupe\" d'une épaisseur inférieur à 0,01 mm et la machine s'occupera du reste!\r\n La gravure " \
'est basée sur le spectre noir et blanc. Les nuances sont obtenues par différentes profondeurs de gravure ' \
"correspondant aux niveaux de gris de votre image. Il suffit pour cela d'apporter une image scannée ou un " \
"fichier photo en noir et blanc pour pouvoir reproduire celle-ci sur votre support! \r\nQuels types de " \
"matériaux pouvons nous graver/découper?\r\n Du bois au tissu, du plexiglass au cuir, cette machine " \
'permet de découper et graver la plupart des matériaux sauf les métaux. La gravure est néanmoins possible ' \
"sur les métaux recouverts d'une couche de peinture ou les aluminiums anodisés. \r\n " \
"Concernant l'épaisseur des matériaux découpés, il est préférable de ne pas dépasser 5 mm pour le bois " \
"et 6 mm pour le plexiglass.\r\n",
spec:
"Puissance: 40W\r\nSurface de travail: 914x609 mm \r\n" \
"Epaisseur maximale de la matière: 305mm\r\nSource laser: tube laser type CO2\r\nContrôles de vitesse " \
"et de puissance: ces deux paramètres sont ajustables en fonction du matériau (de 1% à 100%).\r\n",
slug: 'decoupeuse-laser'
},
{
name: 'Découpeuse vinyle',
description:
"Préparation à l'utilisation de la Roland CAMM-1 GX24\r\nInformations générales \r\n " \
"Envie de réaliser un tee shirt personnalisé ? Un sticker à l'effigie votre groupe préféré ? " \
"Un masque pour la réalisation d'un circuit imprimé? Pour cela, il suffit simplement de venir avec votre " \
"fichier vectorisé (ne pas oublier de vectoriser les textes) type illustrator svg ou dxf.\r\n \r\nMatériaux " \
"utilisés:\r\n Cette machine permet de découper principalement du vinyle,vinyle réfléchissant, flex.\r\n",
spec: "Largeurs de support acceptées: de 50 mm à 700 mm\r\nVitesse de découpe: 50 cm/sec\r\nRésolution mécanique: " \
"0,0125 mm/pas\r\n", slug: 'decoupeuse-vinyle' },
{ name: 'Shopbot / Grande fraiseuse', description: "La fraiseuse numérique ShopBot PRS standard\r\nInformations " \
"générales\r\nCette machine est un fraiseuse 3 axes idéale pour l'usinage de pièces de grandes dimensions. De la " \
"réalisation d'une chaise ou d'un meuble jusqu'à la construction d'une maison ou d'un assemblage immense, le " \
"ShopBot ouvre de nombreuses portes à votre imagination! \r\nMatériaux usinables\r\nLes principaux matériaux " \
"usinables sont le bois, le plastique, le laiton et bien d'autres.\r\nCette machine n'usine pas les métaux.\r\n",
"0,0125 mm/pas\r\n",
slug: 'decoupeuse-vinyle'
},
{
name: 'Shopbot / Grande fraiseuse',
description:
"La fraiseuse numérique ShopBot PRS standard\r\nInformations " \
"générales\r\nCette machine est un fraiseuse 3 axes idéale pour l'usinage de pièces de grandes dimensions. De la " \
"réalisation d'une chaise ou d'un meuble jusqu'à la construction d'une maison ou d'un assemblage immense, le " \
"ShopBot ouvre de nombreuses portes à votre imagination! \r\nMatériaux usinables\r\nLes principaux matériaux " \
"usinables sont le bois, le plastique, le laiton et bien d'autres.\r\nCette machine n'usine pas les métaux.\r\n",
spec: "Surface maximale de travail: 2440x1220x150 (Z) mm\r\nLogiciel utilisé: Partworks 2D & 3D\r\nRésolution " \
"mécanique: 0,015 mm\r\nPrécision de la position: +/- 0,127mm\r\nFormats acceptés: DXF, STL \r\n",
slug: 'shopbot-grande-fraiseuse' },
{ name: 'Imprimante 3D', description: "L'utimaker est une imprimante 3D low cost utilisant une technologie FFF " \
"(Fused Filament Fabrication) avec extrusion thermoplastique.\r\nC'est une machine idéale pour réaliser rapidement " \
"des prototypes 3D dans des couleurs différentes.\r\n", spec: "Surface maximale de travail: 210x210x220mm \r\n" \
"Résolution méchanique: 0,02 mm \r\nPrécision de position: +/- 0,05 \r\nLogiciel utilisé: Cura\r\nFormats de " \
"fichier acceptés: STL \r\nMatériaux utilisés: PLA (en stock).", slug: 'imprimante-3d' },
{ name: 'Petite Fraiseuse', description: "La fraiseuse numérique Roland Modela MDX-20\r\nInformations générales" \
"\r\nCette machine est utilisée pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner" \
' des circuits imprimés et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm' \
") induit que certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être " \
"laissée en autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\nMatériaux usinables:" \
"\r\nLes principaux matériaux usinables sont le bois, plâtre, résine, cire usinable, cuivre.\r\n",
"mécanique: 0,015 mm\r\nPrécision de la position: +/- 0,127mm\r\nFormats acceptés: DXF, STL \r\n",
slug: 'shopbot-grande-fraiseuse'
},
{
name: 'Imprimante 3D',
description:
"L'utimaker est une imprimante 3D low cost utilisant une technologie FFF " \
"(Fused Filament Fabrication) avec extrusion thermoplastique.\r\nC'est une machine idéale pour réaliser " \
"rapidement des prototypes 3D dans des couleurs différentes.\r\n",
spec: "Surface maximale de travail: 210x210x220mm \r\n" \
"Résolution méchanique: 0,02 mm \r\nPrécision de position: +/- 0,05 \r\nLogiciel utilisé: Cura\r\nFormats de " \
"fichier acceptés: STL \r\nMatériaux utilisés: PLA (en stock).",
slug: 'imprimante-3d'
},
{
name: 'Petite Fraiseuse',
description:
"La fraiseuse numérique Roland Modela MDX-20\r\nInformations générales\r\nCette machine est utilisée " \
"pour l'usinage et le scannage 3D de précision. Elle permet principalement d'usiner des circuits imprimés " \
'et des moules de petite taille. Le faible diamètre des fraises utilisées (Ø 0,3 mm à Ø 6mm) induit que ' \
"certains temps d'usinages peuvent êtres long (> 12h), c'est pourquoi cette fraiseuse peut être laissée en " \
"autonomie toute une nuit afin d'obtenir le plus précis des usinages au FabLab.\r\nMatériaux usinables:" \
"\r\nLes principaux matériaux usinables sont le bois, plâtre, résine, cire usinable, cuivre.\r\n",
spec: "Taille du plateau X/Y : 220 mm x 160 mm\r\nVolume maximal de travail: 203,2 mm (X), 152,4 mm (Y), 60,5 mm " \
"(Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm (axe Z)" \
"\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: Roland Modela player" \
" 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\nFormats acceptés: STL,PNG 3D\r\n" \
"Format d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, Grayscale, Point Group et BMP\r\n",
slug: 'petite-fraiseuse' }
"(Z)\r\nPrécision usinage: 0,00625 mm\r\nPrécision scannage: réglable de 0,05 à 5 mm (axes X,Y) et 0,025 mm " \
"(axe Z)\r\nVitesse d'analyse (scannage): 4-15 mm/sec\r\n \r\n \r\nLogiciel utilisé pour le fraisage: " \
"Roland Modela player 4 \r\nLogiciel utilisé pour l'usinage de circuits imprimés: Cad.py (linux)\r\n" \
"Formats acceptés: STL,PNG 3D\r\nFormat d'exportation des données scannées: DXF, VRML, STL, 3DMF, IGES, " \
"Grayscale, Point Group et BMP\r\n",
slug: 'petite-fraiseuse'
}
])
Price.all.each do |p|
p.update_columns(amount: (rand * 50 + 5).floor * 100)
p.update(amount: ((rand * 50) + 5).floor * 100)
end
end
@ -273,19 +341,19 @@ Setting.set('twitter_name', 'Fab_Manager') unless Setting.find_by(name: 'twitter
unless Setting.find_by(name: 'machine_explications_alert').try(:value)
setting = Setting.find_or_initialize_by(name: 'machine_explications_alert')
setting.value = 'Tout achat de créneau machine est définitif. Aucune' \
' annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez en' \
" modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais," \
' aucun changement ne pourra être effectué.'
setting.value = 'Tout achat de créneau machine est définitif. Aucune ' \
'annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez en ' \
"modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais, " \
'aucun changement ne pourra être effectué.'
setting.save
end
unless Setting.find_by(name: 'training_explications_alert').try(:value)
setting = Setting.find_or_initialize_by(name: 'training_explications_alert')
setting.value = 'Toute réservation de formation est définitive.' \
' Aucune annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez' \
" en modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais," \
' aucun changement ne pourra être effectué.'
setting.value = 'Toute réservation de formation est définitive. ' \
'Aucune annulation ne pourra être effectuée, néanmoins au plus tard 24h avant le créneau fixé, vous pouvez ' \
"en modifier la date et l'horaire à votre convenance et en fonction du calendrier proposé. Passé ce délais, " \
'aucun changement ne pourra être effectué.'
setting.save
end
@ -633,8 +701,8 @@ Stylesheet.build_home!
unless Setting.find_by(name: 'training_information_message').try(:value)
setting = Setting.find_or_initialize_by(name: 'training_information_message')
setting.value = "Avant de réserver une formation, nous vous conseillons de consulter nos offres d'abonnement qui" \
' proposent des conditions avantageuses sur le prix des formations et les créneaux machines.'
setting.value = "Avant de réserver une formation, nous vous conseillons de consulter nos offres d'abonnement qui " \
'proposent des conditions avantageuses sur le prix des formations et les créneaux machines.'
setting.save
end
@ -919,6 +987,8 @@ Setting.set('extended_prices_in_same_day', false) unless Setting.find_by(name: '
Setting.set('show_username_in_admin_list', false) unless Setting.find_by(name: 'show_username_in_admin_list').try(:value)
Setting.set('store_module', false) unless Setting.find_by(name: 'store_module').try(:value)
if StatisticCustomAggregation.count.zero?
# available reservations hours for machines
machine_hours = StatisticType.find_by(key: 'hour', statistic_index_id: 2)