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

(merge) merge pre_inscription

This commit is contained in:
Du Peng 2023-07-04 15:16:57 +02:00
commit cf132cf5cf
93 changed files with 1453 additions and 293 deletions

View File

@ -1,5 +1,20 @@
# Changelog Fab-manager # Changelog Fab-manager
## v6.0.8 2023 July 03
- Improved projects list filter
- Fix a bug: unable to refresh machine/space/training calender after pay an reservation
- Fix a bug: Accouning Line in duplicate
- Fix a bug: displays "my orders" link only if store module is active
- [TODO DEPLOY] `rails fablab:setup:build_accounting_lines`
## v6.0.7 2023 June 20
- Fix a bug: OpenAPI accounting gateway_object_id missing error
- Fix a bug: unable to modify the price of prepaid pack
- Fix a bug: notification type missing
- Fix critical bug: Incorrect amount calculation when paying monthly subcription with a wallet for PayZen
## v6.0.6 2023 May 4 ## v6.0.6 2023 May 4
- Fix a bug: invalid duration for machine/spaces reservations in statistics, when using slots of not 1 hour - Fix a bug: invalid duration for machine/spaces reservations in statistics, when using slots of not 1 hour

View File

@ -30,6 +30,7 @@ group :development, :test do
# comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging # comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging
# gem 'byebug' # gem 'byebug'
gem 'dotenv-rails' gem 'dotenv-rails'
gem 'pry'
end end
group :development do group :development do
@ -43,7 +44,6 @@ group :development do
# Preview mail in the browser # Preview mail in the browser
gem 'listen', '~> 3.0.5' gem 'listen', '~> 3.0.5'
gem 'overcommit' gem 'overcommit'
gem 'pry'
gem 'rb-readline' gem 'rb-readline'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'railroady' gem 'railroady'
@ -149,3 +149,5 @@ gem 'acts_as_list'
# Error reporting # Error reporting
gem 'sentry-rails' gem 'sentry-rails'
gem 'sentry-ruby' gem 'sentry-ruby'
gem "reverse_markdown"

View File

@ -82,7 +82,7 @@ GEM
rails (>= 4.1) rails (>= 4.1)
ast (2.4.2) ast (2.4.2)
attr_required (1.0.1) attr_required (1.0.1)
awesome_print (1.8.0) awesome_print (1.9.2)
axiom-types (0.1.1) axiom-types (0.1.1)
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
@ -398,6 +398,8 @@ GEM
responders (3.1.0) responders (3.1.0)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
reverse_markdown (2.1.1)
nokogiri
rexml (3.2.5) rexml (3.2.5)
rolify (5.3.0) rolify (5.3.0)
rubocop (1.31.2) rubocop (1.31.2)
@ -590,6 +592,7 @@ DEPENDENCIES
redis-session-store redis-session-store
repost repost
responders (~> 3.0) responders (~> 3.0)
reverse_markdown
rolify rolify
rubocop (~> 1.31) rubocop (~> 1.31)
rubocop-rails rubocop-rails

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
# API Controller for resources of type ProjectCategory
class API::ProjectCategoriesController < ApplicationController
before_action :set_project_category, only: %i[update destroy]
before_action :authenticate_user!, only: %i[create update destroy]
def index
@project_categories = ProjectCategory.all
end
def create
authorize ProjectCategory
@project_category = ProjectCategory.new(project_category_params)
if @project_category.save
render json: @project_category, status: :created
else
render json: @project_category.errors, status: :unprocessable_entity
end
end
def update
authorize ProjectCategory
if @project_category.update(project_category_params)
render json: @project_category, status: :ok
else
render json: @project_category.errors, status: :unprocessable_entity
end
end
def destroy
authorize ProjectCategory
@project_category.destroy
head :no_content
end
private
def set_project_category
@project_category = ProjectCategory.find(params[:id])
end
def project_category_params
params.require(:project_category).permit(:name)
end
end

View File

@ -18,6 +18,12 @@ class API::ProjectsController < API::APIController
@project = Project.friendly.find(params[:id]) @project = Project.friendly.find(params[:id])
end end
def markdown
@project = Project.friendly.find(params[:id])
authorize @project
send_data ProjectToMarkdown.new(@project).call, filename: "#{@project.name.parameterize}-#{@project.id}.md", disposition: 'attachment', type: 'text/markdown'
end
def create def create
@project = Project.new(project_params.merge(author_statistic_profile_id: current_user.statistic_profile.id)) @project = Project.new(project_params.merge(author_statistic_profile_id: current_user.statistic_profile.id))
if @project.save if @project.save
@ -53,12 +59,23 @@ class API::ProjectsController < API::APIController
def search def search
service = ProjectService.new service = ProjectService.new
res = service.search(params, current_user) paginate = request.format.zip? ? false : true
res = service.search(params, current_user, paginate: paginate)
render json: res, status: :unprocessable_entity and return if res[:error] render json: res, status: :unprocessable_entity and return if res[:error]
@total = res[:total] respond_to do |format|
@projects = res[:projects] format.json do
render :index @total = res[:total]
@projects = res[:projects]
render :index
end
format.zip do
head :forbidden unless current_user.admin? || current_user.manager?
send_data ProjectsArchive.new(res[:projects]).call, filename: "projets.zip", disposition: 'attachment', type: 'application/zip'
end
end
end end
private private
@ -69,7 +86,7 @@ class API::ProjectsController < API::APIController
def project_params def project_params
params.require(:project).permit(:name, :description, :tags, :machine_ids, :component_ids, :theme_ids, :licence_id, :status_id, :state, params.require(:project).permit(:name, :description, :tags, :machine_ids, :component_ids, :theme_ids, :licence_id, :status_id, :state,
user_ids: [], machine_ids: [], component_ids: [], theme_ids: [], user_ids: [], machine_ids: [], component_ids: [], theme_ids: [], project_category_ids: [],
project_image_attributes: [:attachment], project_image_attributes: [:attachment],
project_caos_attributes: %i[id attachment _destroy], project_caos_attributes: %i[id attachment _destroy],
project_steps_attributes: [ project_steps_attributes: [

View File

@ -0,0 +1,25 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProjectCategory } from '../models/project-category';
export default class ProjectCategoryAPI {
static async index (): Promise<Array<ProjectCategory>> {
const res: AxiosResponse<Array<ProjectCategory>> = await apiClient.get('/api/project_categories');
return res?.data;
}
static async create (newProjectCategory: ProjectCategory): Promise<ProjectCategory> {
const res: AxiosResponse<ProjectCategory> = await apiClient.post('/api/project_categories', { project_category: newProjectCategory });
return res?.data;
}
static async update (updatedProjectCategory: ProjectCategory): Promise<ProjectCategory> {
const res: AxiosResponse<ProjectCategory> = await apiClient.patch(`/api/project_categories/${updatedProjectCategory.id}`, { project_category: updatedProjectCategory });
return res?.data;
}
static async destroy (projectCategoryId: number): Promise<void> {
const res: AxiosResponse<void> = await apiClient.delete(`/api/project_categories/${projectCategoryId}`);
return res?.data;
}
}

View File

@ -115,17 +115,17 @@ export const ConfigurePacksButton: React.FC<ConfigurePacksButtonProps> = ({ pack
itemId={p.id} itemId={p.id}
itemType={t('app.admin.configure_packs_button.pack')} itemType={t('app.admin.configure_packs_button.pack')}
destroy={PrepaidPackAPI.destroy}/> destroy={PrepaidPackAPI.destroy}/>
<FabModal isOpen={isOpen}
toggleModal={toggleModal}
title={t('app.admin.configure_packs_button.edit_pack')}
className="edit-pack-modal"
closeButton
confirmButton={t('app.admin.configure_packs_button.confirm_changes')}
onConfirmSendFormId="edit-pack">
{packData && <PackForm formId="edit-pack" onSubmit={handleUpdate} pack={packData} />}
</FabModal>
</li>)} </li>)}
</ul> </ul>
<FabModal isOpen={isOpen}
toggleModal={toggleModal}
title={t('app.admin.configure_packs_button.edit_pack')}
className="edit-pack-modal"
closeButton
confirmButton={t('app.admin.configure_packs_button.confirm_changes')}
onConfirmSendFormId="edit-pack">
{packData && <PackForm formId="edit-pack" onSubmit={handleUpdate} pack={packData} />}
</FabModal>
{packs?.length === 0 && <span>{t('app.admin.configure_packs_button.no_packs')}</span>} {packs?.length === 0 && <span>{t('app.admin.configure_packs_button.no_packs')}</span>}
</FabPopover>} </FabPopover>}
</div> </div>

View File

@ -12,8 +12,8 @@
*/ */
'use strict'; 'use strict';
Application.Controllers.controller('AdminProjectsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise', 'growl', Application.Controllers.controller('AdminProjectsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'ProjectCategory', 'componentsPromise', 'licencesPromise', 'themesPromise', 'projectCategoriesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise', 'growl',
function ($scope, $state, Component, Licence, Theme, componentsPromise, licencesPromise, themesPromise, _t, Member, uiTourService, settingsPromise, growl) { function ($scope, $state, Component, Licence, Theme, ProjectCategory, componentsPromise, licencesPromise, themesPromise, projectCategoriesPromise, _t, Member, uiTourService, settingsPromise, growl) {
// Materials list (plastic, wood ...) // Materials list (plastic, wood ...)
$scope.components = componentsPromise; $scope.components = componentsPromise;
@ -23,6 +23,9 @@ Application.Controllers.controller('AdminProjectsController', ['$scope', '$state
// Themes list (cooking, sport ...) // Themes list (cooking, sport ...)
$scope.themes = themesPromise; $scope.themes = themesPromise;
// Project categories list (generic categorization)
$scope.projectCategories = projectCategoriesPromise;
// Application settings // Application settings
$scope.allSettings = settingsPromise; $scope.allSettings = settingsPromise;
@ -115,6 +118,49 @@ Application.Controllers.controller('AdminProjectsController', ['$scope', '$state
} }
}; };
/**
* Saves a new project category / Update an existing project category to the server (form validation callback)
* @param data {Object} project category name
* @param [data] {number} project category id, in case of update
*/
$scope.saveProjectCategory = function (data, id) {
if (id != null) {
return ProjectCategory.update({ id }, data);
} else {
return ProjectCategory.save(data, resp => $scope.projectCategories[$scope.projectCategories.length - 1].id = resp.id);
}
};
/**
* Deletes the project category at the specified index
* @param index {number} project category index in the $scope.projectCategories array
*/
$scope.removeProjectCategory = function (index) {
ProjectCategory.delete($scope.projectCategories[index]);
return $scope.projectCategories.splice(index, 1);
};
/**
* Creates a new empty entry in the $scope.projectCategories array
*/
$scope.addProjectCategory = function () {
$scope.inserted = { name: '' };
$scope.projectCategories.push($scope.inserted);
};
/**
* Removes the newly inserted but not saved project category / Cancel the current project category modification
* @param rowform {Object} see http://vitalets.github.io/angular-xeditable/
* @param index {number} project category index in the $scope.projectCategories array
*/
$scope.cancelProjectCategory = function (rowform, index) {
if ($scope.projectCategories[index].id != null) {
rowform.$cancel();
} else {
$scope.projectCategories.splice(index, 1);
}
};
/** /**
* Saves a new licence / Update an existing licence to the server (form validation callback) * Saves a new licence / Update an existing licence to the server (form validation callback)
* @param data {Object} licence name and description * @param data {Object} licence name and description

View File

@ -692,20 +692,9 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$tran
* Refetch all events from the API and re-populate the calendar with the resulting slots * Refetch all events from the API and re-populate the calendar with the resulting slots
*/ */
const refreshCalendar = function () { const refreshCalendar = function () {
const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView'); $scope.eventSources.splice(0, 1, {
return Availability.machine({ url: `/api/availabilities/machines/${$transition$.params().id}?member_id=${$scope.ctrl.member.id}`,
machineId: $scope.machine.id, textColor: 'black'
member_id: $scope.ctrl.member.id,
start: view.start,
end: view.end,
timezone: Fablab.timezone
}, function (slots) {
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
return $scope.eventSources.splice(0, 1, {
events: slots,
textColor: 'black'
}
);
}); });
} }

View File

@ -29,6 +29,7 @@
* - $scope.themes = [{Theme}] * - $scope.themes = [{Theme}]
* - $scope.licences = [{Licence}] * - $scope.licences = [{Licence}]
* - $scope.allowedExtensions = [{String}] * - $scope.allowedExtensions = [{String}]
* - $scope.projectCategoriesWording = [{String}]
* - $scope.submited(content) * - $scope.submited(content)
* - $scope.cancel() * - $scope.cancel()
* - $scope.addFile() * - $scope.addFile()
@ -43,7 +44,7 @@
* - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ] * - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ]
*/ */
class ProjectsController { class ProjectsController {
constructor ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, _t) { constructor ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) {
// remove codeview from summernote editor // remove codeview from summernote editor
$scope.summernoteOptsProject = angular.copy($rootScope.summernoteOpts); $scope.summernoteOptsProject = angular.copy($rootScope.summernoteOpts);
$scope.summernoteOptsProject.toolbar[6][1].splice(1, 1); $scope.summernoteOptsProject.toolbar[6][1].splice(1, 1);
@ -78,6 +79,16 @@ class ProjectsController {
}); });
}); });
// Retrieve the list of themes from the server
ProjectCategory.query().$promise.then(function (data) {
$scope.projectCategories = data.map(function (d) {
return ({
id: d.id,
name: d.name
});
});
});
// Retrieve the list of licences from the server // Retrieve the list of licences from the server
Licence.query().$promise.then(function (data) { Licence.query().$promise.then(function (data) {
$scope.licences = data.map(function (d) { $scope.licences = data.map(function (d) {
@ -104,6 +115,8 @@ class ProjectsController {
// List of extensions allowed for CAD attachements upload // List of extensions allowed for CAD attachements upload
$scope.allowedExtensions = allowedExtensions.setting.value.split(' '); $scope.allowedExtensions = allowedExtensions.setting.value.split(' ');
$scope.projectCategoriesWording = projectCategoriesWording.setting.value;
/** /**
* For use with ngUpload (https://github.com/twilson63/ngUpload). * For use with ngUpload (https://github.com/twilson63/ngUpload).
* Intended to be the callback when an upload is done: any raised error will be stacked in the * Intended to be the callback when an upload is done: any raised error will be stacked in the
@ -281,8 +294,8 @@ class ProjectsController {
/** /**
* Controller used on projects listing page * Controller used on projects listing page
*/ */
Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', 'settingsPromise', 'openLabActive', Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'Project', 'machinesPromise', 'themesPromise', 'projectCategoriesPromise', 'componentsPromise', 'paginationService', 'OpenlabProject', '$window', 'growl', '_t', '$location', '$timeout', 'settingsPromise', 'openLabActive', 'Member', 'Diacritics',
function ($scope, $state, Project, machinesPromise, themesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive) { function ($scope, $state, Project, machinesPromise, themesPromise, projectCategoriesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive, Member, Diacritics) {
/* PRIVATE STATIC CONSTANTS */ /* PRIVATE STATIC CONSTANTS */
// Number of projects added to the page when the user clicks on 'load more projects' // Number of projects added to the page when the user clicks on 'load more projects'
@ -294,12 +307,24 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
// Fab-manager's instance ID in the openLab network // Fab-manager's instance ID in the openLab network
$scope.openlabAppId = settingsPromise.openlab_app_id; $scope.openlabAppId = settingsPromise.openlab_app_id;
// settings of optional filters
$scope.memberFilterPresence = settingsPromise.projects_list_member_filter_presence !== 'false';
$scope.dateFiltersPresence = settingsPromise.projects_list_date_filters_presence !== 'false';
$scope.projectCategoriesFilterPlaceholder = settingsPromise.project_categories_filter_placeholder;
// Is openLab enabled on the instance? // Is openLab enabled on the instance?
$scope.openlab = { $scope.openlab = {
projectsActive: openLabActive.isPresent, projectsActive: openLabActive.isPresent,
searchOverWholeNetwork: settingsPromise.openlab_default === 'true' searchOverWholeNetwork: settingsPromise.openlab_default === 'true'
}; };
if (!$scope.memberFilterPresence) {
$location.$$search.member_id = '';
}
fromDate = $location.$$search.from_date ? new Date($location.$$search.from_date) : undefined;
toDate = $location.$$search.to_date ? new Date($location.$$search.to_date) : undefined;
// default search parameters // default search parameters
$scope.search = { $scope.search = {
q: ($location.$$search.q || ''), q: ($location.$$search.q || ''),
@ -307,7 +332,27 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
machine_id: (parseInt($location.$$search.machine_id) || undefined), machine_id: (parseInt($location.$$search.machine_id) || undefined),
component_id: (parseInt($location.$$search.component_id) || undefined), component_id: (parseInt($location.$$search.component_id) || undefined),
theme_id: (parseInt($location.$$search.theme_id) || undefined), theme_id: (parseInt($location.$$search.theme_id) || undefined),
status_id: (parseInt($location.$$search.status_id) || undefined) status_id: (parseInt($location.$$search.status_id) || undefined),
project_category_id: (parseInt($location.$$search.project_category_id) || undefined),
member_id: (parseInt($location.$$search.member_id) || undefined),
from_date: fromDate,
to_date: toDate
};
$scope.autoCompleteMemberName = function (nameLookup) {
if (!nameLookup) {
return;
}
$scope.isLoadingMembers = true;
const asciiName = Diacritics.remove(nameLookup);
const q = { query: asciiName };
Member.search(q, function (users) {
$scope.matchingMembers = users;
$scope.isLoadingMembers = false;
}
, function (error) { console.error(error); });
}; };
// list of projects to display // list of projects to display
@ -319,6 +364,9 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
// list of themes / used for filtering // list of themes / used for filtering
$scope.themes = themesPromise; $scope.themes = themesPromise;
// list of projectCategories / used for filtering
$scope.projectCategories = projectCategoriesPromise;
// list of components / used for filtering // list of components / used for filtering
$scope.components = componentsPromise; $scope.components = componentsPromise;
@ -332,6 +380,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
$scope.triggerSearch(); $scope.triggerSearch();
}; };
$scope.zipUrl = '/api/projects/search.zip';
/** /**
* Callback triggered when the button "search from the whole network" is toggled * Callback triggered when the button "search from the whole network" is toggled
*/ */
@ -361,6 +411,10 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
$scope.search.component_id = undefined; $scope.search.component_id = undefined;
$scope.search.theme_id = undefined; $scope.search.theme_id = undefined;
$scope.search.status_id = undefined; $scope.search.status_id = undefined;
$scope.search.member_id = undefined;
$scope.search.from_date = undefined;
$scope.search.to_date = undefined;
$scope.search.project_category_id = undefined;
$scope.$apply(); $scope.$apply();
$scope.setUrlQueryParams($scope.search); $scope.setUrlQueryParams($scope.search);
$scope.triggerSearch(); $scope.triggerSearch();
@ -389,7 +443,10 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
} else { } else {
updateUrlParam('whole_network', 'f'); updateUrlParam('whole_network', 'f');
$scope.projectsPagination = new paginationService.Instance(Project, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreCallback, 'search'); $scope.projectsPagination = new paginationService.Instance(Project, currentPage, PROJECTS_PER_PAGE, null, { }, loadMoreCallback, 'search');
Project.search({ search: $scope.search, page: currentPage, per_page: PROJECTS_PER_PAGE }, function (projectsPromise) { const fromDate = $scope.search.from_date ? $scope.search.from_date.toLocaleDateString() : undefined;
const toDate = $scope.search.to_date ? $scope.search.to_date.toLocaleDateString() : undefined;
const searchParams = Object.assign({}, $scope.search, { from_date: fromDate, to_date: toDate });
Project.search({ search: searchParams, page: currentPage, per_page: PROJECTS_PER_PAGE }, function (projectsPromise) {
$scope.projectsPagination.totalCount = projectsPromise.meta.total; $scope.projectsPagination.totalCount = projectsPromise.meta.total;
$scope.projects = projectsPromise.projects; $scope.projects = projectsPromise.projects;
}); });
@ -420,6 +477,22 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
updateUrlParam('component_id', search.component_id); updateUrlParam('component_id', search.component_id);
updateUrlParam('machine_id', search.machine_id); updateUrlParam('machine_id', search.machine_id);
updateUrlParam('status_id', search.status_id); updateUrlParam('status_id', search.status_id);
updateUrlParam('member_id', search.member_id);
const fromDate = search.from_date ? search.from_date.toDateString() : undefined;
updateUrlParam('from_date', fromDate);
const toDate = search.to_date ? search.to_date.toDateString() : undefined;
updateUrlParam('to_date', toDate);
updateUrlParam('project_category_id', search.project_category_id);
$scope.zipUrl = '/api/projects/search.zip?' + new URLSearchParams({ search: JSON.stringify($location.search()) }).toString();
return true;
};
$scope.setSearchMemberId = function (searchMember) {
if (searchMember) {
$scope.search.member_id = searchMember.id;
} else {
$scope.search.member_id = undefined;
}
return true; return true;
}; };
@ -450,6 +523,11 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
} else { } else {
$scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive; $scope.openlab.searchOverWholeNetwork = $scope.openlab.projectsActive;
} }
if ($location.$$search.member_id && $scope.memberFilterPresence) {
Member.get({ id: $location.$$search.member_id }, function (member) {
$scope.searchMember = member;
});
}
return $scope.triggerSearch(); return $scope.triggerSearch();
}; };
@ -496,8 +574,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P
/** /**
* Controller used in the project creation page * Controller used in the project creation page
*/ */
Application.Controllers.controller('NewProjectController', ['$rootScope', '$scope', '$state', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', 'Status', '$document', 'CSRF', 'Diacritics', 'dialogs', 'allowedExtensions', '_t', Application.Controllers.controller('NewProjectController', ['$rootScope', '$scope', '$state', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'ProjectCategory', 'Licence', 'Status', '$document', 'CSRF', 'Diacritics', 'dialogs', 'allowedExtensions', 'projectCategoriesWording', '_t',
function ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, CSRF, Diacritics, dialogs, allowedExtensions, _t) { function ($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, CSRF, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) {
CSRF.setMetaTags(); CSRF.setMetaTags();
// API URL where the form will be posted // API URL where the form will be posted
@ -529,15 +607,15 @@ Application.Controllers.controller('NewProjectController', ['$rootScope', '$scop
}; };
// Using the ProjectsController // Using the ProjectsController
return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, _t); return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t);
} }
]); ]);
/** /**
* Controller used in the project edition page * Controller used in the project edition page
*/ */
Application.Controllers.controller('EditProjectController', ['$rootScope', '$scope', '$state', '$transition$', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'Licence', 'Status', '$document', 'CSRF', 'projectPromise', 'Diacritics', 'dialogs', 'allowedExtensions', '_t', Application.Controllers.controller('EditProjectController', ['$rootScope', '$scope', '$state', '$transition$', 'Project', 'Machine', 'Member', 'Component', 'Theme', 'ProjectCategory', 'Licence', 'Status', '$document', 'CSRF', 'projectPromise', 'Diacritics', 'dialogs', 'allowedExtensions', 'projectCategoriesWording', '_t',
function ($rootScope, $scope, $state, $transition$, Project, Machine, Member, Component, Theme, Licence, Status, $document, CSRF, projectPromise, Diacritics, dialogs, allowedExtensions, _t) { function ($rootScope, $scope, $state, $transition$, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, CSRF, projectPromise, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) {
/* PUBLIC SCOPE */ /* PUBLIC SCOPE */
// API URL where the form will be posted // API URL where the form will be posted
@ -583,7 +661,7 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco
} }
// Using the ProjectsController // Using the ProjectsController
return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, _t); return new ProjectsController($rootScope, $scope, $state, Project, Machine, Member, Component, Theme, ProjectCategory, Licence, Status, $document, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t);
}; };
// !!! MUST BE CALLED AT THE END of the controller // !!! MUST BE CALLED AT THE END of the controller
@ -594,14 +672,15 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco
/** /**
* Controller used in the public project's details page * Controller used in the public project's details page
*/ */
Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', 'shortnamePromise', '$location', '$uibModal', 'dialogs', '_t', Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', 'shortnamePromise', 'projectCategoriesWording', '$location', '$uibModal', 'dialogs', '_t',
function ($scope, $state, projectPromise, shortnamePromise, $location, $uibModal, dialogs, _t) { function ($scope, $state, projectPromise, shortnamePromise, projectCategoriesWording, $location, $uibModal, dialogs, _t) {
/* PUBLIC SCOPE */ /* PUBLIC SCOPE */
// Store the project's details // Store the project's details
$scope.project = projectPromise; $scope.project = projectPromise;
$scope.projectUrl = $location.absUrl(); $scope.projectUrl = $location.absUrl();
$scope.disqusShortname = shortnamePromise.setting.value; $scope.disqusShortname = shortnamePromise.setting.value;
$scope.projectCategoriesWording = projectCategoriesWording.setting.value;
/** /**
* Test if the provided user has the edition rights on the current project * Test if the provided user has the edition rights on the current project

View File

@ -609,20 +609,9 @@ Application.Controllers.controller('ReserveSpaceController', ['$scope', '$transi
* Refetch all events from the API and re-populate the calendar with the resulting slots * Refetch all events from the API and re-populate the calendar with the resulting slots
*/ */
const refreshCalendar = function () { const refreshCalendar = function () {
const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView'); $scope.eventSources.splice(0, 1, {
return Availability.spaces({ url: `/api/availabilities/spaces/${$transition$.params().id}?member_id=${$scope.ctrl.member.id}`,
spaceId: $scope.space.id, textColor: 'black'
member_id: $scope.ctrl.member.id,
start: view.start,
end: view.end,
timezone: Fablab.timezone
}, function (spaces) {
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
return $scope.eventSources.splice(0, 1, {
events: spaces,
textColor: 'black'
}
);
}); });
}; };

View File

@ -385,20 +385,9 @@ Application.Controllers.controller('ReserveTrainingController', ['$scope', '$tra
* Refetch all events from the API and re-populate the calendar with the resulting slots * Refetch all events from the API and re-populate the calendar with the resulting slots
*/ */
const refreshCalendar = function () { const refreshCalendar = function () {
const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView'); $scope.eventSources.splice(0, 1, {
const id = $transition$.params().id === 'all' ? $transition$.params().id : $scope.training.id; url: `/api/availabilities/trainings/${$transition$.params().id}`,
Availability.trainings({ textColor: 'black'
trainingId: id,
member_id: $scope.ctrl.member.id,
start: view.start,
end: view.end,
timezone: Fablab.timezone
}, function (trainings) {
uiCalendarConfig.calendars.calendar.fullCalendar('removeEvents');
$scope.eventSources.splice(0, 1, {
events: trainings,
textColor: 'black'
});
}); });
} }

View File

@ -0,0 +1,5 @@
// Type model used in ProjectSettings and its child components
export interface ProjectCategory {
name: string,
id?: number,
}

View File

@ -199,7 +199,11 @@ export const fabHubSettings = [
export const projectsSettings = [ export const projectsSettings = [
'allowed_cad_extensions', 'allowed_cad_extensions',
'allowed_cad_mime_types', 'allowed_cad_mime_types',
'disqus_shortname' 'disqus_shortname',
'projects_list_member_filter_presence',
'projects_list_date_filters_presence',
'project_categories_filter_placeholder',
'project_categories_wording'
] as const; ] as const;
export const prepaidPacksSettings = [ export const prepaidPacksSettings = [
@ -222,7 +226,7 @@ export const pricingSettings = [
'extended_prices_in_same_day' 'extended_prices_in_same_day'
] as const; ] as const;
export const poymentSettings = [ export const paymentSettings = [
'payment_gateway' 'payment_gateway'
] as const; ] as const;
@ -292,7 +296,7 @@ export const allSettings = [
...registrationSettings, ...registrationSettings,
...adminSettings, ...adminSettings,
...pricingSettings, ...pricingSettings,
...poymentSettings, ...paymentSettings,
...displaySettings, ...displaySettings,
...storeSettings, ...storeSettings,
...trainingsSettings, ...trainingsSettings,

View File

@ -313,8 +313,9 @@ angular.module('application.router', ['ui.router'])
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }], themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }], componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id', 'openlab_default']" }).$promise; }], settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id', 'openlab_default', 'projects_list_member_filter_presence', 'projects_list_date_filters_presence', 'project_categories_filter_placeholder']" }).$promise; }],
openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$promise; }] openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$promise; }],
projectCategoriesPromise: ['ProjectCategory', function (ProjectCategory) { return ProjectCategory.query().$promise; }]
} }
}) })
.state('app.logged.projects_new', { .state('app.logged.projects_new', {
@ -326,7 +327,8 @@ angular.module('application.router', ['ui.router'])
} }
}, },
resolve: { resolve: {
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }] allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }],
projectCategoriesWording: ['Setting', function (Setting) { return Setting.get({ name: 'project_categories_wording' }).$promise; }]
} }
}) })
.state('app.public.projects_show', { .state('app.public.projects_show', {
@ -339,7 +341,8 @@ angular.module('application.router', ['ui.router'])
}, },
resolve: { resolve: {
projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }], projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }],
shortnamePromise: ['Setting', function (Setting) { return Setting.get({ name: 'disqus_shortname' }).$promise; }] shortnamePromise: ['Setting', function (Setting) { return Setting.get({ name: 'disqus_shortname' }).$promise; }],
projectCategoriesWording: ['Setting', function (Setting) { return Setting.get({ name: 'project_categories_wording' }).$promise; }]
} }
}) })
.state('app.logged.projects_edit', { .state('app.logged.projects_edit', {
@ -352,7 +355,8 @@ angular.module('application.router', ['ui.router'])
}, },
resolve: { resolve: {
projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }], projectPromise: ['$transition$', 'Project', function ($transition$, Project) { return Project.get({ id: $transition$.params().id }).$promise; }],
allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }] allowedExtensions: ['Setting', function (Setting) { return Setting.get({ name: 'allowed_cad_extensions' }).$promise; }],
projectCategoriesWording: ['Setting', function (Setting) { return Setting.get({ name: 'project_categories_wording' }).$promise; }]
} }
}) })
@ -747,10 +751,11 @@ angular.module('application.router', ['ui.router'])
componentsPromise: ['Component', function (Component) { return Component.query().$promise; }], componentsPromise: ['Component', function (Component) { return Component.query().$promise; }],
licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }], licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }],
themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }], themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }],
projectCategoriesPromise: ['ProjectCategory', function (ProjectCategory) { return ProjectCategory.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { settingsPromise: ['Setting', function (Setting) {
return Setting.query({ return Setting.query({
names: "['feature_tour_display', 'disqus_shortname', 'allowed_cad_extensions', " + names: "['feature_tour_display', 'disqus_shortname', 'allowed_cad_extensions', " +
"'allowed_cad_mime_types', 'openlab_app_id', 'openlab_app_secret', 'openlab_default']" "'allowed_cad_mime_types', 'openlab_app_id', 'openlab_app_secret', 'openlab_default', 'project_categories_filter_placeholder', 'project_categories_wording']"
}).$promise; }).$promise;
}] }]
} }

View File

@ -0,0 +1,11 @@
'use strict';
Application.Services.factory('ProjectCategory', ['$resource', function ($resource) {
return $resource('/api/project_categories/:id',
{ id: '@id' }, {
update: {
method: 'PUT'
}
}
);
}]);

View File

@ -42,7 +42,10 @@
<uib-tab heading="{{ 'app.admin.projects.statuses' | translate }}" index="3"> <uib-tab heading="{{ 'app.admin.projects.statuses' | translate }}" index="3">
<status-settings on-error="onError" on-success="onSuccess"/> <status-settings on-error="onError" on-success="onSuccess"/>
</uib-tab> </uib-tab>
<uib-tab heading="{{ 'app.admin.projects.settings.title' | translate }}" index="4" class="settings-tab"> <uib-tab heading="{{ 'app.admin.projects.project_categories' | translate }}" index="4">
<ng-include src="'/admin/projects/project_categories.html'"></ng-include>
</uib-tab>
<uib-tab heading="{{ 'app.admin.projects.settings.title' | translate }}" index="5" class="settings-tab">
<ng-include src="'/admin/projects/settings.html'"></ng-include> <ng-include src="'/admin/projects/settings.html'"></ng-include>
</uib-tab> </uib-tab>
</uib-tabset> </uib-tabset>

View File

@ -0,0 +1,38 @@
<button type="button" class="btn btn-warning m-t m-b" ng-click="addProjectCategory()" translate>{{ 'app.admin.projects.add_a_new_project_category' }}</button>
<table class="table">
<thead>
<tr>
<th style="width:80%" translate>{{ 'app.admin.project_categories.name' }}</th>
<th style="width:20%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="projectCategory in projectCategories">
<td>
<span editable-text="projectCategory.name" e-name="name" e-form="rowform" e-required>
{{ projectCategory.name }}
</span>
</td>
<td>
<!-- form -->
<form editable-form name="rowform" onbeforesave="saveProjectCategory($data, projectCategory.id)" ng-show="rowform.$visible" class="form-buttons form-inline" shown="inserted == projectCategory">
<button type="submit" ng-disabled="rowform.$waiting" class="btn btn-warning">
<i class="fa fa-check"></i>
</button>
<button type="button" ng-disabled="rowform.$waiting" ng-click="cancelProjectCategory(rowform, $index)" class="btn btn-default">
<i class="fa fa-times"></i>
</button>
</form>
<div class="buttons" ng-show="!rowform.$visible">
<button class="btn btn-default" ng-click="rowform.$show()">
<i class="fa fa-edit"></i> <span class="hidden-xs hidden-sm" translate>{{ 'app.shared.buttons.edit' }}</span>
</button>
<button class="btn btn-danger" ng-click="removeProjectCategory($index)">
<i class="fa fa-trash-o"></i>
</button>
</div>
</td>
</tr>
</tbody>
</table>

View File

@ -95,3 +95,49 @@
</div> </div>
</div> </div>
</div> </div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.filters' }}</span>
</div>
<div class="panel-body">
<div class="row">
<boolean-setting name="'projects_list_member_filter_presence'"
label="'app.admin.settings.projects_list_member_filter_presence' | translate"
on-success="onSuccess"
on-error="onError"
class-name="'m-l'"></boolean-setting>
</div>
<div class="row">
<boolean-setting name="'projects_list_date_filters_presence'"
label="'app.admin.settings.projects_list_date_filters_presence' | translate"
on-success="onSuccess"
on-error="onError"
class-name="'m-l'"></boolean-setting>
</div>
</div>
</div>
<div class="panel panel-default m-t-lg">
<div class="panel-heading">
<span class="font-sbold" translate>{{ 'app.admin.projects.settings.project_categories' }}</span>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<text-setting name="project_categories_filter_placeholder"
settings="allSettings"
label="app.admin.settings.project_categories_filter_placeholder">
</text-setting>
</div>
</div>
<div class="row m-t">
<div class="col-md-6">
<text-setting name="project_categories_wording"
settings="allSettings"
label="app.admin.settings.project_categories_wording">
</text-setting>
</div>
</div>
</div>
</div>

View File

@ -279,6 +279,23 @@
</div> </div>
</div> </div>
<div class="widget panel b-a m m-t-lg">
<div class="panel-heading b-b small">
<h3 translate>{{ projectCategoriesWording }}</h3>
</div>
<div class="widget-content no-bg wrapper">
<input type="hidden" name="project[project_category_ids][]" value="" />
<ui-select multiple ng-model="project.project_category_ids" class="form-control">
<ui-select-match>
<span ng-bind="$item.name"></span>
<input type="hidden" name="project[project_category_ids][]" value="{{$item.id}}" />
</ui-select-match>
<ui-select-choices repeat="pc.id as pc in (projectCategories | filter: $select.search)">
<span ng-bind-html="pc.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -22,7 +22,7 @@
<section class="projects"> <section class="projects">
<div class="projects-filters"> <div class="projects-filters">
<header> <header>
<h3>Filter</h3> <h3 translate>{{ 'app.public.projects_list.filter' }}</h3>
<a href="javascript:void(0);" class="fab-button is-black" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork">{{ 'app.public.projects_list.reset_all_filters' | translate }}</a> <a href="javascript:void(0);" class="fab-button is-black" name="button" ng-click="resetFiltersAndTriggerSearch()" ng-show="!openlab.searchOverWholeNetwork">{{ 'app.public.projects_list.reset_all_filters' | translate }}</a>
</header> </header>
<span class="switch" ng-if="openlab.projectsActive" uib-tooltip="{{ 'app.public.projects_list.tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter"> <span class="switch" ng-if="openlab.projectsActive" uib-tooltip="{{ 'app.public.projects_list.tooltip_openlab_projects_switch' | translate }}" tooltip-trigger="mouseenter">
@ -61,12 +61,57 @@
<option value="" translate>{{ 'app.public.projects_list.all_themes' }}</option> <option value="" translate>{{ 'app.public.projects_list.all_themes' }}</option>
</select> </select>
<select ng-model="search.project_category_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="pc.id as pc.name for pc in projectCategories">
<option value="" translate>{{ projectCategoriesFilterPlaceholder }}</option>
</select>
<select ng-model="search.component_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components"> <select ng-model="search.component_id" ng-change="setUrlQueryParams(search) && triggerSearch()" class="form-control" ng-options="t.id as t.name for t in components">
<option value="" translate>{{ 'app.public.projects_list.all_materials' }}</option> <option value="" translate>{{ 'app.public.projects_list.all_materials' }}</option>
</select> </select>
<ui-select ng-if="currentUser && memberFilterPresence" ng-model="searchMember" on-select="setSearchMemberId(searchMember) && setUrlQueryParams(search) && triggerSearch()">
<ui-select-match allow-clear="true" placeholder="{{ 'app.public.projects_list.filter_by_member' | translate }}">
<span ng-bind="$select.selected.name"></span>
</ui-select-match>
<ui-select-choices repeat="m in matchingMembers" refresh="autoCompleteMemberName($select.search)" refresh-delay="300">
<span ng-bind-html="m.name | highlight: $select.search"></span>
</ui-select-choices>
</ui-select>
<label class="form-group m-n" ng-if="dateFiltersPresence">
<div class="form-item-header">
<p translate>{{ 'app.public.projects_list.created_from' }}</p>
</div>
<input class="form-control"
ng-model="search.from_date"
ng-model-options='{ debounce: 1000 }'
ng-change="setUrlQueryParams(search) && triggerSearch()"
type="date"
min="2000-01-01"
max="2060-01-01"/>
</label>
<label class="form-group m-n" ng-if="dateFiltersPresence">
<div class="form-item-header">
<p translate>{{ 'app.public.projects_list.created_to' }}</p>
</div>
<input class="form-control"
ng-model="search.to_date"
ng-model-options='{ debounce: 1000 }'
ng-change="setUrlQueryParams(search) && triggerSearch()"
type="date"
min="2000-01-01"
max="2060-01-01"/>
</label>
<status-filter on-filter-change="onStatusChange" current-status-index="search.status_id"/> <status-filter on-filter-change="onStatusChange" current-status-index="search.status_id"/>
</div> </div>
<div class="text-center m m-b-lg" ng-if="!openlab.searchOverWholeNetwork && (projects.length != 0) && (isAuthorized('admin') || isAuthorized('manager'))">
<a class="btn bg-light text-black" ng-href="{{ zipUrl }}" target="_blank">
<i class="fa fa-download"></i> {{ 'app.public.projects_list.download_archive' | translate }}
</a>
</div>
</div> </div>
<div class="projects-list"> <div class="projects-list">

View File

@ -1,187 +1,202 @@
<div> <div>
<section class="heading b-b"> <section class="heading b-b">
<div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1">
<section class="heading-btn">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{ project.name }} <span class="badge" ng-if="project.state == 'draft'" translate>{{ 'app.public.projects_show.rough_draft' }}</span></h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a ui-sref="app.logged.projects_edit({id: project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}</a>
<a ng-click="deleteProject(event)" ng-if="projectDeletableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
</section>
</div>
</div>
</section>
<div class="row no-gutter"> <div class="row no-gutter">
<div class="col-xs-2 col-sm-2 col-md-1"> <div class="col-sm-12 col-md-12 col-lg-9 b-r-lg">
<section class="heading-btn">
<a ng-click="backPrevLocation($event)"><i class="fas fa-long-arrow-alt-left "></i></a>
</section>
</div>
<div class="col-xs-10 col-sm-10 col-md-8 b-l b-r-md">
<section class="heading-title">
<h1>{{ project.name }} <span class="badge" ng-if="project.state == 'draft'" translate>{{ 'app.public.projects_show.rough_draft' }}</span></h1>
</section>
</div>
<div class="col-xs-12 col-sm-12 col-md-3 b-t hide-b-md">
<section class="heading-actions wrapper">
<a ui-sref="app.logged.projects_edit({id: project.id})" ng-if="projectEditableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-warning bg-white b-2x rounded m-t-xs text-u-c text-sm"><i class="fa fa-edit"></i> {{ 'app.shared.buttons.edit' | translate }}</a>
<a ng-click="deleteProject(event)" ng-if="projectDeletableBy(currentUser) || isAuthorized('admin')" class="btn btn-lg btn-danger b-2x rounded no-b m-t-xs"><i class="fa fa-trash-o"></i></a>
</section>
</div>
</div>
</section>
<div class="row no-gutter">
<div class="col-sm-12 col-md-12 col-lg-9 b-r-lg">
<div class="article wrapper-lg">
<div class="article-thumbnail" ng-if="project.project_image">
<a href="{{project.project_full_image}}" target="_blank"><img ng-src="{{project.project_image}}" alt="{{project.name}}"></a>
</div>
<h3 translate>{{ 'app.public.projects_show.project_description' }}</h3>
<p ng-bind-html="project.description | toTrusted"></p>
<div class="article-steps">
<div class="row article-step m-b-lg" ng-repeat="step in project.project_steps_attributes">
<div class="col-md-12 m-b-xs">
<h3 class="well well-simple step-title">{{ 'app.public.projects_show.step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}</h3>
</div>
<div ng-repeat-start="image in step.project_step_images_attributes" class="clearfix" ng-if="$index % 3 == 0"></div>
<div class="col-md-4" ng-repeat-end>
<a href="{{image.attachment_full_url}}" target="_blank"><img class="m-b" ng-src="{{image.attachment_url}}" alt="{{image.attachment}}" ></a>
</div>
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_images_attributes.length > 1 || step.project_step_images_attributes.length == 0}">
<p ng-bind-html="step.description | toTrusted"></p>
</div>
<div class="article wrapper-lg">
<div class="article-thumbnail" ng-if="project.project_image">
<a href="{{project.project_full_image}}" target="_blank"><img ng-src="{{project.project_image}}" alt="{{project.name}}"></a>
</div> </div>
<h3 translate>{{ 'app.public.projects_show.project_description' }}</h3>
<p ng-bind-html="project.description | toTrusted"></p>
<div class="article-steps">
<div class="row article-step m-b-lg" ng-repeat="step in project.project_steps_attributes">
<div class="col-md-12 m-b-xs">
<h3 class="well well-simple step-title">{{ 'app.public.projects_show.step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}</h3>
</div>
<div ng-repeat-start="image in step.project_step_images_attributes" class="clearfix" ng-if="$index % 3 == 0"></div>
<div class="col-md-4" ng-repeat-end>
<a href="{{image.attachment_full_url}}" target="_blank"><img class="m-b" ng-src="{{image.attachment_url}}" alt="{{image.attachment}}" ></a>
</div>
<div class="col-md-8" ng-class="{'col-md-12' : step.project_step_images_attributes.length > 1 || step.project_step_images_attributes.length == 0}">
<p ng-bind-html="step.description | toTrusted"></p>
</div>
</div>
</div>
</div>
<div class="text-center" id="social-share">
<a ng-href="{{shareOnFacebook()}}" target="_blank" class="btn btn-facebook btn-lg m-t"><i class="fa fa-facebook m-r"></i> {{ 'app.public.projects_show.share_on_facebook' | translate }}</a>
<a ng-href="{{shareOnTwitter()}}" target="_blank" class="btn btn-twitter btn-lg m-t"><i class="fa fa-twitter m-r"></i> {{ 'app.public.projects_show.share_on_twitter' | translate }}</a>
</div>
<div class="wrapper-lg" ng-if="disqusShortname">
<dir-disqus disqus-shortname="{{ disqusShortname }}" disqus-identifier="project_{{ project.id }}" disqus-url="{{ projectUrl }}" ready-to-bind="{{ project }}">
</dir-disqus>
</div> </div>
</div> </div>
<div class="text-center" id="social-share"> <div class="col-sm-12 col-md-12 col-lg-3">
<a ng-href="{{shareOnFacebook()}}" target="_blank" class="btn btn-facebook btn-lg m-t"><i class="fa fa-facebook m-r"></i> {{ 'app.public.projects_show.share_on_facebook' | translate }}</a>
<a ng-href="{{shareOnTwitter()}}" target="_blank" class="btn btn-twitter btn-lg m-t"><i class="fa fa-twitter m-r"></i> {{ 'app.public.projects_show.share_on_twitter' | translate }}</a>
</div>
<div class="wrapper-lg" ng-if="disqusShortname">
<dir-disqus disqus-shortname="{{ disqusShortname }}" disqus-identifier="project_{{ project.id }}" disqus-url="{{ projectUrl }}" ready-to-bind="{{ project }}">
</dir-disqus>
</div>
</div>
<div class="col-sm-12 col-md-12 col-lg-3">
<div class="text-center m-t-lg m-v"> <div class="text-center m-t-lg m-v">
<div class="thumb-lg m-b-xs"> <div class="thumb-lg m-b-xs">
<fab-user-avatar ng-model="project.author.user_avatar" avatar-class="thumb-50"></fab-user-avatar> <fab-user-avatar ng-model="project.author.user_avatar" avatar-class="thumb-50"></fab-user-avatar>
</div>
<div>
<a ng-show="project.author_id" class="text-sm font-sbold project-author" ui-sref="app.logged.members_show({id: project.author.slug})">
<i> {{ 'app.public.projects_show.by_name' | translate:{NAME:project.author.first_name} }}</i>
</a>
<span ng-hide="project.author_id" class="text-sm font-sbold text-gray" translate>{{ 'app.public.projects_show.deleted_user' }}</span>
</div>
<small class="text-xs m-b"><i>{{ 'app.public.projects_show.posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}}</i></small>
<div class="m" ng-if="project.themes">
<span ng-repeat="theme in project.themes" class="badge m-r-sm">
{{theme.name}}
</span>
</div>
</div> </div>
<div>
<a ng-show="project.author_id" class="text-sm font-sbold project-author" ui-sref="app.logged.members_show({id: project.author.slug})">
<i> {{ 'app.public.projects_show.by_name' | translate:{NAME:project.author.first_name} }}</i> <section class="widget panel b-a m" ng-if="project.project_caos_attributes">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.project_caos_attributes.length}}</span>
<h3 translate translate-values="{COUNT:project.project_caos_attributes.length}">{{ 'app.public.projects_show.CAD_file_to_download' }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="file in project.project_caos_attributes" class="list-group-item no-b clearfix">
<a target="_blank" ng-href="{{file.attachment_url}}" download="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.status">
<div class="panel-heading b-b">
<h3 translate>{{ 'app.public.projects_show.status' }}</h3>
</div>
<div class="panel-body">
{{ project.status.name }}
</div>
</section>
<section class="widget panel b-a m" ng-if="project.machines">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.machines.length}}</span>
<h3 translate>{{ 'app.public.projects_show.machines_and_materials' }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="machine in project.machines" class="list-group-item no-b clearfix">
<a ui-sref="app.public.machines_show({id: machine.id})">{{machine.name}}</a>
</li>
</ul>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="component in project.components" class="list-group-item no-b clearfix">
{{component.name}}
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.project_users.length > 0">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.project_users.length}}</span>
<h3 translate>{{ 'app.public.projects_show.collaborators' }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li class="list-group-item no-b clearfix block-link" ng-repeat="collaborator in project.project_users" ui-sref="app.logged.members_show({id: collaborator.slug})">
<span class="pull-left thumb-sm avatar m-r">
<fab-user-avatar ng-model="collaborator.user_avatar" avatar-class="thumb-38"></fab-user-avatar>
<i class="on b-white bottom" ng-if="collaborator.is_valid"></i>
<i class="off b-white bottom" ng-if="!collaborator.is_valid"></i>
</span>
<span class="clear"><span>{{collaborator.full_name}}</span>
<small class="text-muted clear text-ellipsis text-c">{{collaborator.username}}</small>
</span>
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.licence">
<div class="panel-heading b-b">
<h3 translate>{{ 'app.public.projects_show.licence' }}</h3>
</div>
<div class="panel-body">
{{ project.licence.name }}
</div>
</section>
<section class="widget panel b-a m" ng-if="project.tags">
<div class="panel-heading b-b">
<h3 translate>{{ 'app.shared.project.tags' }}</h3>
</div>
<div class="panel-body">
<pre>{{ project.tags }}</pre>
</div>
</section>
<section class="widget panel b-a m" ng-if="project.project_categories">
<div class="panel-heading b-b">
<h3 translate>{{ projectCategoriesWording }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="projectCategory in project.project_categories" class="list-group-item no-b clearfix">
{{projectCategory.name}}
</li>
</ul>
</section>
<div class="text-center m m-b-lg" ng-if="projectEditableBy(currentUser) || isAuthorized('admin') || isAuthorized('manager')">
<a class="btn bg-light text-black" ng-href="api/projects/{{ project.id}}/markdown" target="_blank">
<i class="fa fa-download"></i> {{ 'app.public.projects_show.markdown_file' | translate }}
</a> </a>
<span ng-hide="project.author_id" class="text-sm font-sbold text-gray" translate>{{ 'app.public.projects_show.deleted_user' }}</span>
</div> </div>
<small class="text-xs m-b"><i>{{ 'app.public.projects_show.posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}}</i></small>
<div class="m" ng-if="project.themes">
<span ng-repeat="theme in project.themes" class="badge m-r-sm">
{{theme.name}}
</span>
</div>
</div>
<section class="widget panel b-a m" ng-if="project.project_caos_attributes">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.project_caos_attributes.length}}</span>
<h3 translate translate-values="{COUNT:project.project_caos_attributes.length}">{{ 'app.public.projects_show.CAD_file_to_download' }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="file in project.project_caos_attributes" class="list-group-item no-b clearfix">
<a target="_blank" ng-href="{{file.attachment_url}}" download="{{file.attachment_url}}"><i class="fa fa-arrow-circle-o-down"> </i> {{file.attachment | humanize : 25}}</a>
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.status">
<div class="panel-heading b-b">
<h3 translate>{{ 'app.public.projects_show.status' }}</h3>
</div>
<div class="panel-body">
{{ project.status.name }}
</div>
</section>
<section class="widget panel b-a m" ng-if="project.machines">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.machines.length}}</span>
<h3 translate>{{ 'app.public.projects_show.machines_and_materials' }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="machine in project.machines" class="list-group-item no-b clearfix">
<a ui-sref="app.public.machines_show({id: machine.id})">{{machine.name}}</a>
</li>
</ul>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li ng-repeat="component in project.components" class="list-group-item no-b clearfix">
{{component.name}}
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.project_users.length > 0">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{project.project_users.length}}</span>
<h3 translate>{{ 'app.public.projects_show.collaborators' }}</h3>
</div>
<ul class="widget-content list-group list-group-lg no-bg auto">
<li class="list-group-item no-b clearfix block-link" ng-repeat="collaborator in project.project_users" ui-sref="app.logged.members_show({id: collaborator.slug})">
<span class="pull-left thumb-sm avatar m-r">
<fab-user-avatar ng-model="collaborator.user_avatar" avatar-class="thumb-38"></fab-user-avatar>
<i class="on b-white bottom" ng-if="collaborator.is_valid"></i>
<i class="off b-white bottom" ng-if="!collaborator.is_valid"></i>
</span>
<span class="clear"><span>{{collaborator.full_name}}</span>
<small class="text-muted clear text-ellipsis text-c">{{collaborator.username}}</small>
</span>
</li>
</ul>
</section>
<section class="widget panel b-a m" ng-if="project.licence">
<div class="panel-heading b-b">
<h3 translate>{{ 'app.public.projects_show.licence' }}</h3>
</div>
<div class="panel-body">
{{ project.licence.name }}
</div>
</section>
<section class="widget panel b-a m" ng-if="project.tags">
<div class="panel-heading b-b">
<h3 translate>{{ 'app.shared.project.tags' }}</h3>
</div>
<div class="panel-body">
<pre>{{ project.tags }}</pre>
</div>
</section>
<section class="widget b-t"> <section class="widget b-t">
<div class="widget-content text-center m-t">
<div class="widget-content text-center m-t"> <a ng-click="signalAbuse($event)"><i class="fa fa-warning"></i> {{ 'app.public.projects_show.report_an_abuse' | translate }}</a>
<a ng-click="signalAbuse($event)"><i class="fa fa-warning"></i> {{ 'app.public.projects_show.report_an_abuse' | translate }}</a> </div>
</div>
</section> </section>
</div>
</div> </div>
</div> </div>
</div>

View File

@ -49,7 +49,7 @@
<li><a ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li> <li><a ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li><a ui-sref="app.logged.dashboard.invoices" ng-show="$root.modules.invoicing" translate>{{ 'app.public.common.my_invoices' }}</a></li> <li><a ui-sref="app.logged.dashboard.invoices" ng-show="$root.modules.invoicing" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li><a ui-sref="app.logged.dashboard.payment_schedules" ng-show="$root.modules.invoicing" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li> <li><a ui-sref="app.logged.dashboard.payment_schedules" ng-show="$root.modules.invoicing" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>
<li><a ui-sref="app.logged.dashboard.orders" translate>{{ 'app.public.common.my_orders' }}</a></li> <li ng-if="$root.modules.store"><a ui-sref="app.logged.dashboard.orders" translate>{{ 'app.public.common.my_orders' }}</a></li>
<li ng-show="$root.modules.wallet"><a ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li> <li ng-show="$root.modules.wallet"><a ui-sref="app.logged.dashboard.wallet" translate>{{ 'app.public.common.my_wallet' }}</a></li>
<li class="divider" ng-if="isAuthorized(['admin', 'manager'])"></li> <li class="divider" ng-if="isAuthorized(['admin', 'manager'])"></li>
<li><a class="text-black pointer" ng-click="help($event)" ng-if="isAuthorized(['admin', 'manager'])"><i class="fa fa-question-circle"></i> <span translate>{{ 'app.public.common.help' }}</span> </a></li> <li><a class="text-black pointer" ng-click="help($event)" ng-if="isAuthorized(['admin', 'manager'])"><i class="fa fa-question-circle"></i> <span translate>{{ 'app.public.common.help' }}</span> </a></li>

View File

@ -198,6 +198,10 @@ module SettingsHelper
events_banner_cta_active events_banner_cta_active
events_banner_cta_label events_banner_cta_label
events_banner_cta_url events_banner_cta_url
projects_list_member_filter_presence
projects_list_date_filters_presence
project_categories_filter_placeholder
project_categories_wording
].freeze ].freeze
end end
# rubocop:enable Metrics/ModuleLength # rubocop:enable Metrics/ModuleLength

View File

@ -20,7 +20,7 @@ class Footprintable < ApplicationRecord
return false unless persisted? return false unless persisted?
reload reload
footprint_children.map(&:check_footprint).all? && !chained_element.corrupted? footprint_children.map(&:check_footprint).all? && chained_element && !chained_element.corrupted?
end end
# @return [ChainedElement] # @return [ChainedElement]

View File

@ -175,8 +175,8 @@ class Invoice < PaymentDocument
if paid_by_card? if paid_by_card?
{ {
payment_mean: mean, payment_mean: mean,
gateway_object_id: payment_gateway_object.gateway_object_id, gateway_object_id: payment_gateway_object&.gateway_object_id,
gateway_object_type: payment_gateway_object.gateway_object_type gateway_object_type: payment_gateway_object&.gateway_object_type
} }
end end
when :wallet when :wallet

View File

@ -41,6 +41,8 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :project_steps, allow_destroy: true accepts_nested_attributes_for :project_steps, allow_destroy: true
has_many :abuses, as: :signaled, dependent: :destroy, class_name: 'Abuse' has_many :abuses, as: :signaled, dependent: :destroy, class_name: 'Abuse'
has_many :projects_project_categories, dependent: :destroy
has_many :project_categories, through: :projects_project_categories
# validations # validations
validates :author, :name, presence: true validates :author, :name, presence: true
@ -68,6 +70,7 @@ class Project < ApplicationRecord
scope :with_component, ->(component_ids) { joins(:projects_components).where(projects_components: { component_id: component_ids }) } scope :with_component, ->(component_ids) { joins(:projects_components).where(projects_components: { component_id: component_ids }) }
scope :with_space, ->(spaces_ids) { joins(:projects_spaces).where(projects_spaces: { space_id: spaces_ids }) } scope :with_space, ->(spaces_ids) { joins(:projects_spaces).where(projects_spaces: { space_id: spaces_ids }) }
scope :with_status, ->(statuses_ids) { where(status_id: statuses_ids) } scope :with_status, ->(statuses_ids) { where(status_id: statuses_ids) }
scope :with_project_category, ->(project_category_ids) { joins(:projects_project_categories).where(projects_project_categories: { project_category_id: project_category_ids }) }
pg_search_scope :search, pg_search_scope :search,
against: :search_vector, against: :search_vector,
using: { using: {

View File

@ -0,0 +1,6 @@
class ProjectCategory < ApplicationRecord
validates :name, presence: true
has_many :projects_project_categories, dependent: :destroy
has_many :projects, through: :projects_project_categories
end

View File

@ -5,4 +5,6 @@ class ProjectStep < ApplicationRecord
belongs_to :project, touch: true belongs_to :project, touch: true
has_many :project_step_images, as: :viewable, dependent: :destroy has_many :project_step_images, as: :viewable, dependent: :destroy
accepts_nested_attributes_for :project_step_images, allow_destroy: true, reject_if: :all_blank accepts_nested_attributes_for :project_step_images, allow_destroy: true, reject_if: :all_blank
default_scope -> { order(:step_nb) }
end end

View File

@ -0,0 +1,4 @@
class ProjectsProjectCategory < ApplicationRecord
belongs_to :project
belongs_to :project_category
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
# Check if user is an admin to allow create, update and destroy project_category
class ProjectCategoryPolicy < ApplicationPolicy
def create?
user.admin?
end
def update?
create?
end
def destroy?
create?
end
end

View File

@ -16,10 +16,14 @@ class ProjectPolicy < ApplicationPolicy
end end
def update? def update?
user.admin? or record.author.user_id == user.id or record.users.include?(user) user.admin? || record.author.user_id == user.id || record.users.include?(user)
end
def markdown?
user.admin? || user.manager? || record.author.user_id == user.id || record.users.include?(user)
end end
def destroy? def destroy?
user.admin? or record.author.user_id == user.id user.admin? || record.author.user_id == user.id
end end
end end

View File

@ -46,7 +46,8 @@ class SettingPolicy < ApplicationPolicy
external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label external_id machines_banner_active machines_banner_text machines_banner_cta_active machines_banner_cta_label
machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label machines_banner_cta_url trainings_banner_active trainings_banner_text trainings_banner_cta_active trainings_banner_cta_label
trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active events_banner_cta_label
events_banner_cta_url family_account child_validation_required] events_banner_cta_url projects_list_member_filter_presence projects_list_date_filters_presence
project_categories_filter_placeholder project_categories_wording family_account child_validation_required]
end end
## ##

View File

@ -22,11 +22,19 @@ class Accounting::AccountingService
lines = [] lines = []
processed = [] processed = []
invoices.find_each do |i| invoices.find_each do |i|
Rails.logger.debug { "processing invoice #{i.id}..." } unless Rails.env.test? Rails.logger.debug { "[AccountLine] processing invoice #{i.id}..." } unless Rails.env.test?
lines.concat(generate_lines(i)) if i.main_item.nil?
processed.push(i.id) Rails.logger.error { "[AccountLine] invoice #{i.id} main_item is nil" } unless Rails.env.test?
else
lines.concat(generate_lines(i))
processed.push(i.id)
end
end
ActiveRecord::Base.transaction do
ids = invoices.map(&:id)
AccountingLine.where(invoice_id: ids).delete_all
AccountingLine.create!(lines)
end end
AccountingLine.create!(lines)
processed processed
end end

View File

@ -2,17 +2,17 @@
# Provides methods for Project # Provides methods for Project
class ProjectService class ProjectService
def search(params, current_user) def search(params, current_user, paginate: true)
connection = ActiveRecord::Base.connection connection = ActiveRecord::Base.connection
return { error: 'invalid adapter' } unless connection.instance_values['config'][:adapter] == 'postgresql' return { error: 'invalid adapter' } unless connection.instance_values['config'][:adapter] == 'postgresql'
search_from_postgre(params, current_user) search_from_postgre(params, current_user, paginate: paginate)
end end
private private
def search_from_postgre(params, current_user) def search_from_postgre(params, current_user, paginate: true)
query_params = JSON.parse(params[:search]) query_params = JSON.parse(params[:search] || "{}")
records = Project.published_or_drafts(current_user&.statistic_profile&.id) records = Project.published_or_drafts(current_user&.statistic_profile&.id)
records = Project.user_projects(current_user&.statistic_profile&.id) if query_params['from'] == 'mine' records = Project.user_projects(current_user&.statistic_profile&.id) if query_params['from'] == 'mine'
@ -21,14 +21,32 @@ class ProjectService
records = records.with_machine(query_params['machine_id']) if query_params['machine_id'].present? records = records.with_machine(query_params['machine_id']) if query_params['machine_id'].present?
records = records.with_component(query_params['component_id']) if query_params['component_id'].present? records = records.with_component(query_params['component_id']) if query_params['component_id'].present?
records = records.with_theme(query_params['theme_id']) if query_params['theme_id'].present? records = records.with_theme(query_params['theme_id']) if query_params['theme_id'].present?
records = records.with_project_category(query_params['project_category_id']) if query_params['project_category_id'].present?
records = records.with_space(query_params['space_id']) if query_params['space_id'].present? records = records.with_space(query_params['space_id']) if query_params['space_id'].present?
records = records.with_status(query_params['status_id']) if query_params['status_id'].present? records = records.with_status(query_params['status_id']) if query_params['status_id'].present?
if query_params['member_id'].present?
member = User.find(query_params['member_id'])
if member
records = records.where(id: Project.user_projects(member.statistic_profile.id)).or(Project.where(id: Project.collaborations(member.id)))
end
end
created_from = Time.zone.parse(query_params['from_date']).beginning_of_day if query_params['from_date'].present?
created_to = Time.zone.parse(query_params['to_date']).end_of_day if query_params['to_date'].present?
if created_from || created_to
records = records.where(created_at: created_from..created_to)
end
records = if query_params['q'].present? records = if query_params['q'].present?
records.search(query_params['q']) records.search(query_params['q'])
else else
records.order(created_at: :desc) records.order(created_at: :desc)
end end
{ total: records.count, projects: records.includes(:users, :project_image).page(params[:page]) } records = records.includes(:users, :project_image)
records = records.page(params[:page]) if paginate
{ total: records.count, projects: records }
end end
end end

View File

@ -0,0 +1,88 @@
class ProjectToMarkdown
attr_reader :project
def initialize(project)
@project = project
end
def call
md = []
md << "# #{project.name}"
md << "![#{I18n.t('app.shared.project.illustration')}](#{full_url(project.project_image.attachment.url)})" if project.project_image
md << ReverseMarkdown.convert(project.description.to_s)
project_steps = project.project_steps
if project_steps.present?
md << "## #{I18n.t('app.shared.project.steps')}"
project_steps.each do |project_step|
md << "### #{I18n.t('app.shared.project.step_N').gsub('{INDEX}', project_step.step_nb.to_s)} : #{project_step.title}"
md << ReverseMarkdown.convert(project_step.description.to_s)
project_step.project_step_images.each_with_index do |image, i|
md << "![#{I18n.t('app.shared.project.step_image')} #{i+1}](#{full_url(project.project_image.attachment.url)})"
end
end
end
md << "## #{I18n.t('app.shared.project.author')}"
md << project.author&.user&.profile&.full_name
if project.themes.present?
md << "## #{I18n.t('app.shared.project.themes')}"
md << project.themes.map(&:name).join(', ')
end
if project.project_caos.present?
md << "## #{I18n.t('app.shared.project.CAD_files')}"
project.project_caos.each do |cao|
md << "![#{cao.attachment_identifier}](#{full_url(cao.attachment_url)})"
end
end
if project.status
md << "## #{I18n.t('app.shared.project.status')}"
md << project.status.name
end
if project.machines.present?
md << "## #{I18n.t('app.shared.project.employed_machines')}"
md << project.machines.map(&:name).join(', ')
end
if project.components.present?
md << "## #{I18n.t('app.shared.project.employed_materials')}"
md << project.components.map(&:name).join(', ')
end
if project.users.present?
md << "## #{I18n.t('app.shared.project.collaborators')}"
md << project.users.map { |u| u.profile.full_name }.join(', ')
end
if project.licence.present?
md << "## #{I18n.t('app.shared.project.licence')}"
md << project.licence.name
end
if project.tags.present?
md << "## #{I18n.t('app.shared.project.tags')}"
md << project.tags
end
md = md.reject { |line| line.blank? }
md.join("\n\n")
end
private
def full_url(path)
"#{Rails.application.routes.url_helpers.root_url[...-1]}#{path}"
end
end

View File

@ -0,0 +1,22 @@
class ProjectsArchive
attr_reader :projects
def initialize(projects)
@projects = projects
end
def call
stringio = Zip::OutputStream.write_buffer do |zio|
projects.includes(:project_image, :themes,
:project_caos, :status, :machines,
:components, :licence,
project_steps: :project_step_images,
author: { user: :profile },
users: :profile).find_each do |project|
zio.put_next_entry("#{project.name.parameterize}-#{project.id}.md")
zio.write ProjectToMarkdown.new(project).call
end
end
stringio.string
end
end

View File

@ -14,8 +14,8 @@ if payment_schedule.operator_profile
end end
end end
json.main_object do json.main_object do
json.type payment_schedule.main_object.object_type json.type payment_schedule.main_object&.object_type
json.id payment_schedule.main_object.object_id json.id payment_schedule.main_object&.object_id
end end
if payment_schedule.gateway_subscription if payment_schedule.gateway_subscription
# this attribute is used to known which gateway should we interact with, in the front-end # this attribute is used to known which gateway should we interact with, in the front-end

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
json.array!(@project_categories) do |project_category|
json.extract! project_category, :id, :name
end

View File

@ -7,6 +7,7 @@ json.user_ids project.user_ids
json.machine_ids project.machine_ids json.machine_ids project.machine_ids
json.theme_ids project.theme_ids json.theme_ids project.theme_ids
json.component_ids project.component_ids json.component_ids project.component_ids
json.project_category_ids project.project_category_ids
json.tags project.tags json.tags project.tags
json.name project.name json.name project.name
json.description project.description json.description project.description

View File

@ -39,6 +39,11 @@ json.themes @project.themes do |t|
json.id t.id json.id t.id
json.name t.name json.name t.name
end end
json.project_category_ids @project.project_category_ids
json.project_categories @project.project_categories do |t|
json.id t.id
json.name t.name
end
json.user_ids @project.user_ids json.user_ids @project.user_ids
json.project_users @project.project_users do |pu| json.project_users @project.project_users do |pu|
json.id pu.user.id json.id pu.user.id

View File

@ -27,8 +27,6 @@ class AccountingWorker
end end
def invoices(invoices_ids) def invoices(invoices_ids)
# clean
AccountingLine.where(invoice_id: invoices_ids).delete_all
# build # build
service = Accounting::AccountingService.new service = Accounting::AccountingService.new
invoices = Invoice.where(id: invoices_ids) invoices = Invoice.where(id: invoices_ids)
@ -37,8 +35,6 @@ class AccountingWorker
end end
def all def all
# clean
AccountingLine.delete_all
# build # build
service = Accounting::AccountingService.new service = Accounting::AccountingService.new
ids = service.build_from_invoices(Invoice.all) ids = service.build_from_invoices(Invoice.all)

View File

@ -415,6 +415,8 @@ de:
add_a_material: "Materialien hinfügen" add_a_material: "Materialien hinfügen"
themes: "Themen" themes: "Themen"
add_a_new_theme: "Neues Thema hinzufügen" add_a_new_theme: "Neues Thema hinzufügen"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Lizenzen" licences: "Lizenzen"
statuses: "Statuses" statuses: "Statuses"
description: "Beschreibung" description: "Beschreibung"
@ -445,6 +447,10 @@ de:
open_lab_app_secret: "Geheimnis" open_lab_app_secret: "Geheimnis"
openlab_default_info_html: "In der Projektgalerie können Besucher zwischen zwei Ansichten wechseln: alle gemeinsam geteilten Projekte des OpenLab-Netzwerkes oder nur die in Ihrem FabLab dokumentierten Projekte.<br/>Hier können Sie die standardmäßig angezeigte Ansicht auswählen." openlab_default_info_html: "In der Projektgalerie können Besucher zwischen zwei Ansichten wechseln: alle gemeinsam geteilten Projekte des OpenLab-Netzwerkes oder nur die in Ihrem FabLab dokumentierten Projekte.<br/>Hier können Sie die standardmäßig angezeigte Ansicht auswählen."
default_to_openlab: "OpenLab standardmäßig anzeigen" default_to_openlab: "OpenLab standardmäßig anzeigen"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Hinzufügen" add: "Hinzufügen"
actions_controls: "Actions" actions_controls: "Actions"
@ -1773,6 +1779,10 @@ de:
extended_prices_in_same_day: "Erweiterte Preise am selben Tag" extended_prices_in_same_day: "Erweiterte Preise am selben Tag"
public_registrations: "Öffentliche Registrierungen" public_registrations: "Öffentliche Registrierungen"
show_username_in_admin_list: "Show the username in the list" show_username_in_admin_list: "Show the username in the list"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
overlapping_options: overlapping_options:
training_reservations: "Schulungen" training_reservations: "Schulungen"
machine_reservations: "Maschinen" machine_reservations: "Maschinen"

View File

@ -423,6 +423,8 @@ en:
add_a_material: "Add a material" add_a_material: "Add a material"
themes: "Themes" themes: "Themes"
add_a_new_theme: "Add a new theme" add_a_new_theme: "Add a new theme"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Licences" licences: "Licences"
statuses: "Statuses" statuses: "Statuses"
description: "Description" description: "Description"
@ -453,6 +455,10 @@ en:
open_lab_app_secret: "Secret" open_lab_app_secret: "Secret"
openlab_default_info_html: "In the projects gallery, visitors can switch between two views: all shared projects from the whole OpenLab network, or only the projects documented in your Fab Lab.<br/>Here, you can choose which view is shown by default." openlab_default_info_html: "In the projects gallery, visitors can switch between two views: all shared projects from the whole OpenLab network, or only the projects documented in your Fab Lab.<br/>Here, you can choose which view is shown by default."
default_to_openlab: "Display OpenLab by default" default_to_openlab: "Display OpenLab by default"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Add" add: "Add"
actions_controls: "Actions" actions_controls: "Actions"
@ -1823,6 +1829,10 @@ en:
extended_prices_in_same_day: "Extended prices in the same day" extended_prices_in_same_day: "Extended prices in the same day"
public_registrations: "Public registrations" public_registrations: "Public registrations"
show_username_in_admin_list: "Show the username in the list" show_username_in_admin_list: "Show the username in the list"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
family_account: "family account" family_account: "family account"
family_account_info_html: "The Family account allows your members to add their children under 18 years old to their own account and directly register them for Family events. You can also request supporting documents for each child and validate their account." family_account_info_html: "The Family account allows your members to add their children under 18 years old to their own account and directly register them for Family events. You can also request supporting documents for each child and validate their account."
enable_family_account: "Enable the Family Account option" enable_family_account: "Enable the Family Account option"

View File

@ -415,6 +415,8 @@ es:
add_a_material: "Añadir un material" add_a_material: "Añadir un material"
themes: "Temas" themes: "Temas"
add_a_new_theme: "Añadir un nuevo tema" add_a_new_theme: "Añadir un nuevo tema"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Licencias" licences: "Licencias"
statuses: "Statuses" statuses: "Statuses"
description: "Descripción" description: "Descripción"
@ -445,6 +447,10 @@ es:
open_lab_app_secret: "Secret" open_lab_app_secret: "Secret"
openlab_default_info_html: "In the projects gallery, visitors can switch between two views: all shared projects from the whole OpenLab network, or only the projects documented in your Fab Lab.<br/>Here, you can choose which view is shown by default." openlab_default_info_html: "In the projects gallery, visitors can switch between two views: all shared projects from the whole OpenLab network, or only the projects documented in your Fab Lab.<br/>Here, you can choose which view is shown by default."
default_to_openlab: "Display OpenLab by default" default_to_openlab: "Display OpenLab by default"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Add" add: "Add"
actions_controls: "Actions" actions_controls: "Actions"
@ -1773,6 +1779,10 @@ es:
extended_prices_in_same_day: "Extended prices in the same day" extended_prices_in_same_day: "Extended prices in the same day"
public_registrations: "Public registrations" public_registrations: "Public registrations"
show_username_in_admin_list: "Show the username in the list" show_username_in_admin_list: "Show the username in the list"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
overlapping_options: overlapping_options:
training_reservations: "Trainings" training_reservations: "Trainings"
machine_reservations: "Machines" machine_reservations: "Machines"

View File

@ -423,6 +423,8 @@ fr:
add_a_material: "Ajouter un matériau" add_a_material: "Ajouter un matériau"
themes: "Thématiques" themes: "Thématiques"
add_a_new_theme: "Ajouter une nouvelle thématique" add_a_new_theme: "Ajouter une nouvelle thématique"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Licences" licences: "Licences"
statuses: "Statuts" statuses: "Statuts"
description: "Description" description: "Description"
@ -453,6 +455,10 @@ fr:
open_lab_app_secret: "Secret" open_lab_app_secret: "Secret"
openlab_default_info_html: "Dans la galerie de projets, les visiteurs peuvent choisir entre deux vues : tous les projets de l'ensemble du réseau OpenLab, ou uniquement les projets documentés dans votre Fab Lab.<br/>Ici, vous pouvez choisir quelle vue est affichée par défaut." openlab_default_info_html: "Dans la galerie de projets, les visiteurs peuvent choisir entre deux vues : tous les projets de l'ensemble du réseau OpenLab, ou uniquement les projets documentés dans votre Fab Lab.<br/>Ici, vous pouvez choisir quelle vue est affichée par défaut."
default_to_openlab: "Afficher OpenLab par défaut" default_to_openlab: "Afficher OpenLab par défaut"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Ajouter" add: "Ajouter"
actions_controls: "Actions" actions_controls: "Actions"
@ -1815,6 +1821,10 @@ fr:
extended_prices_in_same_day: "Prix étendus le même jour" extended_prices_in_same_day: "Prix étendus le même jour"
public_registrations: "Inscriptions publiques" public_registrations: "Inscriptions publiques"
show_username_in_admin_list: "Afficher le nom d'utilisateur dans la liste" show_username_in_admin_list: "Afficher le nom d'utilisateur dans la liste"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
family_account: "Compte famille" family_account: "Compte famille"
family_account_info_html: "Le compte Famille permet à vos membres d'ajouter leurs enfants de moins de 18 ans sur leur propre compte et de les inscrire directement aux évènements de type Famille. Vous pouvez aussi demander des justificatifs pour chaque enfant et valider leur compte." family_account_info_html: "Le compte Famille permet à vos membres d'ajouter leurs enfants de moins de 18 ans sur leur propre compte et de les inscrire directement aux évènements de type Famille. Vous pouvez aussi demander des justificatifs pour chaque enfant et valider leur compte."
enable_family_account: "Activer l'option Compte Famille" enable_family_account: "Activer l'option Compte Famille"

View File

@ -415,6 +415,8 @@ it:
add_a_material: "Aggiungi un materiale" add_a_material: "Aggiungi un materiale"
themes: "Temi" themes: "Temi"
add_a_new_theme: "Aggiungi un nuovo tema" add_a_new_theme: "Aggiungi un nuovo tema"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Licenze" licences: "Licenze"
statuses: "Status" statuses: "Status"
description: "Descrizione" description: "Descrizione"
@ -445,6 +447,10 @@ it:
open_lab_app_secret: "Segreto" open_lab_app_secret: "Segreto"
openlab_default_info_html: "Nella galleria di progetti, i visitatori possono scegliere tra due viste: tutti i progetti condivisi da tutta la rete di OpenLab, o solo i progetti documentati nel tuo Fab Lab.<br/>Qui, puoi scegliere quale vista è mostrata per impostazione predefinita." openlab_default_info_html: "Nella galleria di progetti, i visitatori possono scegliere tra due viste: tutti i progetti condivisi da tutta la rete di OpenLab, o solo i progetti documentati nel tuo Fab Lab.<br/>Qui, puoi scegliere quale vista è mostrata per impostazione predefinita."
default_to_openlab: "Visualizza OpenLab per impostazione predefinita" default_to_openlab: "Visualizza OpenLab per impostazione predefinita"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Aggiungi" add: "Aggiungi"
actions_controls: "Azioni" actions_controls: "Azioni"
@ -1773,6 +1779,10 @@ it:
extended_prices_in_same_day: "Prezzi estesi nello stesso giorno" extended_prices_in_same_day: "Prezzi estesi nello stesso giorno"
public_registrations: "Registri pubblici" public_registrations: "Registri pubblici"
show_username_in_admin_list: "Mostra il nome utente nella lista" show_username_in_admin_list: "Mostra il nome utente nella lista"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
overlapping_options: overlapping_options:
training_reservations: "Abilitazioni" training_reservations: "Abilitazioni"
machine_reservations: "Macchine" machine_reservations: "Macchine"

View File

@ -415,6 +415,8 @@
add_a_material: "Legg til et materiale" add_a_material: "Legg til et materiale"
themes: "Temaer" themes: "Temaer"
add_a_new_theme: "Legge til et nytt tema" add_a_new_theme: "Legge til et nytt tema"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Lisenser" licences: "Lisenser"
statuses: "Statuses" statuses: "Statuses"
description: "Beskrivelse" description: "Beskrivelse"
@ -445,6 +447,10 @@
open_lab_app_secret: "Hemmelighet" open_lab_app_secret: "Hemmelighet"
openlab_default_info_html: "I prosjektgalleriet kan besøkende bytte mellom to visninger: alle delte projetter fra hele OpenLab-nettverket. eller bare prosjektene som er dokumentert i din Fab Lab.<br/>Her kan du velge hvilken visning som standard." openlab_default_info_html: "I prosjektgalleriet kan besøkende bytte mellom to visninger: alle delte projetter fra hele OpenLab-nettverket. eller bare prosjektene som er dokumentert i din Fab Lab.<br/>Her kan du velge hvilken visning som standard."
default_to_openlab: "Vis OpenLab som standard" default_to_openlab: "Vis OpenLab som standard"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Add" add: "Add"
actions_controls: "Actions" actions_controls: "Actions"
@ -1773,6 +1779,10 @@
extended_prices_in_same_day: "Extended prices in the same day" extended_prices_in_same_day: "Extended prices in the same day"
public_registrations: "Public registrations" public_registrations: "Public registrations"
show_username_in_admin_list: "Show the username in the list" show_username_in_admin_list: "Show the username in the list"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
overlapping_options: overlapping_options:
training_reservations: "Trainings" training_reservations: "Trainings"
machine_reservations: "Machines" machine_reservations: "Machines"

View File

@ -415,6 +415,8 @@ pt:
add_a_material: "Adicionar material" add_a_material: "Adicionar material"
themes: "Temas" themes: "Temas"
add_a_new_theme: "Adicionar um novo tema" add_a_new_theme: "Adicionar um novo tema"
project_categories: "Categories"
add_a_new_project_category: "Add a new category"
licences: "Licenças" licences: "Licenças"
statuses: "Statuses" statuses: "Statuses"
description: "Descrição" description: "Descrição"
@ -445,6 +447,10 @@ pt:
open_lab_app_secret: "Senha" open_lab_app_secret: "Senha"
openlab_default_info_html: "Na galeria de projetos, os visitantes podem alternar entre duas visualizações: todos os projetos compartilhados de toda a rede OpenLab, ou apenas os projetos documentados no seu Fab Lab.<br/>Aqui, você pode escolher qual exibição é mostrada por padrão." openlab_default_info_html: "Na galeria de projetos, os visitantes podem alternar entre duas visualizações: todos os projetos compartilhados de toda a rede OpenLab, ou apenas os projetos documentados no seu Fab Lab.<br/>Aqui, você pode escolher qual exibição é mostrada por padrão."
default_to_openlab: "Mostrar OpenLab por padrão" default_to_openlab: "Mostrar OpenLab por padrão"
filters: Projects list filters
project_categories: Categories
project_categories:
name: "Name"
projects_setting: projects_setting:
add: "Add" add: "Add"
actions_controls: "Actions" actions_controls: "Actions"
@ -1773,6 +1779,10 @@ pt:
extended_prices_in_same_day: "Preços estendidos no mesmo dia" extended_prices_in_same_day: "Preços estendidos no mesmo dia"
public_registrations: "Inscrições públicas" public_registrations: "Inscrições públicas"
show_username_in_admin_list: "Mostrar o nome de usuário na lista" show_username_in_admin_list: "Mostrar o nome de usuário na lista"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of date filters on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
overlapping_options: overlapping_options:
training_reservations: "Treinamentos" training_reservations: "Treinamentos"
machine_reservations: "Máquinas" machine_reservations: "Máquinas"

View File

@ -415,6 +415,8 @@ zu:
add_a_material: "crwdns24306:0crwdne24306:0" add_a_material: "crwdns24306:0crwdne24306:0"
themes: "crwdns24308:0crwdne24308:0" themes: "crwdns24308:0crwdne24308:0"
add_a_new_theme: "crwdns24310:0crwdne24310:0" add_a_new_theme: "crwdns24310:0crwdne24310:0"
project_categories: "crwdns37617:0crwdne37617:0"
add_a_new_project_category: "crwdns37619:0crwdne37619:0"
licences: "crwdns24312:0crwdne24312:0" licences: "crwdns24312:0crwdne24312:0"
statuses: "crwdns36893:0crwdne36893:0" statuses: "crwdns36893:0crwdne36893:0"
description: "crwdns24314:0crwdne24314:0" description: "crwdns24314:0crwdne24314:0"
@ -445,6 +447,10 @@ zu:
open_lab_app_secret: "crwdns24362:0crwdne24362:0" open_lab_app_secret: "crwdns24362:0crwdne24362:0"
openlab_default_info_html: "crwdns37609:0crwdne37609:0" openlab_default_info_html: "crwdns37609:0crwdne37609:0"
default_to_openlab: "crwdns24366:0crwdne24366:0" default_to_openlab: "crwdns24366:0crwdne24366:0"
filters: crwdns37621:0crwdne37621:0
project_categories: crwdns37623:0crwdne37623:0
project_categories:
name: "crwdns37625:0crwdne37625:0"
projects_setting: projects_setting:
add: "crwdns36895:0crwdne36895:0" add: "crwdns36895:0crwdne36895:0"
actions_controls: "crwdns36897:0crwdne36897:0" actions_controls: "crwdns36897:0crwdne36897:0"
@ -1773,6 +1779,10 @@ zu:
extended_prices_in_same_day: "crwdns26752:0crwdne26752:0" extended_prices_in_same_day: "crwdns26752:0crwdne26752:0"
public_registrations: "crwdns26754:0crwdne26754:0" public_registrations: "crwdns26754:0crwdne26754:0"
show_username_in_admin_list: "crwdns26756:0crwdne26756:0" show_username_in_admin_list: "crwdns26756:0crwdne26756:0"
projects_list_member_filter_presence: "crwdns37627:0crwdne37627:0"
projects_list_date_filters_presence: "crwdns37629:0crwdne37629:0"
project_categories_filter_placeholder: "crwdns37631:0crwdne37631:0"
project_categories_wording: "crwdns37633:0crwdne37633:0"
overlapping_options: overlapping_options:
training_reservations: "crwdns26758:0crwdne26758:0" training_reservations: "crwdns26758:0crwdne26758:0"
machine_reservations: "crwdns26760:0crwdne26760:0" machine_reservations: "crwdns26760:0crwdne26760:0"

View File

@ -167,6 +167,7 @@ de:
full_price: "Voller Preis: " full_price: "Voller Preis: "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "The projects" the_fablab_projects: "The projects"
add_a_project: "Projekt hinzufügen" add_a_project: "Projekt hinzufügen"
network_search: "Fab-manager network" network_search: "Fab-manager network"
@ -183,6 +184,10 @@ de:
all_materials: "Alle Materialien" all_materials: "Alle Materialien"
load_next_projects: "Nächste Projekte laden" load_next_projects: "Nächste Projekte laden"
rough_draft: "Grober Entwurf" rough_draft: "Grober Entwurf"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "All statuses" all_statuses: "All statuses"
select_status: "Select a status" select_status: "Select a status"
@ -216,6 +221,7 @@ de:
report: "Melden" report: "Melden"
do_you_really_want_to_delete_this_project: "Wollen Sie dieses Projekt wirklich löschen?" do_you_really_want_to_delete_this_project: "Wollen Sie dieses Projekt wirklich löschen?"
status: "Status" status: "Status"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "The machines" the_fablab_s_machines: "The machines"

View File

@ -168,6 +168,7 @@ en:
full_price: "Full price: " full_price: "Full price: "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "The projects" the_fablab_projects: "The projects"
add_a_project: "Add a project" add_a_project: "Add a project"
network_search: "Fab-manager network" network_search: "Fab-manager network"
@ -184,6 +185,10 @@ en:
all_materials: "All materials" all_materials: "All materials"
load_next_projects: "Load next projects" load_next_projects: "Load next projects"
rough_draft: "Rough draft" rough_draft: "Rough draft"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "All statuses" all_statuses: "All statuses"
select_status: "Select a status" select_status: "Select a status"
@ -217,6 +222,7 @@ en:
report: "Report" report: "Report"
do_you_really_want_to_delete_this_project: "Do you really want to delete this project?" do_you_really_want_to_delete_this_project: "Do you really want to delete this project?"
status: "Status" status: "Status"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "The machines" the_fablab_s_machines: "The machines"

View File

@ -167,6 +167,7 @@ es:
full_price: "Full price: " full_price: "Full price: "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "The projects" the_fablab_projects: "The projects"
add_a_project: "Añadir un proyecto" add_a_project: "Añadir un proyecto"
network_search: "Fab-manager network" network_search: "Fab-manager network"
@ -183,6 +184,10 @@ es:
all_materials: "Todo el material" all_materials: "Todo el material"
load_next_projects: "Cargar más proyectos" load_next_projects: "Cargar más proyectos"
rough_draft: "Borrador" rough_draft: "Borrador"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "All statuses" all_statuses: "All statuses"
select_status: "Select a status" select_status: "Select a status"
@ -216,6 +221,7 @@ es:
report: "Reportar" report: "Reportar"
do_you_really_want_to_delete_this_project: "¿Está seguro de querer eliminar este proyecto?" do_you_really_want_to_delete_this_project: "¿Está seguro de querer eliminar este proyecto?"
status: "Status" status: "Status"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "The machines" the_fablab_s_machines: "The machines"

View File

@ -168,6 +168,7 @@ fr:
full_price: "Plein tarif : " full_price: "Plein tarif : "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "Les projets" the_fablab_projects: "Les projets"
add_a_project: "Ajouter un projet" add_a_project: "Ajouter un projet"
network_search: "Réseau Fab-Manager" network_search: "Réseau Fab-Manager"
@ -184,6 +185,10 @@ fr:
all_materials: "Tous les matériaux" all_materials: "Tous les matériaux"
load_next_projects: "Charger les projets suivants" load_next_projects: "Charger les projets suivants"
rough_draft: "Brouillon" rough_draft: "Brouillon"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "Tous les statuts" all_statuses: "Tous les statuts"
select_status: "Sélectionnez un statut" select_status: "Sélectionnez un statut"
@ -217,6 +222,7 @@ fr:
report: "Signaler" report: "Signaler"
do_you_really_want_to_delete_this_project: "Êtes-vous sur de vouloir supprimer ce projet ?" do_you_really_want_to_delete_this_project: "Êtes-vous sur de vouloir supprimer ce projet ?"
status: "Statut" status: "Statut"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "Les machines" the_fablab_s_machines: "Les machines"
@ -366,7 +372,7 @@ fr:
last_name_and_first_name: "Nom et prénom" last_name_and_first_name: "Nom et prénom"
pre_book: "Pré-inscrire" pre_book: "Pré-inscrire"
pre_registration_end_date: "Date limite de pré-inscription" pre_registration_end_date: "Date limite de pré-inscription"
pre_registration: "Pré-réservation" pre_registration: "Pré-inscription"
#public calendar #public calendar
calendar: calendar:
calendar: "Calendrier" calendar: "Calendrier"

View File

@ -167,6 +167,7 @@ it:
full_price: "Prezzo intero: " full_price: "Prezzo intero: "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "Progetti" the_fablab_projects: "Progetti"
add_a_project: "Aggiungi un progetto" add_a_project: "Aggiungi un progetto"
network_search: "Fab-manager network" network_search: "Fab-manager network"
@ -183,6 +184,10 @@ it:
all_materials: "Tutti i materiali" all_materials: "Tutti i materiali"
load_next_projects: "Carica i progetti successivi" load_next_projects: "Carica i progetti successivi"
rough_draft: "Bozza preliminare" rough_draft: "Bozza preliminare"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "Tutti gli stati" all_statuses: "Tutti gli stati"
select_status: "Seleziona uno status" select_status: "Seleziona uno status"
@ -216,6 +221,7 @@ it:
report: "Segnalazione" report: "Segnalazione"
do_you_really_want_to_delete_this_project: "Vuoi davvero eliminare questo progetto?" do_you_really_want_to_delete_this_project: "Vuoi davvero eliminare questo progetto?"
status: "Stato" status: "Stato"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "Le macchine" the_fablab_s_machines: "Le macchine"

View File

@ -167,6 +167,7 @@
full_price: "Full pris: " full_price: "Full pris: "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "The projects" the_fablab_projects: "The projects"
add_a_project: "Legg til et prosjekt" add_a_project: "Legg til et prosjekt"
network_search: "Fab-manager network" network_search: "Fab-manager network"
@ -183,6 +184,10 @@
all_materials: "Alle materialer" all_materials: "Alle materialer"
load_next_projects: "Last neste prosjekt" load_next_projects: "Last neste prosjekt"
rough_draft: "Tidlig utkast" rough_draft: "Tidlig utkast"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "All statuses" all_statuses: "All statuses"
select_status: "Select a status" select_status: "Select a status"
@ -216,6 +221,7 @@
report: "Rapporter" report: "Rapporter"
do_you_really_want_to_delete_this_project: "Vil du virkelig slette dette prosjektet?" do_you_really_want_to_delete_this_project: "Vil du virkelig slette dette prosjektet?"
status: "Status" status: "Status"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "The machines" the_fablab_s_machines: "The machines"

View File

@ -167,6 +167,7 @@ pt:
full_price: "Valor inteira: " full_price: "Valor inteira: "
#projects gallery #projects gallery
projects_list: projects_list:
filter: Filter
the_fablab_projects: "Os projetos" the_fablab_projects: "Os projetos"
add_a_project: "Adicionar projeto" add_a_project: "Adicionar projeto"
network_search: "Rede Fab-manager" network_search: "Rede Fab-manager"
@ -183,6 +184,10 @@ pt:
all_materials: "Todos os materiais" all_materials: "Todos os materiais"
load_next_projects: "Carregar próximos projetos" load_next_projects: "Carregar próximos projetos"
rough_draft: "Rascunho" rough_draft: "Rascunho"
filter_by_member: "Filter by member"
created_from: Created from
created_to: Created to
download_archive: Download
status_filter: status_filter:
all_statuses: "Todos os status" all_statuses: "Todos os status"
select_status: "Selecione um status" select_status: "Selecione um status"
@ -216,6 +221,7 @@ pt:
report: "Enviar" report: "Enviar"
do_you_really_want_to_delete_this_project: "Você quer realmente deletar esse projeto?" do_you_really_want_to_delete_this_project: "Você quer realmente deletar esse projeto?"
status: "Status" status: "Status"
markdown_file: "Markdown file"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "As máquinas" the_fablab_s_machines: "As máquinas"

View File

@ -167,6 +167,7 @@ zu:
full_price: "crwdns28058:0crwdne28058:0" full_price: "crwdns28058:0crwdne28058:0"
#projects gallery #projects gallery
projects_list: projects_list:
filter: crwdns37635:0crwdne37635:0
the_fablab_projects: "crwdns36237:0crwdne36237:0" the_fablab_projects: "crwdns36237:0crwdne36237:0"
add_a_project: "crwdns28062:0crwdne28062:0" add_a_project: "crwdns28062:0crwdne28062:0"
network_search: "crwdns37071:0crwdne37071:0" network_search: "crwdns37071:0crwdne37071:0"
@ -183,6 +184,10 @@ zu:
all_materials: "crwdns28088:0crwdne28088:0" all_materials: "crwdns28088:0crwdne28088:0"
load_next_projects: "crwdns28090:0crwdne28090:0" load_next_projects: "crwdns28090:0crwdne28090:0"
rough_draft: "crwdns28092:0crwdne28092:0" rough_draft: "crwdns28092:0crwdne28092:0"
filter_by_member: "crwdns37637:0crwdne37637:0"
created_from: crwdns37639:0crwdne37639:0
created_to: crwdns37641:0crwdne37641:0
download_archive: crwdns37643:0crwdne37643:0
status_filter: status_filter:
all_statuses: "crwdns37073:0crwdne37073:0" all_statuses: "crwdns37073:0crwdne37073:0"
select_status: "crwdns37075:0crwdne37075:0" select_status: "crwdns37075:0crwdne37075:0"
@ -216,6 +221,7 @@ zu:
report: "crwdns28144:0crwdne28144:0" report: "crwdns28144:0crwdne28144:0"
do_you_really_want_to_delete_this_project: "crwdns28146:0crwdne28146:0" do_you_really_want_to_delete_this_project: "crwdns28146:0crwdne28146:0"
status: "crwdns37077:0crwdne37077:0" status: "crwdns37077:0crwdne37077:0"
markdown_file: "crwdns37645:0crwdne37645:0"
#list of machines #list of machines
machines_list: machines_list:
the_fablab_s_machines: "crwdns36239:0crwdne36239:0" the_fablab_s_machines: "crwdns36239:0crwdne36239:0"

View File

@ -131,6 +131,7 @@ de:
illustration: "Ansicht" illustration: "Ansicht"
add_an_illustration: "Illustration hinzufügen" add_an_illustration: "Illustration hinzufügen"
CAD_file: "CAD-Datei" CAD_file: "CAD-Datei"
CAD_files: "CAD files"
allowed_extensions: "Zugelassene Dateitypen:" allowed_extensions: "Zugelassene Dateitypen:"
add_a_new_file: "Neue Datei hinzufügen" add_a_new_file: "Neue Datei hinzufügen"
description: "Beschreibung" description: "Beschreibung"
@ -138,6 +139,7 @@ de:
steps: "Schritte" steps: "Schritte"
step_N: "Schritt {INDEX}" step_N: "Schritt {INDEX}"
step_title: "Titel des Schrits" step_title: "Titel des Schrits"
step_image: "Image"
add_a_picture: "Ein Bild hinzufügen" add_a_picture: "Ein Bild hinzufügen"
change_the_picture: "Bild ändern" change_the_picture: "Bild ändern"
delete_the_step: "Diesen Schritt löschen" delete_the_step: "Diesen Schritt löschen"
@ -149,7 +151,9 @@ de:
employed_materials: "Verwendetes Material" employed_materials: "Verwendetes Material"
employed_machines: "Verwendete Maschinen" employed_machines: "Verwendete Maschinen"
collaborators: "Mitarbeitende" collaborators: "Mitarbeitende"
author: Author
creative_commons_licences: "Creative Commons-Lizenzen" creative_commons_licences: "Creative Commons-Lizenzen"
licence: "Licence"
themes: "Themen" themes: "Themen"
tags: "Stichwörter" tags: "Stichwörter"
save_as_draft: "Als Entwurf speichern" save_as_draft: "Als Entwurf speichern"

View File

@ -131,6 +131,7 @@ en:
illustration: "Visual" illustration: "Visual"
add_an_illustration: "Add an illustration" add_an_illustration: "Add an illustration"
CAD_file: "CAD file" CAD_file: "CAD file"
CAD_files: "CAD files"
allowed_extensions: "Allowed extensions:" allowed_extensions: "Allowed extensions:"
add_a_new_file: "Add a new file" add_a_new_file: "Add a new file"
description: "Description" description: "Description"
@ -138,6 +139,7 @@ en:
steps: "Steps" steps: "Steps"
step_N: "Step {INDEX}" step_N: "Step {INDEX}"
step_title: "Step title" step_title: "Step title"
step_image: "Image"
add_a_picture: "Add a picture" add_a_picture: "Add a picture"
change_the_picture: "Change the picture" change_the_picture: "Change the picture"
delete_the_step: "Delete the step" delete_the_step: "Delete the step"
@ -149,7 +151,9 @@ en:
employed_materials: "Employed materials" employed_materials: "Employed materials"
employed_machines: "Employed machines" employed_machines: "Employed machines"
collaborators: "Collaborators" collaborators: "Collaborators"
author: Author
creative_commons_licences: "Creative Commons licences" creative_commons_licences: "Creative Commons licences"
licence: "Licence"
themes: "Themes" themes: "Themes"
tags: "Tags" tags: "Tags"
save_as_draft: "Save as draft" save_as_draft: "Save as draft"

View File

@ -131,6 +131,7 @@ es:
illustration: "Ilustración" illustration: "Ilustración"
add_an_illustration: "Añadir una ilustración" add_an_illustration: "Añadir una ilustración"
CAD_file: "Fichero CAD" CAD_file: "Fichero CAD"
CAD_files: "CAD files"
allowed_extensions: "Extensiones permitidas:" allowed_extensions: "Extensiones permitidas:"
add_a_new_file: "Añadir un nuevo archivo" add_a_new_file: "Añadir un nuevo archivo"
description: "Description" description: "Description"
@ -138,6 +139,7 @@ es:
steps: "Pasos" steps: "Pasos"
step_N: "Step {INDEX}" step_N: "Step {INDEX}"
step_title: "Título de los pasos" step_title: "Título de los pasos"
step_image: "Image"
add_a_picture: "Añadir imagen" add_a_picture: "Añadir imagen"
change_the_picture: "Cambiar imagen" change_the_picture: "Cambiar imagen"
delete_the_step: "Eliminar el paso" delete_the_step: "Eliminar el paso"
@ -149,7 +151,9 @@ es:
employed_materials: "Material empleados" employed_materials: "Material empleados"
employed_machines: "Máquinas empleadas" employed_machines: "Máquinas empleadas"
collaborators: "Collaborators" collaborators: "Collaborators"
author: Author
creative_commons_licences: "Licencias Creative Commons" creative_commons_licences: "Licencias Creative Commons"
licence: "Licence"
themes: "Themes" themes: "Themes"
tags: "Tags" tags: "Tags"
save_as_draft: "Save as draft" save_as_draft: "Save as draft"

View File

@ -131,6 +131,7 @@ fr:
illustration: "Illustration" illustration: "Illustration"
add_an_illustration: "Ajouter un visuel" add_an_illustration: "Ajouter un visuel"
CAD_file: "Fichier CAO" CAD_file: "Fichier CAO"
CAD_files: "CAD files"
allowed_extensions: "Extensions autorisées :" allowed_extensions: "Extensions autorisées :"
add_a_new_file: "Ajouter un nouveau fichier" add_a_new_file: "Ajouter un nouveau fichier"
description: "Description" description: "Description"
@ -138,6 +139,7 @@ fr:
steps: "Étapes" steps: "Étapes"
step_N: "Étape {INDEX}" step_N: "Étape {INDEX}"
step_title: "Titre de l'étape" step_title: "Titre de l'étape"
step_image: "Image"
add_a_picture: "Ajouter une image" add_a_picture: "Ajouter une image"
change_the_picture: "Modifier l'image" change_the_picture: "Modifier l'image"
delete_the_step: "Supprimer l'étape" delete_the_step: "Supprimer l'étape"
@ -149,7 +151,9 @@ fr:
employed_materials: "Matériaux utilisés" employed_materials: "Matériaux utilisés"
employed_machines: "Machines utilisées" employed_machines: "Machines utilisées"
collaborators: "Les collaborateurs" collaborators: "Les collaborateurs"
author: Author
creative_commons_licences: "Licences Creative Commons" creative_commons_licences: "Licences Creative Commons"
licence: "Licence"
themes: "Thématiques" themes: "Thématiques"
tags: "Étiquettes" tags: "Étiquettes"
save_as_draft: "Enregistrer comme brouillon" save_as_draft: "Enregistrer comme brouillon"

View File

@ -131,6 +131,7 @@ it:
illustration: "Illustrazione" illustration: "Illustrazione"
add_an_illustration: "Aggiungi un'illustrazione" add_an_illustration: "Aggiungi un'illustrazione"
CAD_file: "File CAD" CAD_file: "File CAD"
CAD_files: "CAD files"
allowed_extensions: "Estensioni consentite:" allowed_extensions: "Estensioni consentite:"
add_a_new_file: "Aggiungi nuovo file" add_a_new_file: "Aggiungi nuovo file"
description: "Descrizione" description: "Descrizione"
@ -138,6 +139,7 @@ it:
steps: "Passaggi" steps: "Passaggi"
step_N: "Passaggio {INDEX}" step_N: "Passaggio {INDEX}"
step_title: "Titolo del passaggio" step_title: "Titolo del passaggio"
step_image: "Image"
add_a_picture: "Aggiungi un'immagine" add_a_picture: "Aggiungi un'immagine"
change_the_picture: "Cambia immagine" change_the_picture: "Cambia immagine"
delete_the_step: "Elimina il passaggio" delete_the_step: "Elimina il passaggio"
@ -149,7 +151,9 @@ it:
employed_materials: "Materiali impiegati" employed_materials: "Materiali impiegati"
employed_machines: "Macchine impiegate" employed_machines: "Macchine impiegate"
collaborators: "Collaboratori" collaborators: "Collaboratori"
author: Author
creative_commons_licences: "Licenze Creative Commons" creative_commons_licences: "Licenze Creative Commons"
licence: "Licence"
themes: "Temi" themes: "Temi"
tags: "Etichette" tags: "Etichette"
save_as_draft: "Salva come bozza" save_as_draft: "Salva come bozza"

View File

@ -131,6 +131,7 @@
illustration: "Bilde" illustration: "Bilde"
add_an_illustration: "Legg til en illustrasjon" add_an_illustration: "Legg til en illustrasjon"
CAD_file: "CAD-filer" CAD_file: "CAD-filer"
CAD_files: "CAD files"
allowed_extensions: "Tillatte filtyper:" allowed_extensions: "Tillatte filtyper:"
add_a_new_file: "Legg til ny fil" add_a_new_file: "Legg til ny fil"
description: "Beskrivelse" description: "Beskrivelse"
@ -138,6 +139,7 @@
steps: "Skritt" steps: "Skritt"
step_N: "Trinn {INDEX}" step_N: "Trinn {INDEX}"
step_title: "Tittel på steg" step_title: "Tittel på steg"
step_image: "Image"
add_a_picture: "Legg til bilde" add_a_picture: "Legg til bilde"
change_the_picture: "Endre bilde" change_the_picture: "Endre bilde"
delete_the_step: "Slett trinnet" delete_the_step: "Slett trinnet"
@ -149,7 +151,9 @@
employed_materials: "Materialer brukt" employed_materials: "Materialer brukt"
employed_machines: "Maskiner brukt" employed_machines: "Maskiner brukt"
collaborators: "Samarbeidspartnere" collaborators: "Samarbeidspartnere"
author: Author
creative_commons_licences: "Creative Commons lisenser" creative_commons_licences: "Creative Commons lisenser"
licence: "Licence"
themes: "Temaer" themes: "Temaer"
tags: "Etiketter" tags: "Etiketter"
save_as_draft: "Lagre som utkast" save_as_draft: "Lagre som utkast"

View File

@ -131,6 +131,7 @@ pt:
illustration: "Foto" illustration: "Foto"
add_an_illustration: "Adicionar foto" add_an_illustration: "Adicionar foto"
CAD_file: "Arquivo CAD" CAD_file: "Arquivo CAD"
CAD_files: "CAD files"
allowed_extensions: "Extensões permitidas:" allowed_extensions: "Extensões permitidas:"
add_a_new_file: "Adicionar novo arquivo" add_a_new_file: "Adicionar novo arquivo"
description: "Descrição" description: "Descrição"
@ -138,6 +139,7 @@ pt:
steps: "Passos" steps: "Passos"
step_N: "Passo {INDEX}" step_N: "Passo {INDEX}"
step_title: "Passo Título" step_title: "Passo Título"
step_image: "Image"
add_a_picture: "Adicionar imagem" add_a_picture: "Adicionar imagem"
change_the_picture: "Alterar imagem" change_the_picture: "Alterar imagem"
delete_the_step: "Deletar este passo" delete_the_step: "Deletar este passo"
@ -149,7 +151,9 @@ pt:
employed_materials: "Materiais utilizados" employed_materials: "Materiais utilizados"
employed_machines: "Máquinas utilizadas" employed_machines: "Máquinas utilizadas"
collaborators: "Colaboradores" collaborators: "Colaboradores"
author: Author
creative_commons_licences: "Licença Creative Commons" creative_commons_licences: "Licença Creative Commons"
licence: "Licence"
themes: "Temas" themes: "Temas"
tags: "Tags" tags: "Tags"
save_as_draft: "Salvar como rascunho" save_as_draft: "Salvar como rascunho"

View File

@ -131,6 +131,7 @@ zu:
illustration: "crwdns28728:0crwdne28728:0" illustration: "crwdns28728:0crwdne28728:0"
add_an_illustration: "crwdns28730:0crwdne28730:0" add_an_illustration: "crwdns28730:0crwdne28730:0"
CAD_file: "crwdns28732:0crwdne28732:0" CAD_file: "crwdns28732:0crwdne28732:0"
CAD_files: "crwdns37647:0crwdne37647:0"
allowed_extensions: "crwdns28734:0crwdne28734:0" allowed_extensions: "crwdns28734:0crwdne28734:0"
add_a_new_file: "crwdns28736:0crwdne28736:0" add_a_new_file: "crwdns28736:0crwdne28736:0"
description: "crwdns28738:0crwdne28738:0" description: "crwdns28738:0crwdne28738:0"
@ -138,6 +139,7 @@ zu:
steps: "crwdns28742:0crwdne28742:0" steps: "crwdns28742:0crwdne28742:0"
step_N: "crwdns28744:0{INDEX}crwdne28744:0" step_N: "crwdns28744:0{INDEX}crwdne28744:0"
step_title: "crwdns28746:0crwdne28746:0" step_title: "crwdns28746:0crwdne28746:0"
step_image: "crwdns37649:0crwdne37649:0"
add_a_picture: "crwdns28748:0crwdne28748:0" add_a_picture: "crwdns28748:0crwdne28748:0"
change_the_picture: "crwdns28750:0crwdne28750:0" change_the_picture: "crwdns28750:0crwdne28750:0"
delete_the_step: "crwdns28752:0crwdne28752:0" delete_the_step: "crwdns28752:0crwdne28752:0"
@ -149,7 +151,9 @@ zu:
employed_materials: "crwdns28764:0crwdne28764:0" employed_materials: "crwdns28764:0crwdne28764:0"
employed_machines: "crwdns28766:0crwdne28766:0" employed_machines: "crwdns28766:0crwdne28766:0"
collaborators: "crwdns28768:0crwdne28768:0" collaborators: "crwdns28768:0crwdne28768:0"
author: crwdns37651:0crwdne37651:0
creative_commons_licences: "crwdns28770:0crwdne28770:0" creative_commons_licences: "crwdns28770:0crwdne28770:0"
licence: "crwdns37653:0crwdne37653:0"
themes: "crwdns28772:0crwdne28772:0" themes: "crwdns28772:0crwdne28772:0"
tags: "crwdns28774:0crwdne28774:0" tags: "crwdns28774:0crwdne28774:0"
save_as_draft: "crwdns28776:0crwdne28776:0" save_as_draft: "crwdns28776:0crwdne28776:0"

View File

@ -697,6 +697,10 @@ de:
trainings_authorization_validity_duration: "Trainings validity period duration" trainings_authorization_validity_duration: "Trainings validity period duration"
trainings_invalidation_rule: "Trainings automatic invalidation" trainings_invalidation_rule: "Trainings automatic invalidation"
trainings_invalidation_rule_period: "Grace period before invalidating a training" trainings_invalidation_rule_period: "Grace period before invalidating a training"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
#statuses of projects #statuses of projects
statuses: statuses:
new: "Neu" new: "Neu"

View File

@ -720,6 +720,10 @@ en:
trainings_authorization_validity_duration: "Trainings validity period duration" trainings_authorization_validity_duration: "Trainings validity period duration"
trainings_invalidation_rule: "Trainings automatic invalidation" trainings_invalidation_rule: "Trainings automatic invalidation"
trainings_invalidation_rule_period: "Grace period before invalidating a training" trainings_invalidation_rule_period: "Grace period before invalidating a training"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
family_account: "Family account" family_account: "Family account"
#statuses of projects #statuses of projects
statuses: statuses:

View File

@ -697,6 +697,10 @@ es:
trainings_authorization_validity_duration: "Trainings validity period duration" trainings_authorization_validity_duration: "Trainings validity period duration"
trainings_invalidation_rule: "Trainings automatic invalidation" trainings_invalidation_rule: "Trainings automatic invalidation"
trainings_invalidation_rule_period: "Grace period before invalidating a training" trainings_invalidation_rule_period: "Grace period before invalidating a training"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
#statuses of projects #statuses of projects
statuses: statuses:
new: "New" new: "New"

View File

@ -720,6 +720,10 @@ fr:
trainings_authorization_validity_duration: "Durée de la période de validité des formations" trainings_authorization_validity_duration: "Durée de la période de validité des formations"
trainings_invalidation_rule: "Invalidation automatique des formations" trainings_invalidation_rule: "Invalidation automatique des formations"
trainings_invalidation_rule_period: "Période de grâce avant d'invalider une formation" trainings_invalidation_rule_period: "Période de grâce avant d'invalider une formation"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
#statuses of projects #statuses of projects
statuses: statuses:
new: "Nouveau" new: "Nouveau"

View File

@ -697,6 +697,10 @@ it:
trainings_authorization_validity_duration: "Durata del periodo di validità delle abilitazioni" trainings_authorization_validity_duration: "Durata del periodo di validità delle abilitazioni"
trainings_invalidation_rule: "Annullamento automatico delle abilitazioni" trainings_invalidation_rule: "Annullamento automatico delle abilitazioni"
trainings_invalidation_rule_period: "Periodo di tolleranza prima di invalidare un'abilitazione" trainings_invalidation_rule_period: "Periodo di tolleranza prima di invalidare un'abilitazione"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
#statuses of projects #statuses of projects
statuses: statuses:
new: "Nuovo" new: "Nuovo"

View File

@ -697,6 +697,10 @@
trainings_authorization_validity_duration: "Trainings validity period duration" trainings_authorization_validity_duration: "Trainings validity period duration"
trainings_invalidation_rule: "Trainings automatic invalidation" trainings_invalidation_rule: "Trainings automatic invalidation"
trainings_invalidation_rule_period: "Grace period before invalidating a training" trainings_invalidation_rule_period: "Grace period before invalidating a training"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
#statuses of projects #statuses of projects
statuses: statuses:
new: "New" new: "New"

View File

@ -697,6 +697,10 @@ pt:
trainings_authorization_validity_duration: "Trainings validity period duration" trainings_authorization_validity_duration: "Trainings validity period duration"
trainings_invalidation_rule: "Trainings automatic invalidation" trainings_invalidation_rule: "Trainings automatic invalidation"
trainings_invalidation_rule_period: "Grace period before invalidating a training" trainings_invalidation_rule_period: "Grace period before invalidating a training"
projects_list_member_filter_presence: "Presence of member filter on projects list"
projects_list_date_filters_presence: "Presence of dates filter on projects list"
project_categories_filter_placeholder: "Placeholder for categories filter in project gallery"
project_categories_wording: "Wording used to replace \"Categories\" on public pages"
#statuses of projects #statuses of projects
statuses: statuses:
new: "Novo" new: "Novo"

View File

@ -697,6 +697,10 @@ zu:
trainings_authorization_validity_duration: "crwdns37105:0crwdne37105:0" trainings_authorization_validity_duration: "crwdns37105:0crwdne37105:0"
trainings_invalidation_rule: "crwdns37107:0crwdne37107:0" trainings_invalidation_rule: "crwdns37107:0crwdne37107:0"
trainings_invalidation_rule_period: "crwdns37109:0crwdne37109:0" trainings_invalidation_rule_period: "crwdns37109:0crwdne37109:0"
projects_list_member_filter_presence: "crwdns37655:0crwdne37655:0"
projects_list_date_filters_presence: "crwdns37657:0crwdne37657:0"
project_categories_filter_placeholder: "crwdns37659:0crwdne37659:0"
project_categories_wording: "crwdns37661:0crwdne37661:0"
#statuses of projects #statuses of projects
statuses: statuses:
new: "crwdns37111:0crwdne37111:0" new: "crwdns37111:0crwdne37111:0"

View File

@ -38,6 +38,7 @@ Rails.application.routes.draw do
get :last_published get :last_published
get :search get :search
end end
get :markdown, on: :member
end end
resources :openlab_projects, only: :index resources :openlab_projects, only: :index
resources :machines resources :machines
@ -46,6 +47,7 @@ Rails.application.routes.draw do
resources :themes resources :themes
resources :licences resources :licences
resources :statuses resources :statuses
resources :project_categories
resources :admins, only: %i[index create destroy] resources :admins, only: %i[index create destroy]
resources :settings, only: %i[show update index], param: :name do resources :settings, only: %i[show update index], param: :name do
patch '/bulk_update', action: 'bulk_update', on: :collection patch '/bulk_update', action: 'bulk_update', on: :collection

View File

@ -0,0 +1,9 @@
class CreateProjectCategories < ActiveRecord::Migration[7.0]
def change
create_table :project_categories do |t|
t.string :name
t.timestamps
end
end
end

View File

@ -0,0 +1,12 @@
class CreateProjectsProjectCategories < ActiveRecord::Migration[7.0]
def change
create_table :projects_project_categories do |t|
t.belongs_to :project, foreign_key: true, null: false
t.belongs_to :project_category, foreign_key: true, null: false
t.timestamps
end
add_index :projects_project_categories, [:project_id, :project_category_id], unique: true, name: :idx_projects_project_categories
end
end

View File

@ -729,5 +729,9 @@ Setting.set('accounting_Error_label', 'Erroneous invoices to refund') unless Set
Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value) Setting.set('external_id', false) unless Setting.find_by(name: 'external_id').try(:value)
Setting.set('projects_list_member_filter_presence', false) unless Setting.find_by(name: 'projects_list_member_filter_presence')
Setting.set('projects_list_date_filters_presence', false) unless Setting.find_by(name: 'projects_list_date_filters_presence')
Setting.set('project_categories_filter_placeholder', 'Toutes les catégories') unless Setting.find_by(name: 'project_categories_filter_placeholder').try(:value)
Setting.set('project_categories_wording', 'Catégories') unless Setting.find_by(name: 'project_categories_wording').try(:value)
Setting.set('family_account', false) unless Setting.find_by(name: 'family_account').try(:value) Setting.set('family_account', false) unless Setting.find_by(name: 'family_account').try(:value)
Setting.set('child_validation_required', false) unless Setting.find_by(name: 'child_validation_required').try(:value) Setting.set('child_validation_required', false) unless Setting.find_by(name: 'child_validation_required').try(:value)

View File

@ -9,13 +9,6 @@ SET xmloption = content;
SET client_min_messages = warning; SET client_min_messages = warning;
SET row_security = off; SET row_security = off;
--
-- Name: public; Type: SCHEMA; Schema: -; Owner: -
--
-- *not* creating schema, since initdb creates it
-- --
-- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: - -- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: -
-- --
@ -2835,6 +2828,37 @@ CREATE SEQUENCE public.profiles_id_seq
ALTER SEQUENCE public.profiles_id_seq OWNED BY public.profiles.id; ALTER SEQUENCE public.profiles_id_seq OWNED BY public.profiles.id;
--
-- Name: project_categories; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.project_categories (
id bigint NOT NULL,
name character varying,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
--
-- Name: project_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.project_categories_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: project_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.project_categories_id_seq OWNED BY public.project_categories.id;
-- --
-- Name: project_steps; Type: TABLE; Schema: public; Owner: - -- Name: project_steps; Type: TABLE; Schema: public; Owner: -
-- --
@ -3003,6 +3027,38 @@ CREATE SEQUENCE public.projects_machines_id_seq
ALTER SEQUENCE public.projects_machines_id_seq OWNED BY public.projects_machines.id; ALTER SEQUENCE public.projects_machines_id_seq OWNED BY public.projects_machines.id;
--
-- Name: projects_project_categories; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.projects_project_categories (
id bigint NOT NULL,
project_id bigint NOT NULL,
project_category_id bigint NOT NULL,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
--
-- Name: projects_project_categories_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public.projects_project_categories_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: projects_project_categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public.projects_project_categories_id_seq OWNED BY public.projects_project_categories.id;
-- --
-- Name: projects_spaces; Type: TABLE; Schema: public; Owner: - -- Name: projects_spaces; Type: TABLE; Schema: public; Owner: -
-- --
@ -4881,6 +4937,13 @@ ALTER TABLE ONLY public.profile_custom_fields ALTER COLUMN id SET DEFAULT nextva
ALTER TABLE ONLY public.profiles ALTER COLUMN id SET DEFAULT nextval('public.profiles_id_seq'::regclass); ALTER TABLE ONLY public.profiles ALTER COLUMN id SET DEFAULT nextval('public.profiles_id_seq'::regclass);
--
-- Name: project_categories id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.project_categories ALTER COLUMN id SET DEFAULT nextval('public.project_categories_id_seq'::regclass);
-- --
-- Name: project_steps id; Type: DEFAULT; Schema: public; Owner: - -- Name: project_steps id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -4916,6 +4979,13 @@ ALTER TABLE ONLY public.projects_components ALTER COLUMN id SET DEFAULT nextval(
ALTER TABLE ONLY public.projects_machines ALTER COLUMN id SET DEFAULT nextval('public.projects_machines_id_seq'::regclass); ALTER TABLE ONLY public.projects_machines ALTER COLUMN id SET DEFAULT nextval('public.projects_machines_id_seq'::regclass);
--
-- Name: projects_project_categories id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.projects_project_categories ALTER COLUMN id SET DEFAULT nextval('public.projects_project_categories_id_seq'::regclass);
-- --
-- Name: projects_spaces id; Type: DEFAULT; Schema: public; Owner: - -- Name: projects_spaces id; Type: DEFAULT; Schema: public; Owner: -
-- --
@ -5805,6 +5875,14 @@ ALTER TABLE ONLY public.profiles
ADD CONSTRAINT profiles_pkey PRIMARY KEY (id); ADD CONSTRAINT profiles_pkey PRIMARY KEY (id);
--
-- Name: project_categories project_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.project_categories
ADD CONSTRAINT project_categories_pkey PRIMARY KEY (id);
-- --
-- Name: project_steps project_steps_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- Name: project_steps project_steps_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --
@ -5845,6 +5923,14 @@ ALTER TABLE ONLY public.projects
ADD CONSTRAINT projects_pkey PRIMARY KEY (id); ADD CONSTRAINT projects_pkey PRIMARY KEY (id);
--
-- Name: projects_project_categories projects_project_categories_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.projects_project_categories
ADD CONSTRAINT projects_project_categories_pkey PRIMARY KEY (id);
-- --
-- Name: projects_spaces projects_spaces_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- Name: projects_spaces projects_spaces_pkey; Type: CONSTRAINT; Schema: public; Owner: -
-- --
@ -6165,6 +6251,13 @@ ALTER TABLE ONLY public.wallets
ADD CONSTRAINT wallets_pkey PRIMARY KEY (id); ADD CONSTRAINT wallets_pkey PRIMARY KEY (id);
--
-- Name: idx_projects_project_categories; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX idx_projects_project_categories ON public.projects_project_categories USING btree (project_id, project_category_id);
-- --
-- Name: index_abuses_on_signaled_type_and_signaled_id; Type: INDEX; Schema: public; Owner: - -- Name: index_abuses_on_signaled_type_and_signaled_id; Type: INDEX; Schema: public; Owner: -
-- --
@ -7110,6 +7203,20 @@ CREATE UNIQUE INDEX index_projects_on_slug ON public.projects USING btree (slug)
CREATE INDEX index_projects_on_status_id ON public.projects USING btree (status_id); CREATE INDEX index_projects_on_status_id ON public.projects USING btree (status_id);
--
-- Name: index_projects_project_categories_on_project_category_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_projects_project_categories_on_project_category_id ON public.projects_project_categories USING btree (project_category_id);
--
-- Name: index_projects_project_categories_on_project_id; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX index_projects_project_categories_on_project_id ON public.projects_project_categories USING btree (project_id);
-- --
-- Name: index_projects_spaces_on_project_id; Type: INDEX; Schema: public; Owner: - -- Name: index_projects_spaces_on_project_id; Type: INDEX; Schema: public; Owner: -
-- --
@ -8290,6 +8397,14 @@ ALTER TABLE ONLY public.projects
ADD CONSTRAINT fk_rails_b4a83cd9b3 FOREIGN KEY (status_id) REFERENCES public.statuses(id); ADD CONSTRAINT fk_rails_b4a83cd9b3 FOREIGN KEY (status_id) REFERENCES public.statuses(id);
--
-- Name: projects_project_categories fk_rails_ba4a985e85; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.projects_project_categories
ADD CONSTRAINT fk_rails_ba4a985e85 FOREIGN KEY (project_id) REFERENCES public.projects(id);
-- --
-- Name: statistic_profiles fk_rails_bba64e5eb9; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: statistic_profiles fk_rails_bba64e5eb9; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -8442,6 +8557,14 @@ ALTER TABLE ONLY public.event_price_categories
ADD CONSTRAINT fk_rails_dcd2787d07 FOREIGN KEY (event_id) REFERENCES public.events(id); ADD CONSTRAINT fk_rails_dcd2787d07 FOREIGN KEY (event_id) REFERENCES public.events(id);
--
-- Name: projects_project_categories fk_rails_de9f22810e; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.projects_project_categories
ADD CONSTRAINT fk_rails_de9f22810e FOREIGN KEY (project_category_id) REFERENCES public.project_categories(id);
-- --
-- Name: cart_item_coupons fk_rails_e1cb402fac; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: cart_item_coupons fk_rails_e1cb402fac; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -8941,7 +9064,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20230524083558'), ('20230524083558'),
('20230524110215'), ('20230524110215'),
('20230525101006'), ('20230525101006'),
('20230612123250'), ('20230612123250');
('20230626103314'); ('20230626103314');
('20230626122844'),
('20230626122947');

View File

@ -25,8 +25,15 @@ class PayZen::Service < Payment::Service
order_id: order_id order_id: order_id
} }
unless first_item.details['adjustment']&.zero? && first_item.details['other_items']&.zero? unless first_item.details['adjustment']&.zero? && first_item.details['other_items']&.zero?
params[:initial_amount] = payzen_amount(first_item.amount) initial_amount = first_item.amount
params[:initial_amount_number] = 1 initial_amount -= payment_schedule.wallet_amount if payment_schedule.wallet_amount
if initial_amount.zero?
params[:effect_date] = (first_item.due_date + 1.month).iso8601
params[:rrule] = rrule(payment_schedule, -1)
else
params[:initial_amount] = payzen_amount(initial_amount)
params[:initial_amount_number] = 1
end
end end
pz_subscription = client.create_subscription(**params) pz_subscription = client.create_subscription(**params)
@ -123,16 +130,21 @@ class PayZen::Service < Payment::Service
private private
def rrule(payment_schedule) def rrule(payment_schedule, offset = 0)
count = payment_schedule.payment_schedule_items.count count = payment_schedule.payment_schedule_items.count
"RRULE:FREQ=MONTHLY;COUNT=#{count}" "RRULE:FREQ=MONTHLY;COUNT=#{count + offset}"
end end
# check if the given transaction matches the given PaymentScheduleItem # check if the given transaction matches the given PaymentScheduleItem
def transaction_matches?(transaction, payment_schedule_item) def transaction_matches?(transaction, payment_schedule_item)
transaction_date = Time.zone.parse(transaction['creationDate']).to_date transaction_date = Time.zone.parse(transaction['creationDate']).to_date
transaction['amount'] == payment_schedule_item.amount && amount = payment_schedule_item.amount
if !payment_schedule_item.details['adjustment']&.zero? && payment_schedule_item.payment_schedule.wallet_amount
amount -= payment_schedule_item.payment_schedule.wallet_amount
end
transaction['amount'] == amount &&
transaction_date >= payment_schedule_item.due_date.to_date && transaction_date >= payment_schedule_item.due_date.to_date &&
transaction_date <= payment_schedule_item.due_date.to_date + 7.days transaction_date <= payment_schedule_item.due_date.to_date + 7.days
end end

View File

@ -1,6 +1,6 @@
{ {
"name": "fab-manager", "name": "fab-manager",
"version": "6.0.6", "version": "6.0.8",
"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.", "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": [ "keywords": [
"fablab", "fablab",

View File

@ -855,14 +855,45 @@ history_value_100:
history_value_101: history_value_101:
id: 101 id: 101
setting_id: 100 setting_id: 100
value: 'false' value: 'Toutes les catégories'
created_at: '2023-03-31 14:38:40.000421' created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: '2023-03-31 14:38:40.000421' updated_at: 2023-04-05 09:16:08.000511500 Z
invoicing_profile_id: 1 invoicing_profile_id: 1
history_value_102: history_value_102:
id: 102 id: 102
setting_id: 101 setting_id: 101
value: 'Catégories'
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
invoicing_profile_id: 1
history_value_103:
id: 103
setting_id: 102
value: 'false'
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
invoicing_profile_id: 1
history_value_104:
id: 104
setting_id: 103
value: 'false'
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
history_value_105:
id: 105
setting_id: 104
value: 'false'
created_at: '2023-03-31 14:38:40.000421'
updated_at: '2023-03-31 14:38:40.000421'
invoicing_profile_id: 1
history_value_106:
id: 106
setting_id: 105
value: 'false' value: 'false'
created_at: '2023-03-31 14:38:40.000421' created_at: '2023-03-31 14:38:40.000421'
updated_at: '2023-03-31 14:38:40.000421' updated_at: '2023-03-31 14:38:40.000421'

12
test/fixtures/project_categories.yml vendored Normal file
View File

@ -0,0 +1,12 @@
project_category_1:
id: 1
name: Module 1
created_at: 2023-06-26 15:39:08.259759000 Z
updated_at: 2016-06-26 15:39:08.259759000 Z
project_category_2:
id: 2
name: Module 2
created_at: 2016-06-26 15:39:08.265840000 Z
updated_at: 2016-06-26 15:39:08.265840000 Z

View File

@ -7,6 +7,7 @@ project_step_1:
created_at: 2016-04-04 15:39:08.259759000 Z created_at: 2016-04-04 15:39:08.259759000 Z
updated_at: 2016-04-04 15:39:08.259759000 Z updated_at: 2016-04-04 15:39:08.259759000 Z
title: Le manche title: Le manche
step_nb: 1
project_step_2: project_step_2:
id: 2 id: 2
@ -16,3 +17,4 @@ project_step_2:
created_at: 2016-04-04 15:39:08.265840000 Z created_at: 2016-04-04 15:39:08.265840000 Z
updated_at: 2016-04-04 15:39:08.265840000 Z updated_at: 2016-04-04 15:39:08.265840000 Z
title: La presse title: La presse
step_nb: 2

View File

@ -12,3 +12,4 @@ project_1:
state: published state: published
slug: presse-puree slug: presse-puree
published_at: 2016-04-04 15:39:08.267614000 Z published_at: 2016-04-04 15:39:08.267614000 Z
status_id: 1

View File

@ -0,0 +1,7 @@
projects_project_category_1:
id: 1
project_id: 1
project_category_id: 1
created_at: 2023-06-26 15:39:08.259759000 Z
updated_at: 2016-06-26 15:39:08.259759000 Z

View File

@ -589,12 +589,36 @@ setting_99:
setting_100: setting_100:
id: 100 id: 100
name: project_categories_filter_placeholder
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
setting_101:
id: 101
name: project_categories_wording
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
setting_102:
id: 102
name: projects_list_member_filter_presence
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
setting_103:
id: 103
name: projects_list_date_filters_presence
created_at: 2023-04-05 09:16:08.000511500 Z
updated_at: 2023-04-05 09:16:08.000511500 Z
setting_104:
id: 104
name: family_account name: family_account
created_at: 2023-03-31 14:38:40.000421500 Z created_at: 2023-03-31 14:38:40.000421500 Z
updated_at: 2023-03-31 14:38:40.000421500 Z updated_at: 2023-03-31 14:38:40.000421500 Z
setting_101: setting_105:
id: 101 id: 105
name: child_validation_required name: child_validation_required
created_at: 2023-03-31 14:38:40.000421500 Z created_at: 2023-03-31 14:38:40.000421500 Z
updated_at: 2023-03-31 14:38:40.000421500 Z updated_at: 2023-03-31 14:38:40.000421500 Z

View File

@ -826,6 +826,30 @@ export const settings: Array<Setting> = [
last_update: '2022-12-23T14:39:12+0100', last_update: '2022-12-23T14:39:12+0100',
localized: 'Url' localized: 'Url'
}, },
{
name: 'projects_list_member_filter_presence',
value: 'false',
last_update: '2022-12-23T14:39:12+0100',
localized: 'Projects list member filter presence'
},
{
name: 'projects_list_date_filters_presence',
value: 'false',
last_update: '2022-12-23T14:39:12+0100',
localized: 'Projects list date filters presence'
},
{
name: 'project_categories_filter_placeholder',
value: 'Toutes les catégories',
last_update: '2022-12-23T14:39:12+0100',
localized: 'Placeholder for categories filter in project gallery'
},
{
name: 'project_categories_wording',
value: 'Catégories',
last_update: '2022-12-23T14:39:12+0100',
localized: 'Project categories overridden name'
},
{ {
name: 'family_account', name: 'family_account',
value: 'false', value: 'false',

View File

@ -0,0 +1,68 @@
# frozen_string_literal: true
require 'test_helper'
class ProjectCategoriesTest < ActionDispatch::IntegrationTest
def setup
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end
test 'create a project_category' do
post '/api/project_categories',
params: {
name: 'Module de fou'
}.to_json,
headers: default_headers
# Check response format & project_category
assert_equal 201, response.status, response.body
assert_match Mime[:json].to_s, response.content_type
# Check the correct project_category was created
res = json_response(response.body)
project_category = ProjectCategory.where(id: res[:id]).first
assert_not_nil project_category, 'project_category was not created in database'
assert_equal 'Module de fou', res[:name]
end
test 'update a project_category' do
patch '/api/project_categories/1',
params: {
name: 'Nouveau nom'
}.to_json,
headers: default_headers
# Check response format & project_category
assert_equal 200, response.status, response.body
assert_match Mime[:json].to_s, response.content_type
# Check the project_category was updated
res = json_response(response.body)
assert_equal 1, res[:id]
assert_equal 'Nouveau nom', res[:name]
end
test 'list all project_categories' do
get '/api/project_categories'
# Check response format & project_category
assert_equal 200, response.status, response.body
assert_match Mime[:json].to_s, response.content_type
# Check the list items are ok
project_categories = json_response(response.body)
assert_equal ProjectCategory.count, project_categories.count
end
test 'delete a project_category' do
project_category = ProjectCategory.create!(name: 'Gone too soon')
delete "/api/project_categories/#{project_category.id}"
assert_response :success
assert_empty response.body
assert_raise ActiveRecord::RecordNotFound do
project_category.reload
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'test_helper'
class ProjectsTest < ActionDispatch::IntegrationTest
def setup
@admin = User.find_by(username: 'admin')
login_as(@admin, scope: :user)
end
test 'download markdown file' do
get "/api/projects/1/markdown"
assert_response :success
assert_equal "text/markdown", response.content_type
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'test_helper'
class ProjectCategoryTest < ActiveSupport::TestCase
test 'fixtures are valid' do
ProjectCategory.find_each do |project_category|
assert project_category.valid?
end
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'test_helper'
class ProjectTest < ActiveSupport::TestCase
test 'fixtures are valid' do
Project.find_each do |project|
assert project.valid?
end
end
test 'relation project_categories' do
assert_equal [project_categories(:project_category_1)], projects(:project_1).project_categories
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'test_helper'
class ProjectToMarkdownTest < ActiveSupport::TestCase
test "ProjectToMarkdown is working" do
project = projects(:project_1)
service = ProjectToMarkdown.new(project)
markdown_str = nil
assert_nothing_raised do
markdown_str = service.call
end
assert_includes markdown_str, project.name
project.project_steps.each do |project_step|
assert_includes markdown_str, project_step.title
end
end
end