diff --git a/CHANGELOG.md b/CHANGELOG.md index 53cfd1622..ec5c8cf7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 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 diff --git a/Gemfile b/Gemfile index 822c4dd0b..bbe94468b 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ group :development, :test do # comment over to use visual debugger (eg. RubyMine), uncomment to use manual debugging # gem 'byebug' gem 'dotenv-rails' + gem 'pry' end group :development do @@ -43,7 +44,6 @@ group :development do # Preview mail in the browser gem 'listen', '~> 3.0.5' gem 'overcommit' - gem 'pry' gem 'rb-readline' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'railroady' @@ -149,3 +149,5 @@ gem 'acts_as_list' # Error reporting gem 'sentry-rails' gem 'sentry-ruby' + +gem "reverse_markdown" diff --git a/Gemfile.lock b/Gemfile.lock index 5cb4eaf82..1d1f8f54d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -82,7 +82,7 @@ GEM rails (>= 4.1) ast (2.4.2) attr_required (1.0.1) - awesome_print (1.8.0) + awesome_print (1.9.2) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) ice_nine (~> 0.11.0) @@ -398,6 +398,8 @@ GEM responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) + reverse_markdown (2.1.1) + nokogiri rexml (3.2.5) rolify (5.3.0) rubocop (1.31.2) @@ -590,6 +592,7 @@ DEPENDENCIES redis-session-store repost responders (~> 3.0) + reverse_markdown rolify rubocop (~> 1.31) rubocop-rails diff --git a/app/controllers/api/project_categories_controller.rb b/app/controllers/api/project_categories_controller.rb new file mode 100644 index 000000000..36cd0e59f --- /dev/null +++ b/app/controllers/api/project_categories_controller.rb @@ -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 diff --git a/app/controllers/api/projects_controller.rb b/app/controllers/api/projects_controller.rb index dc1cf7699..a844f7ab3 100644 --- a/app/controllers/api/projects_controller.rb +++ b/app/controllers/api/projects_controller.rb @@ -18,6 +18,12 @@ class API::ProjectsController < API::APIController @project = Project.friendly.find(params[:id]) 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 @project = Project.new(project_params.merge(author_statistic_profile_id: current_user.statistic_profile.id)) if @project.save @@ -53,12 +59,23 @@ class API::ProjectsController < API::APIController def search 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] - @total = res[:total] - @projects = res[:projects] - render :index + respond_to do |format| + format.json do + @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 private @@ -69,7 +86,7 @@ class API::ProjectsController < API::APIController def project_params 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_caos_attributes: %i[id attachment _destroy], project_steps_attributes: [ diff --git a/app/frontend/src/javascript/api/project-category.ts b/app/frontend/src/javascript/api/project-category.ts new file mode 100644 index 000000000..cd723f525 --- /dev/null +++ b/app/frontend/src/javascript/api/project-category.ts @@ -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> { + const res: AxiosResponse> = await apiClient.get('/api/project_categories'); + return res?.data; + } + + static async create (newProjectCategory: ProjectCategory): Promise { + const res: AxiosResponse = await apiClient.post('/api/project_categories', { project_category: newProjectCategory }); + return res?.data; + } + + static async update (updatedProjectCategory: ProjectCategory): Promise { + const res: AxiosResponse = await apiClient.patch(`/api/project_categories/${updatedProjectCategory.id}`, { project_category: updatedProjectCategory }); + return res?.data; + } + + static async destroy (projectCategoryId: number): Promise { + const res: AxiosResponse = await apiClient.delete(`/api/project_categories/${projectCategoryId}`); + return res?.data; + } +} diff --git a/app/frontend/src/javascript/controllers/admin/projects.js b/app/frontend/src/javascript/controllers/admin/projects.js index c184a67f9..4f25dd2ae 100644 --- a/app/frontend/src/javascript/controllers/admin/projects.js +++ b/app/frontend/src/javascript/controllers/admin/projects.js @@ -12,8 +12,8 @@ */ 'use strict'; -Application.Controllers.controller('AdminProjectsController', ['$scope', '$state', 'Component', 'Licence', 'Theme', 'componentsPromise', 'licencesPromise', 'themesPromise', '_t', 'Member', 'uiTourService', 'settingsPromise', 'growl', - function ($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, ProjectCategory, componentsPromise, licencesPromise, themesPromise, projectCategoriesPromise, _t, Member, uiTourService, settingsPromise, growl) { // Materials list (plastic, wood ...) $scope.components = componentsPromise; @@ -23,6 +23,9 @@ Application.Controllers.controller('AdminProjectsController', ['$scope', '$state // Themes list (cooking, sport ...) $scope.themes = themesPromise; + // Project categories list (generic categorization) + $scope.projectCategories = projectCategoriesPromise; + // Application settings $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) * @param data {Object} licence name and description diff --git a/app/frontend/src/javascript/controllers/machines.js.erb b/app/frontend/src/javascript/controllers/machines.js.erb index 9d8556865..984eaf665 100644 --- a/app/frontend/src/javascript/controllers/machines.js.erb +++ b/app/frontend/src/javascript/controllers/machines.js.erb @@ -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 */ const refreshCalendar = function () { - const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView'); - return Availability.machine({ - machineId: $scope.machine.id, - 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' - } - ); + $scope.eventSources.splice(0, 1, { + url: `/api/availabilities/machines/${$transition$.params().id}?member_id=${$scope.ctrl.member.id}`, + textColor: 'black' }); } diff --git a/app/frontend/src/javascript/controllers/projects.js b/app/frontend/src/javascript/controllers/projects.js index 6aa7d8112..de52f50f5 100644 --- a/app/frontend/src/javascript/controllers/projects.js +++ b/app/frontend/src/javascript/controllers/projects.js @@ -29,6 +29,7 @@ * - $scope.themes = [{Theme}] * - $scope.licences = [{Licence}] * - $scope.allowedExtensions = [{String}] + * - $scope.projectCategoriesWording = [{String}] * - $scope.submited(content) * - $scope.cancel() * - $scope.addFile() @@ -43,7 +44,7 @@ * - $state (Ui-Router) [ 'app.public.projects_show', 'app.public.projects_list' ] */ 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 $scope.summernoteOptsProject = angular.copy($rootScope.summernoteOpts); $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 Licence.query().$promise.then(function (data) { $scope.licences = data.map(function (d) { @@ -104,6 +115,8 @@ class ProjectsController { // List of extensions allowed for CAD attachements upload $scope.allowedExtensions = allowedExtensions.setting.value.split(' '); + $scope.projectCategoriesWording = projectCategoriesWording.setting.value; + /** * 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 @@ -281,8 +294,8 @@ class ProjectsController { /** * 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', - function ($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, projectCategoriesPromise, componentsPromise, paginationService, OpenlabProject, $window, growl, _t, $location, $timeout, settingsPromise, openLabActive, Member, Diacritics) { /* PRIVATE STATIC CONSTANTS */ // 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 $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? $scope.openlab = { projectsActive: openLabActive.isPresent, 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 $scope.search = { q: ($location.$$search.q || ''), @@ -307,7 +332,27 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P machine_id: (parseInt($location.$$search.machine_id) || undefined), component_id: (parseInt($location.$$search.component_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 @@ -319,6 +364,9 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P // list of themes / used for filtering $scope.themes = themesPromise; + // list of projectCategories / used for filtering + $scope.projectCategories = projectCategoriesPromise; + // list of components / used for filtering $scope.components = componentsPromise; @@ -332,6 +380,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P $scope.triggerSearch(); }; + $scope.zipUrl = '/api/projects/search.zip'; + /** * 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.theme_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.setUrlQueryParams($scope.search); $scope.triggerSearch(); @@ -389,7 +443,10 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P } else { updateUrlParam('whole_network', 'f'); $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.projects = projectsPromise.projects; }); @@ -420,6 +477,22 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P updateUrlParam('component_id', search.component_id); updateUrlParam('machine_id', search.machine_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; }; @@ -450,6 +523,11 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P } else { $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(); }; @@ -496,8 +574,8 @@ Application.Controllers.controller('ProjectsController', ['$scope', '$state', 'P /** * 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', - function ($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, ProjectCategory, Licence, Status, $document, CSRF, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) { CSRF.setMetaTags(); // API URL where the form will be posted @@ -529,15 +607,15 @@ Application.Controllers.controller('NewProjectController', ['$rootScope', '$scop }; // 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 */ -Application.Controllers.controller('EditProjectController', ['$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, 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, ProjectCategory, Licence, Status, $document, CSRF, projectPromise, Diacritics, dialogs, allowedExtensions, projectCategoriesWording, _t) { /* PUBLIC SCOPE */ // API URL where the form will be posted @@ -583,7 +661,7 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco } // 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 @@ -594,14 +672,15 @@ Application.Controllers.controller('EditProjectController', ['$rootScope', '$sco /** * Controller used in the public project's details page */ -Application.Controllers.controller('ShowProjectController', ['$scope', '$state', 'projectPromise', 'shortnamePromise', '$location', '$uibModal', 'dialogs', '_t', - function ($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, projectCategoriesWording, $location, $uibModal, dialogs, _t) { /* PUBLIC SCOPE */ // Store the project's details $scope.project = projectPromise; $scope.projectUrl = $location.absUrl(); $scope.disqusShortname = shortnamePromise.setting.value; + $scope.projectCategoriesWording = projectCategoriesWording.setting.value; /** * Test if the provided user has the edition rights on the current project diff --git a/app/frontend/src/javascript/controllers/spaces.js.erb b/app/frontend/src/javascript/controllers/spaces.js.erb index d4bded6da..379c17123 100644 --- a/app/frontend/src/javascript/controllers/spaces.js.erb +++ b/app/frontend/src/javascript/controllers/spaces.js.erb @@ -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 */ const refreshCalendar = function () { - const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView'); - return Availability.spaces({ - spaceId: $scope.space.id, - 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' - } - ); + $scope.eventSources.splice(0, 1, { + url: `/api/availabilities/spaces/${$transition$.params().id}?member_id=${$scope.ctrl.member.id}`, + textColor: 'black' }); }; diff --git a/app/frontend/src/javascript/controllers/trainings.js.erb b/app/frontend/src/javascript/controllers/trainings.js.erb index 0fa0a18a8..fe96a2e77 100644 --- a/app/frontend/src/javascript/controllers/trainings.js.erb +++ b/app/frontend/src/javascript/controllers/trainings.js.erb @@ -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 */ const refreshCalendar = function () { - const view = uiCalendarConfig.calendars.calendar.fullCalendar('getView'); - const id = $transition$.params().id === 'all' ? $transition$.params().id : $scope.training.id; - Availability.trainings({ - 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' - }); + $scope.eventSources.splice(0, 1, { + url: `/api/availabilities/trainings/${$transition$.params().id}`, + textColor: 'black' }); } diff --git a/app/frontend/src/javascript/models/project-category.ts b/app/frontend/src/javascript/models/project-category.ts new file mode 100644 index 000000000..83a0c2ab1 --- /dev/null +++ b/app/frontend/src/javascript/models/project-category.ts @@ -0,0 +1,5 @@ +// Type model used in ProjectSettings and its child components +export interface ProjectCategory { + name: string, + id?: number, +} diff --git a/app/frontend/src/javascript/models/setting.ts b/app/frontend/src/javascript/models/setting.ts index 27f84db14..ef4f535f1 100644 --- a/app/frontend/src/javascript/models/setting.ts +++ b/app/frontend/src/javascript/models/setting.ts @@ -198,7 +198,11 @@ export const fabHubSettings = [ export const projectsSettings = [ 'allowed_cad_extensions', '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; export const prepaidPacksSettings = [ @@ -221,7 +225,7 @@ export const pricingSettings = [ 'extended_prices_in_same_day' ] as const; -export const poymentSettings = [ +export const paymentSettings = [ 'payment_gateway' ] as const; @@ -291,7 +295,7 @@ export const allSettings = [ ...registrationSettings, ...adminSettings, ...pricingSettings, - ...poymentSettings, + ...paymentSettings, ...displaySettings, ...storeSettings, ...trainingsSettings, diff --git a/app/frontend/src/javascript/router.js b/app/frontend/src/javascript/router.js index 26ce00a93..5adfd6e40 100644 --- a/app/frontend/src/javascript/router.js +++ b/app/frontend/src/javascript/router.js @@ -301,8 +301,9 @@ angular.module('application.router', ['ui.router']) themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }], componentsPromise: ['Component', function (Component) { return Component.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], - settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['openlab_app_id', 'openlab_default']" }).$promise; }], - openLabActive: ['Setting', function (Setting) { return Setting.isPresent({ name: 'openlab_app_secret' }).$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; }], + projectCategoriesPromise: ['ProjectCategory', function (ProjectCategory) { return ProjectCategory.query().$promise; }] } }) .state('app.logged.projects_new', { @@ -314,7 +315,8 @@ angular.module('application.router', ['ui.router']) } }, 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', { @@ -327,7 +329,8 @@ angular.module('application.router', ['ui.router']) }, resolve: { 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', { @@ -340,7 +343,8 @@ angular.module('application.router', ['ui.router']) }, resolve: { 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; }] } }) @@ -735,10 +739,11 @@ angular.module('application.router', ['ui.router']) componentsPromise: ['Component', function (Component) { return Component.query().$promise; }], licencesPromise: ['Licence', function (Licence) { return Licence.query().$promise; }], themesPromise: ['Theme', function (Theme) { return Theme.query().$promise; }], + projectCategoriesPromise: ['ProjectCategory', function (ProjectCategory) { return ProjectCategory.query().$promise; }], settingsPromise: ['Setting', function (Setting) { return Setting.query({ 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; }] } diff --git a/app/frontend/src/javascript/services/project_category.js b/app/frontend/src/javascript/services/project_category.js new file mode 100644 index 000000000..079636210 --- /dev/null +++ b/app/frontend/src/javascript/services/project_category.js @@ -0,0 +1,11 @@ +'use strict'; + +Application.Services.factory('ProjectCategory', ['$resource', function ($resource) { + return $resource('/api/project_categories/:id', + { id: '@id' }, { + update: { + method: 'PUT' + } + } + ); +}]); diff --git a/app/frontend/templates/admin/projects/index.html b/app/frontend/templates/admin/projects/index.html index 46265ae15..c1cf88997 100644 --- a/app/frontend/templates/admin/projects/index.html +++ b/app/frontend/templates/admin/projects/index.html @@ -42,7 +42,10 @@ - + + + + diff --git a/app/frontend/templates/admin/projects/project_categories.html b/app/frontend/templates/admin/projects/project_categories.html new file mode 100644 index 000000000..36667205b --- /dev/null +++ b/app/frontend/templates/admin/projects/project_categories.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + +
{{ 'app.admin.project_categories.name' }}
+ + {{ projectCategory.name }} + + + +
+ + +
+
+ + +
+
diff --git a/app/frontend/templates/admin/projects/settings.html b/app/frontend/templates/admin/projects/settings.html index 73bd51897..a192e5f89 100644 --- a/app/frontend/templates/admin/projects/settings.html +++ b/app/frontend/templates/admin/projects/settings.html @@ -95,3 +95,49 @@ + +
+
+ {{ 'app.admin.projects.settings.filters' }} +
+
+
+ +
+
+ +
+
+
+ +
+
+ {{ 'app.admin.projects.settings.project_categories' }} +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
diff --git a/app/frontend/templates/projects/_form.html b/app/frontend/templates/projects/_form.html index fb4a9ff69..fccac499c 100644 --- a/app/frontend/templates/projects/_form.html +++ b/app/frontend/templates/projects/_form.html @@ -279,6 +279,23 @@ +
+
+

{{ projectCategoriesWording }}

+
+
+ + + + + + + + + + +
+
diff --git a/app/frontend/templates/projects/index.html b/app/frontend/templates/projects/index.html index 22d8f8143..276258d3e 100644 --- a/app/frontend/templates/projects/index.html +++ b/app/frontend/templates/projects/index.html @@ -22,7 +22,7 @@
-

Filter

+

{{ 'app.public.projects_list.filter' }}

{{ 'app.public.projects_list.reset_all_filters' | translate }}
@@ -61,12 +61,57 @@ + + + + + + + + + + + + + + +
+ +
diff --git a/app/frontend/templates/projects/show.html b/app/frontend/templates/projects/show.html index 5c0aa93ab..12b4467d7 100644 --- a/app/frontend/templates/projects/show.html +++ b/app/frontend/templates/projects/show.html @@ -1,187 +1,202 @@
-
+
+
+
+
+ +
+
+
+
+

{{ project.name }} {{ 'app.public.projects_show.rough_draft' }}

+
+
+ + +
+
+ +
-
-
- -
-
-
-
-

{{ project.name }} {{ 'app.public.projects_show.rough_draft' }}

-
-
- - -
-
- - -
-
- -
- -
- {{project.name}} -
- -

{{ 'app.public.projects_show.project_description' }}

-

- -
-
-
-

{{ 'app.public.projects_show.step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}

-
-
-
- {{image.attachment}} -
-
- -

-
+
+
+
+ {{project.name}}
+ +

{{ 'app.public.projects_show.project_description' }}

+

+ +
+
+
+

{{ 'app.public.projects_show.step_N' | translate:{INDEX:step.step_nb} }} : {{step.title}}

+
+
+
+ {{image.attachment}} +
+
+ +

+
+ + +
+
+ +
+ + + +
+ +
- - -
- - -
- -
- -
+
-
-
- +
+
+ +
+ + {{ 'app.public.projects_show.posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}} + +
+ + {{theme.name}} + +
+
-
- - {{ 'app.public.projects_show.by_name' | translate:{NAME:project.author.first_name} }} + + +
+
+ {{project.project_caos_attributes.length}} +

{{ 'app.public.projects_show.CAD_file_to_download' }}

+
+ +
+
+ +
+
+

{{ 'app.public.projects_show.status' }}

+
+
+ {{ project.status.name }} +
+
+ +
+
+ {{project.machines.length}} +

{{ 'app.public.projects_show.machines_and_materials' }}

+
+ + + +
    +
  • + {{component.name}} +
  • +
+
+ +
+
+ {{project.project_users.length}} +

{{ 'app.public.projects_show.collaborators' }}

+
+ +
    + +
+
+ +
+
+

{{ 'app.public.projects_show.licence' }}

+
+
+ {{ project.licence.name }} +
+
+ +
+
+

{{ 'app.shared.project.tags' }}

+
+
+
{{ project.tags }}
+
+
+ +
+
+

{{ projectCategoriesWording }}

+
+ +
    +
  • + {{projectCategory.name}} +
  • +
+
+ +
+ + {{ 'app.public.projects_show.markdown_file' | translate }} - {{ 'app.public.projects_show.deleted_user' }}
- {{ 'app.public.projects_show.posted_on_' | translate }} {{project.created_at | amDateFormat: 'LL'}} - -
- - {{theme.name}} - -
- -
- - -
-
- {{project.project_caos_attributes.length}} -

{{ 'app.public.projects_show.CAD_file_to_download' }}

-
- - -
- -
-
-

{{ 'app.public.projects_show.status' }}

-
-
- {{ project.status.name }} -
-
- -
-
- {{project.machines.length}} -

{{ 'app.public.projects_show.machines_and_materials' }}

-
- - - -
    -
  • - {{component.name}} -
  • -
-
- -
-
- {{project.project_users.length}} -

{{ 'app.public.projects_show.collaborators' }}

-
- -
    - -
-
- -
-
-

{{ 'app.public.projects_show.licence' }}

-
-
- {{ project.licence.name }} -
-
- -
-
-

{{ 'app.shared.project.tags' }}

-
-
-
{{ project.tags }}
-
-
- - +
+
- - -
diff --git a/app/frontend/templates/shared/header.html.erb b/app/frontend/templates/shared/header.html.erb index 7dbc6dc96..f644d1654 100644 --- a/app/frontend/templates/shared/header.html.erb +++ b/app/frontend/templates/shared/header.html.erb @@ -48,7 +48,7 @@
  • {{ 'app.public.common.my_events' }}
  • {{ 'app.public.common.my_invoices' }}
  • {{ 'app.public.common.my_payment_schedules' }}
  • -
  • {{ 'app.public.common.my_orders' }}
  • +
  • {{ 'app.public.common.my_orders' }}
  • {{ 'app.public.common.my_wallet' }}
  • {{ 'app.public.common.help' }}
  • diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index 1520bd44d..0f268a7e9 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -196,6 +196,10 @@ module SettingsHelper events_banner_cta_active events_banner_cta_label events_banner_cta_url + projects_list_member_filter_presence + projects_list_date_filters_presence + project_categories_filter_placeholder + project_categories_wording ].freeze end # rubocop:enable Metrics/ModuleLength diff --git a/app/models/project.rb b/app/models/project.rb index 238353e96..004fa62db 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -41,6 +41,8 @@ class Project < ApplicationRecord accepts_nested_attributes_for :project_steps, allow_destroy: true 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 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_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_project_category, ->(project_category_ids) { joins(:projects_project_categories).where(projects_project_categories: { project_category_id: project_category_ids }) } pg_search_scope :search, against: :search_vector, using: { diff --git a/app/models/project_category.rb b/app/models/project_category.rb new file mode 100644 index 000000000..20c0a3f45 --- /dev/null +++ b/app/models/project_category.rb @@ -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 diff --git a/app/models/project_step.rb b/app/models/project_step.rb index 0324c54f2..b9b76a248 100644 --- a/app/models/project_step.rb +++ b/app/models/project_step.rb @@ -5,4 +5,6 @@ class ProjectStep < ApplicationRecord belongs_to :project, touch: true has_many :project_step_images, as: :viewable, dependent: :destroy accepts_nested_attributes_for :project_step_images, allow_destroy: true, reject_if: :all_blank + + default_scope -> { order(:step_nb) } end diff --git a/app/models/projects_project_category.rb b/app/models/projects_project_category.rb new file mode 100644 index 000000000..8d2cb3f92 --- /dev/null +++ b/app/models/projects_project_category.rb @@ -0,0 +1,4 @@ +class ProjectsProjectCategory < ApplicationRecord + belongs_to :project + belongs_to :project_category +end diff --git a/app/policies/project_category_policy.rb b/app/policies/project_category_policy.rb new file mode 100644 index 000000000..33472a19e --- /dev/null +++ b/app/policies/project_category_policy.rb @@ -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 diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 8daad8143..8da49c747 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -16,10 +16,14 @@ class ProjectPolicy < ApplicationPolicy end 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 def destroy? - user.admin? or record.author.user_id == user.id + user.admin? || record.author.user_id == user.id end end diff --git a/app/policies/setting_policy.rb b/app/policies/setting_policy.rb index 1593beb55..1a74868be 100644 --- a/app/policies/setting_policy.rb +++ b/app/policies/setting_policy.rb @@ -46,7 +46,8 @@ class SettingPolicy < ApplicationPolicy 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 trainings_banner_cta_url events_banner_active events_banner_text events_banner_cta_active 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] end ## diff --git a/app/services/accounting/accounting_service.rb b/app/services/accounting/accounting_service.rb index ee224065e..1ecfb4b28 100644 --- a/app/services/accounting/accounting_service.rb +++ b/app/services/accounting/accounting_service.rb @@ -22,11 +22,19 @@ class Accounting::AccountingService lines = [] processed = [] invoices.find_each do |i| - Rails.logger.debug { "processing invoice #{i.id}..." } unless Rails.env.test? - lines.concat(generate_lines(i)) - processed.push(i.id) + Rails.logger.debug { "[AccountLine] processing invoice #{i.id}..." } unless Rails.env.test? + if i.main_item.nil? + 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 - AccountingLine.create!(lines) processed end diff --git a/app/services/project_service.rb b/app/services/project_service.rb index 823d9365d..0e6abf32b 100644 --- a/app/services/project_service.rb +++ b/app/services/project_service.rb @@ -2,17 +2,17 @@ # Provides methods for Project class ProjectService - def search(params, current_user) + def search(params, current_user, paginate: true) connection = ActiveRecord::Base.connection 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 private - def search_from_postgre(params, current_user) - query_params = JSON.parse(params[:search]) + def search_from_postgre(params, current_user, paginate: true) + query_params = JSON.parse(params[:search] || "{}") records = Project.published_or_drafts(current_user&.statistic_profile&.id) 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_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_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_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.search(query_params['q']) else records.order(created_at: :desc) 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 diff --git a/app/services/project_to_markdown.rb b/app/services/project_to_markdown.rb new file mode 100644 index 000000000..7f3521ff0 --- /dev/null +++ b/app/services/project_to_markdown.rb @@ -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 \ No newline at end of file diff --git a/app/services/projects_archive.rb b/app/services/projects_archive.rb new file mode 100644 index 000000000..009ea661f --- /dev/null +++ b/app/services/projects_archive.rb @@ -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 \ No newline at end of file diff --git a/app/views/api/project_categories/index.json.jbuilder b/app/views/api/project_categories/index.json.jbuilder new file mode 100644 index 000000000..23380c6d0 --- /dev/null +++ b/app/views/api/project_categories/index.json.jbuilder @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +json.array!(@project_categories) do |project_category| + json.extract! project_category, :id, :name +end diff --git a/app/views/api/projects/_indexed.json.jbuilder b/app/views/api/projects/_indexed.json.jbuilder index afdfa520d..326126429 100644 --- a/app/views/api/projects/_indexed.json.jbuilder +++ b/app/views/api/projects/_indexed.json.jbuilder @@ -7,6 +7,7 @@ json.user_ids project.user_ids json.machine_ids project.machine_ids json.theme_ids project.theme_ids json.component_ids project.component_ids +json.project_category_ids project.project_category_ids json.tags project.tags json.name project.name json.description project.description diff --git a/app/views/api/projects/show.json.jbuilder b/app/views/api/projects/show.json.jbuilder index e13618f81..0c76e9edb 100644 --- a/app/views/api/projects/show.json.jbuilder +++ b/app/views/api/projects/show.json.jbuilder @@ -39,6 +39,11 @@ json.themes @project.themes do |t| json.id t.id json.name t.name 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.project_users @project.project_users do |pu| json.id pu.user.id diff --git a/app/workers/accounting_worker.rb b/app/workers/accounting_worker.rb index e2cf5f448..71b5a443a 100644 --- a/app/workers/accounting_worker.rb +++ b/app/workers/accounting_worker.rb @@ -27,8 +27,6 @@ class AccountingWorker end def invoices(invoices_ids) - # clean - AccountingLine.where(invoice_id: invoices_ids).delete_all # build service = Accounting::AccountingService.new invoices = Invoice.where(id: invoices_ids) @@ -37,8 +35,6 @@ class AccountingWorker end def all - # clean - AccountingLine.delete_all # build service = Accounting::AccountingService.new ids = service.build_from_invoices(Invoice.all) diff --git a/config/locales/app.admin.de.yml b/config/locales/app.admin.de.yml index 1c7c20384..fa60516d2 100644 --- a/config/locales/app.admin.de.yml +++ b/config/locales/app.admin.de.yml @@ -415,6 +415,8 @@ de: add_a_material: "Materialien hinfügen" themes: "Themen" add_a_new_theme: "Neues Thema hinzufügen" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Lizenzen" statuses: "Statuses" description: "Beschreibung" @@ -445,6 +447,10 @@ de: 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.
    Hier können Sie die standardmäßig angezeigte Ansicht auswählen." default_to_openlab: "OpenLab standardmäßig anzeigen" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Hinzufügen" actions_controls: "Actions" @@ -1773,6 +1779,10 @@ de: extended_prices_in_same_day: "Erweiterte Preise am selben Tag" public_registrations: "Öffentliche Registrierungen" 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: training_reservations: "Schulungen" machine_reservations: "Maschinen" diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 8714ac56d..c91c20d25 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -415,6 +415,8 @@ en: add_a_material: "Add a material" themes: "Themes" add_a_new_theme: "Add a new theme" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Licences" statuses: "Statuses" description: "Description" @@ -445,6 +447,10 @@ en: 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.
    Here, you can choose which view is shown by default." default_to_openlab: "Display OpenLab by default" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Add" actions_controls: "Actions" @@ -1773,6 +1779,10 @@ en: extended_prices_in_same_day: "Extended prices in the same day" public_registrations: "Public registrations" 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: training_reservations: "Trainings" machine_reservations: "Machines" diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index aba85bcc3..d230e753b 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -415,6 +415,8 @@ es: add_a_material: "Añadir un material" themes: "Temas" add_a_new_theme: "Añadir un nuevo tema" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Licencias" statuses: "Statuses" description: "Descripción" @@ -445,6 +447,10 @@ es: 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.
    Here, you can choose which view is shown by default." default_to_openlab: "Display OpenLab by default" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Add" actions_controls: "Actions" @@ -1773,6 +1779,10 @@ es: extended_prices_in_same_day: "Extended prices in the same day" public_registrations: "Public registrations" 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: training_reservations: "Trainings" machine_reservations: "Machines" diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index e7082ef58..4bc83e3aa 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -415,6 +415,8 @@ fr: add_a_material: "Ajouter un matériau" themes: "Thématiques" add_a_new_theme: "Ajouter une nouvelle thématique" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Licences" statuses: "Statuts" description: "Description" @@ -445,6 +447,10 @@ fr: 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.
    Ici, vous pouvez choisir quelle vue est affichée par défaut." default_to_openlab: "Afficher OpenLab par défaut" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Ajouter" actions_controls: "Actions" @@ -1773,6 +1779,10 @@ fr: extended_prices_in_same_day: "Prix étendus le même jour" public_registrations: "Inscriptions publiques" 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" overlapping_options: training_reservations: "Formations" machine_reservations: "Machines" diff --git a/config/locales/app.admin.it.yml b/config/locales/app.admin.it.yml index 4a62c96b2..102b4ef2f 100644 --- a/config/locales/app.admin.it.yml +++ b/config/locales/app.admin.it.yml @@ -415,6 +415,8 @@ it: add_a_material: "Aggiungi un materiale" themes: "Temi" add_a_new_theme: "Aggiungi un nuovo tema" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Licenze" statuses: "Status" description: "Descrizione" @@ -445,6 +447,10 @@ it: 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.
    Qui, puoi scegliere quale vista è mostrata per impostazione predefinita." default_to_openlab: "Visualizza OpenLab per impostazione predefinita" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Aggiungi" actions_controls: "Azioni" @@ -1773,6 +1779,10 @@ it: extended_prices_in_same_day: "Prezzi estesi nello stesso giorno" public_registrations: "Registri pubblici" 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: training_reservations: "Abilitazioni" machine_reservations: "Macchine" diff --git a/config/locales/app.admin.no.yml b/config/locales/app.admin.no.yml index 352d90f9d..67b76790e 100644 --- a/config/locales/app.admin.no.yml +++ b/config/locales/app.admin.no.yml @@ -415,6 +415,8 @@ add_a_material: "Legg til et materiale" themes: "Temaer" add_a_new_theme: "Legge til et nytt tema" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Lisenser" statuses: "Statuses" description: "Beskrivelse" @@ -445,6 +447,10 @@ 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.
    Her kan du velge hvilken visning som standard." default_to_openlab: "Vis OpenLab som standard" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Add" actions_controls: "Actions" @@ -1773,6 +1779,10 @@ extended_prices_in_same_day: "Extended prices in the same day" public_registrations: "Public registrations" 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: training_reservations: "Trainings" machine_reservations: "Machines" diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index c3b88f936..05eb8c93b 100644 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -415,6 +415,8 @@ pt: add_a_material: "Adicionar material" themes: "Temas" add_a_new_theme: "Adicionar um novo tema" + project_categories: "Categories" + add_a_new_project_category: "Add a new category" licences: "Licenças" statuses: "Statuses" description: "Descrição" @@ -445,6 +447,10 @@ pt: 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.
    Aqui, você pode escolher qual exibição é mostrada por padrão." default_to_openlab: "Mostrar OpenLab por padrão" + filters: Projects list filters + project_categories: Categories + project_categories: + name: "Name" projects_setting: add: "Add" actions_controls: "Actions" @@ -1773,6 +1779,10 @@ pt: extended_prices_in_same_day: "Preços estendidos no mesmo dia" public_registrations: "Inscrições públicas" 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: training_reservations: "Treinamentos" machine_reservations: "Máquinas" diff --git a/config/locales/app.admin.zu.yml b/config/locales/app.admin.zu.yml index 61a96210a..9977ec4cb 100644 --- a/config/locales/app.admin.zu.yml +++ b/config/locales/app.admin.zu.yml @@ -415,6 +415,8 @@ zu: add_a_material: "crwdns24306:0crwdne24306:0" themes: "crwdns24308:0crwdne24308: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" statuses: "crwdns36893:0crwdne36893:0" description: "crwdns24314:0crwdne24314:0" @@ -445,6 +447,10 @@ zu: open_lab_app_secret: "crwdns24362:0crwdne24362:0" openlab_default_info_html: "crwdns37609:0crwdne37609:0" default_to_openlab: "crwdns24366:0crwdne24366:0" + filters: crwdns37621:0crwdne37621:0 + project_categories: crwdns37623:0crwdne37623:0 + project_categories: + name: "crwdns37625:0crwdne37625:0" projects_setting: add: "crwdns36895:0crwdne36895:0" actions_controls: "crwdns36897:0crwdne36897:0" @@ -1773,6 +1779,10 @@ zu: extended_prices_in_same_day: "crwdns26752:0crwdne26752:0" public_registrations: "crwdns26754:0crwdne26754: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: training_reservations: "crwdns26758:0crwdne26758:0" machine_reservations: "crwdns26760:0crwdne26760:0" diff --git a/config/locales/app.public.de.yml b/config/locales/app.public.de.yml index 01ea569e1..b8f0a9d2b 100644 --- a/config/locales/app.public.de.yml +++ b/config/locales/app.public.de.yml @@ -167,6 +167,7 @@ de: full_price: "Voller Preis: " #projects gallery projects_list: + filter: Filter the_fablab_projects: "The projects" add_a_project: "Projekt hinzufügen" network_search: "Fab-manager network" @@ -183,6 +184,10 @@ de: all_materials: "Alle Materialien" load_next_projects: "Nächste Projekte laden" rough_draft: "Grober Entwurf" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "All statuses" select_status: "Select a status" @@ -216,6 +221,7 @@ de: report: "Melden" do_you_really_want_to_delete_this_project: "Wollen Sie dieses Projekt wirklich löschen?" status: "Status" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "The machines" diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index 7dfc61f69..c90cbf0de 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -167,6 +167,7 @@ en: full_price: "Full price: " #projects gallery projects_list: + filter: Filter the_fablab_projects: "The projects" add_a_project: "Add a project" network_search: "Fab-manager network" @@ -183,6 +184,10 @@ en: all_materials: "All materials" load_next_projects: "Load next projects" rough_draft: "Rough draft" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "All statuses" select_status: "Select a status" @@ -216,6 +221,7 @@ en: report: "Report" do_you_really_want_to_delete_this_project: "Do you really want to delete this project?" status: "Status" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "The machines" diff --git a/config/locales/app.public.es.yml b/config/locales/app.public.es.yml index b6b78c9b0..d884d4321 100644 --- a/config/locales/app.public.es.yml +++ b/config/locales/app.public.es.yml @@ -167,6 +167,7 @@ es: full_price: "Full price: " #projects gallery projects_list: + filter: Filter the_fablab_projects: "The projects" add_a_project: "Añadir un proyecto" network_search: "Fab-manager network" @@ -183,6 +184,10 @@ es: all_materials: "Todo el material" load_next_projects: "Cargar más proyectos" rough_draft: "Borrador" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "All statuses" select_status: "Select a status" @@ -216,6 +221,7 @@ es: report: "Reportar" do_you_really_want_to_delete_this_project: "¿Está seguro de querer eliminar este proyecto?" status: "Status" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "The machines" diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 09a07a116..26d8ac301 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -167,6 +167,7 @@ fr: full_price: "Plein tarif : " #projects gallery projects_list: + filter: Filter the_fablab_projects: "Les projets" add_a_project: "Ajouter un projet" network_search: "Réseau Fab-Manager" @@ -183,6 +184,10 @@ fr: all_materials: "Tous les matériaux" load_next_projects: "Charger les projets suivants" rough_draft: "Brouillon" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "Tous les statuts" select_status: "Sélectionnez un statut" @@ -216,6 +221,7 @@ fr: report: "Signaler" do_you_really_want_to_delete_this_project: "Êtes-vous sur de vouloir supprimer ce projet ?" status: "Statut" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "Les machines" diff --git a/config/locales/app.public.it.yml b/config/locales/app.public.it.yml index 963c6a577..3b48a4ebb 100644 --- a/config/locales/app.public.it.yml +++ b/config/locales/app.public.it.yml @@ -167,6 +167,7 @@ it: full_price: "Prezzo intero: " #projects gallery projects_list: + filter: Filter the_fablab_projects: "Progetti" add_a_project: "Aggiungi un progetto" network_search: "Fab-manager network" @@ -183,6 +184,10 @@ it: all_materials: "Tutti i materiali" load_next_projects: "Carica i progetti successivi" rough_draft: "Bozza preliminare" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "Tutti gli stati" select_status: "Seleziona uno status" @@ -216,6 +221,7 @@ it: report: "Segnalazione" do_you_really_want_to_delete_this_project: "Vuoi davvero eliminare questo progetto?" status: "Stato" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "Le macchine" diff --git a/config/locales/app.public.no.yml b/config/locales/app.public.no.yml index aa9d6a1dc..b1753896c 100644 --- a/config/locales/app.public.no.yml +++ b/config/locales/app.public.no.yml @@ -167,6 +167,7 @@ full_price: "Full pris: " #projects gallery projects_list: + filter: Filter the_fablab_projects: "The projects" add_a_project: "Legg til et prosjekt" network_search: "Fab-manager network" @@ -183,6 +184,10 @@ all_materials: "Alle materialer" load_next_projects: "Last neste prosjekt" rough_draft: "Tidlig utkast" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "All statuses" select_status: "Select a status" @@ -216,6 +221,7 @@ report: "Rapporter" do_you_really_want_to_delete_this_project: "Vil du virkelig slette dette prosjektet?" status: "Status" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "The machines" diff --git a/config/locales/app.public.pt.yml b/config/locales/app.public.pt.yml index 77f61af48..3fcaaec49 100644 --- a/config/locales/app.public.pt.yml +++ b/config/locales/app.public.pt.yml @@ -167,6 +167,7 @@ pt: full_price: "Valor inteira: " #projects gallery projects_list: + filter: Filter the_fablab_projects: "Os projetos" add_a_project: "Adicionar projeto" network_search: "Rede Fab-manager" @@ -183,6 +184,10 @@ pt: all_materials: "Todos os materiais" load_next_projects: "Carregar próximos projetos" rough_draft: "Rascunho" + filter_by_member: "Filter by member" + created_from: Created from + created_to: Created to + download_archive: Download status_filter: all_statuses: "Todos os status" select_status: "Selecione um status" @@ -216,6 +221,7 @@ pt: report: "Enviar" do_you_really_want_to_delete_this_project: "Você quer realmente deletar esse projeto?" status: "Status" + markdown_file: "Markdown file" #list of machines machines_list: the_fablab_s_machines: "As máquinas" diff --git a/config/locales/app.public.zu.yml b/config/locales/app.public.zu.yml index 3c30ca692..417ead98d 100644 --- a/config/locales/app.public.zu.yml +++ b/config/locales/app.public.zu.yml @@ -167,6 +167,7 @@ zu: full_price: "crwdns28058:0crwdne28058:0" #projects gallery projects_list: + filter: crwdns37635:0crwdne37635:0 the_fablab_projects: "crwdns36237:0crwdne36237:0" add_a_project: "crwdns28062:0crwdne28062:0" network_search: "crwdns37071:0crwdne37071:0" @@ -183,6 +184,10 @@ zu: all_materials: "crwdns28088:0crwdne28088:0" load_next_projects: "crwdns28090:0crwdne28090: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: all_statuses: "crwdns37073:0crwdne37073:0" select_status: "crwdns37075:0crwdne37075:0" @@ -216,6 +221,7 @@ zu: report: "crwdns28144:0crwdne28144:0" do_you_really_want_to_delete_this_project: "crwdns28146:0crwdne28146:0" status: "crwdns37077:0crwdne37077:0" + markdown_file: "crwdns37645:0crwdne37645:0" #list of machines machines_list: the_fablab_s_machines: "crwdns36239:0crwdne36239:0" diff --git a/config/locales/app.shared.de.yml b/config/locales/app.shared.de.yml index 70ecece02..2e5b7333e 100644 --- a/config/locales/app.shared.de.yml +++ b/config/locales/app.shared.de.yml @@ -131,6 +131,7 @@ de: illustration: "Ansicht" add_an_illustration: "Illustration hinzufügen" CAD_file: "CAD-Datei" + CAD_files: "CAD files" allowed_extensions: "Zugelassene Dateitypen:" add_a_new_file: "Neue Datei hinzufügen" description: "Beschreibung" @@ -138,6 +139,7 @@ de: steps: "Schritte" step_N: "Schritt {INDEX}" step_title: "Titel des Schrits" + step_image: "Image" add_a_picture: "Ein Bild hinzufügen" change_the_picture: "Bild ändern" delete_the_step: "Diesen Schritt löschen" @@ -149,7 +151,9 @@ de: employed_materials: "Verwendetes Material" employed_machines: "Verwendete Maschinen" collaborators: "Mitarbeitende" + author: Author creative_commons_licences: "Creative Commons-Lizenzen" + licence: "Licence" themes: "Themen" tags: "Stichwörter" save_as_draft: "Als Entwurf speichern" diff --git a/config/locales/app.shared.en.yml b/config/locales/app.shared.en.yml index 423e3a6f9..bfe88a627 100644 --- a/config/locales/app.shared.en.yml +++ b/config/locales/app.shared.en.yml @@ -131,6 +131,7 @@ en: illustration: "Visual" add_an_illustration: "Add an illustration" CAD_file: "CAD file" + CAD_files: "CAD files" allowed_extensions: "Allowed extensions:" add_a_new_file: "Add a new file" description: "Description" @@ -138,6 +139,7 @@ en: steps: "Steps" step_N: "Step {INDEX}" step_title: "Step title" + step_image: "Image" add_a_picture: "Add a picture" change_the_picture: "Change the picture" delete_the_step: "Delete the step" @@ -149,7 +151,9 @@ en: employed_materials: "Employed materials" employed_machines: "Employed machines" collaborators: "Collaborators" + author: Author creative_commons_licences: "Creative Commons licences" + licence: "Licence" themes: "Themes" tags: "Tags" save_as_draft: "Save as draft" diff --git a/config/locales/app.shared.es.yml b/config/locales/app.shared.es.yml index da12eb735..f2d54ba74 100644 --- a/config/locales/app.shared.es.yml +++ b/config/locales/app.shared.es.yml @@ -131,6 +131,7 @@ es: illustration: "Ilustración" add_an_illustration: "Añadir una ilustración" CAD_file: "Fichero CAD" + CAD_files: "CAD files" allowed_extensions: "Extensiones permitidas:" add_a_new_file: "Añadir un nuevo archivo" description: "Description" @@ -138,6 +139,7 @@ es: steps: "Pasos" step_N: "Step {INDEX}" step_title: "Título de los pasos" + step_image: "Image" add_a_picture: "Añadir imagen" change_the_picture: "Cambiar imagen" delete_the_step: "Eliminar el paso" @@ -149,7 +151,9 @@ es: employed_materials: "Material empleados" employed_machines: "Máquinas empleadas" collaborators: "Collaborators" + author: Author creative_commons_licences: "Licencias Creative Commons" + licence: "Licence" themes: "Themes" tags: "Tags" save_as_draft: "Save as draft" diff --git a/config/locales/app.shared.fr.yml b/config/locales/app.shared.fr.yml index e03e762b3..8bf660c5d 100644 --- a/config/locales/app.shared.fr.yml +++ b/config/locales/app.shared.fr.yml @@ -131,6 +131,7 @@ fr: illustration: "Illustration" add_an_illustration: "Ajouter un visuel" CAD_file: "Fichier CAO" + CAD_files: "CAD files" allowed_extensions: "Extensions autorisées :" add_a_new_file: "Ajouter un nouveau fichier" description: "Description" @@ -138,6 +139,7 @@ fr: steps: "Étapes" step_N: "Étape {INDEX}" step_title: "Titre de l'étape" + step_image: "Image" add_a_picture: "Ajouter une image" change_the_picture: "Modifier l'image" delete_the_step: "Supprimer l'étape" @@ -149,7 +151,9 @@ fr: employed_materials: "Matériaux utilisés" employed_machines: "Machines utilisées" collaborators: "Les collaborateurs" + author: Author creative_commons_licences: "Licences Creative Commons" + licence: "Licence" themes: "Thématiques" tags: "Étiquettes" save_as_draft: "Enregistrer comme brouillon" diff --git a/config/locales/app.shared.it.yml b/config/locales/app.shared.it.yml index f136a1376..0186856bb 100644 --- a/config/locales/app.shared.it.yml +++ b/config/locales/app.shared.it.yml @@ -131,6 +131,7 @@ it: illustration: "Illustrazione" add_an_illustration: "Aggiungi un'illustrazione" CAD_file: "File CAD" + CAD_files: "CAD files" allowed_extensions: "Estensioni consentite:" add_a_new_file: "Aggiungi nuovo file" description: "Descrizione" @@ -138,6 +139,7 @@ it: steps: "Passaggi" step_N: "Passaggio {INDEX}" step_title: "Titolo del passaggio" + step_image: "Image" add_a_picture: "Aggiungi un'immagine" change_the_picture: "Cambia immagine" delete_the_step: "Elimina il passaggio" @@ -149,7 +151,9 @@ it: employed_materials: "Materiali impiegati" employed_machines: "Macchine impiegate" collaborators: "Collaboratori" + author: Author creative_commons_licences: "Licenze Creative Commons" + licence: "Licence" themes: "Temi" tags: "Etichette" save_as_draft: "Salva come bozza" diff --git a/config/locales/app.shared.no.yml b/config/locales/app.shared.no.yml index ce8e6acbd..da0d91a74 100644 --- a/config/locales/app.shared.no.yml +++ b/config/locales/app.shared.no.yml @@ -131,6 +131,7 @@ illustration: "Bilde" add_an_illustration: "Legg til en illustrasjon" CAD_file: "CAD-filer" + CAD_files: "CAD files" allowed_extensions: "Tillatte filtyper:" add_a_new_file: "Legg til ny fil" description: "Beskrivelse" @@ -138,6 +139,7 @@ steps: "Skritt" step_N: "Trinn {INDEX}" step_title: "Tittel på steg" + step_image: "Image" add_a_picture: "Legg til bilde" change_the_picture: "Endre bilde" delete_the_step: "Slett trinnet" @@ -149,7 +151,9 @@ employed_materials: "Materialer brukt" employed_machines: "Maskiner brukt" collaborators: "Samarbeidspartnere" + author: Author creative_commons_licences: "Creative Commons lisenser" + licence: "Licence" themes: "Temaer" tags: "Etiketter" save_as_draft: "Lagre som utkast" diff --git a/config/locales/app.shared.pt.yml b/config/locales/app.shared.pt.yml index 7ca328679..de9c0ad2d 100644 --- a/config/locales/app.shared.pt.yml +++ b/config/locales/app.shared.pt.yml @@ -131,6 +131,7 @@ pt: illustration: "Foto" add_an_illustration: "Adicionar foto" CAD_file: "Arquivo CAD" + CAD_files: "CAD files" allowed_extensions: "Extensões permitidas:" add_a_new_file: "Adicionar novo arquivo" description: "Descrição" @@ -138,6 +139,7 @@ pt: steps: "Passos" step_N: "Passo {INDEX}" step_title: "Passo Título" + step_image: "Image" add_a_picture: "Adicionar imagem" change_the_picture: "Alterar imagem" delete_the_step: "Deletar este passo" @@ -149,7 +151,9 @@ pt: employed_materials: "Materiais utilizados" employed_machines: "Máquinas utilizadas" collaborators: "Colaboradores" + author: Author creative_commons_licences: "Licença Creative Commons" + licence: "Licence" themes: "Temas" tags: "Tags" save_as_draft: "Salvar como rascunho" diff --git a/config/locales/app.shared.zu.yml b/config/locales/app.shared.zu.yml index 0f2f6cc5b..fc1b8764d 100644 --- a/config/locales/app.shared.zu.yml +++ b/config/locales/app.shared.zu.yml @@ -131,6 +131,7 @@ zu: illustration: "crwdns28728:0crwdne28728:0" add_an_illustration: "crwdns28730:0crwdne28730:0" CAD_file: "crwdns28732:0crwdne28732:0" + CAD_files: "crwdns37647:0crwdne37647:0" allowed_extensions: "crwdns28734:0crwdne28734:0" add_a_new_file: "crwdns28736:0crwdne28736:0" description: "crwdns28738:0crwdne28738:0" @@ -138,6 +139,7 @@ zu: steps: "crwdns28742:0crwdne28742:0" step_N: "crwdns28744:0{INDEX}crwdne28744:0" step_title: "crwdns28746:0crwdne28746:0" + step_image: "crwdns37649:0crwdne37649:0" add_a_picture: "crwdns28748:0crwdne28748:0" change_the_picture: "crwdns28750:0crwdne28750:0" delete_the_step: "crwdns28752:0crwdne28752:0" @@ -149,7 +151,9 @@ zu: employed_materials: "crwdns28764:0crwdne28764:0" employed_machines: "crwdns28766:0crwdne28766:0" collaborators: "crwdns28768:0crwdne28768:0" + author: crwdns37651:0crwdne37651:0 creative_commons_licences: "crwdns28770:0crwdne28770:0" + licence: "crwdns37653:0crwdne37653:0" themes: "crwdns28772:0crwdne28772:0" tags: "crwdns28774:0crwdne28774:0" save_as_draft: "crwdns28776:0crwdne28776:0" diff --git a/config/locales/de.yml b/config/locales/de.yml index f372552c4..63482dae1 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -697,6 +697,10 @@ de: trainings_authorization_validity_duration: "Trainings validity period duration" trainings_invalidation_rule: "Trainings automatic invalidation" 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: new: "Neu" diff --git a/config/locales/en.yml b/config/locales/en.yml index 3bd7aae37..248a18e50 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -697,6 +697,10 @@ en: trainings_authorization_validity_duration: "Trainings validity period duration" trainings_invalidation_rule: "Trainings automatic invalidation" 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: new: "New" diff --git a/config/locales/es.yml b/config/locales/es.yml index 869b6de38..5221aef5a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -697,6 +697,10 @@ es: trainings_authorization_validity_duration: "Trainings validity period duration" trainings_invalidation_rule: "Trainings automatic invalidation" 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: new: "New" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 5331975c3..6d8a4588f 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -697,6 +697,10 @@ fr: trainings_authorization_validity_duration: "Durée de la période de validité des formations" trainings_invalidation_rule: "Invalidation automatique des formations" 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: new: "Nouveau" diff --git a/config/locales/it.yml b/config/locales/it.yml index bb8eda00a..9a2ba4665 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -697,6 +697,10 @@ it: trainings_authorization_validity_duration: "Durata del periodo di validità delle abilitazioni" trainings_invalidation_rule: "Annullamento automatico delle abilitazioni" 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: new: "Nuovo" diff --git a/config/locales/no.yml b/config/locales/no.yml index 17dd1da76..3d42226a0 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -697,6 +697,10 @@ trainings_authorization_validity_duration: "Trainings validity period duration" trainings_invalidation_rule: "Trainings automatic invalidation" 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: new: "New" diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 08b3bf79b..2aced98a9 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -697,6 +697,10 @@ pt: trainings_authorization_validity_duration: "Trainings validity period duration" trainings_invalidation_rule: "Trainings automatic invalidation" 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: new: "Novo" diff --git a/config/locales/zu.yml b/config/locales/zu.yml index ae7cc63dd..1388b87ae 100644 --- a/config/locales/zu.yml +++ b/config/locales/zu.yml @@ -697,6 +697,10 @@ zu: trainings_authorization_validity_duration: "crwdns37105:0crwdne37105:0" trainings_invalidation_rule: "crwdns37107:0crwdne37107: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: new: "crwdns37111:0crwdne37111:0" diff --git a/config/routes.rb b/config/routes.rb index 4f9872af2..f4ce0b5af 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -38,6 +38,7 @@ Rails.application.routes.draw do get :last_published get :search end + get :markdown, on: :member end resources :openlab_projects, only: :index resources :machines @@ -46,6 +47,7 @@ Rails.application.routes.draw do resources :themes resources :licences resources :statuses + resources :project_categories resources :admins, only: %i[index create destroy] resources :settings, only: %i[show update index], param: :name do patch '/bulk_update', action: 'bulk_update', on: :collection diff --git a/db/migrate/20230626122844_create_project_categories.rb b/db/migrate/20230626122844_create_project_categories.rb new file mode 100644 index 000000000..2c4a21ca4 --- /dev/null +++ b/db/migrate/20230626122844_create_project_categories.rb @@ -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 diff --git a/db/migrate/20230626122947_create_projects_project_categories.rb b/db/migrate/20230626122947_create_projects_project_categories.rb new file mode 100644 index 000000000..2465af743 --- /dev/null +++ b/db/migrate/20230626122947_create_projects_project_categories.rb @@ -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 diff --git a/db/seeds/settings.rb b/db/seeds/settings.rb index 4df47c23c..7e455834e 100644 --- a/db/seeds/settings.rb +++ b/db/seeds/settings.rb @@ -728,3 +728,8 @@ Setting.set('accounting_Error_code', 'ERROR') unless Setting.find_by(name: 'acco Setting.set('accounting_Error_label', 'Erroneous invoices to refund') unless Setting.find_by(name: 'accounting_Error_label').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) diff --git a/db/structure.sql b/db/structure.sql index 0f648a913..a9aa1f96d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -9,13 +9,6 @@ SET xmloption = content; SET client_min_messages = warning; SET row_security = off; --- --- Name: public; Type: SCHEMA; Schema: -; Owner: - --- - --- *not* creating schema, since initdb creates it - - -- -- Name: fuzzystrmatch; Type: EXTENSION; Schema: -; Owner: - -- @@ -2725,6 +2718,37 @@ CREATE SEQUENCE public.profiles_id_seq 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: - -- @@ -2893,6 +2917,38 @@ CREATE SEQUENCE public.projects_machines_id_seq 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: - -- @@ -4746,6 +4802,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); +-- +-- 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: - -- @@ -4781,6 +4844,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); +-- +-- 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: - -- @@ -5646,6 +5716,14 @@ ALTER TABLE ONLY public.profiles 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: - -- @@ -5686,6 +5764,14 @@ ALTER TABLE ONLY public.projects 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: - -- @@ -5998,6 +6084,13 @@ ALTER TABLE ONLY public.wallets 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: - -- @@ -6894,6 +6987,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); +-- +-- 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: - -- @@ -8057,6 +8164,14 @@ ALTER TABLE ONLY public.projects 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: - -- @@ -8209,6 +8324,14 @@ ALTER TABLE ONLY public.event_price_categories 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: - -- @@ -8693,6 +8816,8 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230324095639'), ('20230328094807'), ('20230328094808'), -('20230328094809'); +('20230328094809'), +('20230626122844'), +('20230626122947'); diff --git a/package.json b/package.json index 1805174ea..3d86e5e37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fab-manager", - "version": "6.0.7", + "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.", "keywords": [ "fablab", diff --git a/test/fixtures/history_values.yml b/test/fixtures/history_values.yml index 4ce437af6..e8ac8f534 100644 --- a/test/fixtures/history_values.yml +++ b/test/fixtures/history_values.yml @@ -852,3 +852,32 @@ history_value_100: updated_at: 2023-04-05 09:16:08.000511500 Z invoicing_profile_id: 1 +history_value_101: + setting_id: 100 + value: 'Toutes les 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_102: + 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 + invoicing_profile_id: 1 diff --git a/test/fixtures/project_categories.yml b/test/fixtures/project_categories.yml new file mode 100644 index 000000000..d0216e095 --- /dev/null +++ b/test/fixtures/project_categories.yml @@ -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 diff --git a/test/fixtures/project_steps.yml b/test/fixtures/project_steps.yml index cc2516cba..96d44b4fb 100644 --- a/test/fixtures/project_steps.yml +++ b/test/fixtures/project_steps.yml @@ -7,6 +7,7 @@ project_step_1: created_at: 2016-04-04 15:39:08.259759000 Z updated_at: 2016-04-04 15:39:08.259759000 Z title: Le manche + step_nb: 1 project_step_2: id: 2 @@ -16,3 +17,4 @@ project_step_2: created_at: 2016-04-04 15:39:08.265840000 Z updated_at: 2016-04-04 15:39:08.265840000 Z title: La presse + step_nb: 2 \ No newline at end of file diff --git a/test/fixtures/projects.yml b/test/fixtures/projects.yml index 07e635e93..cec0d59b7 100644 --- a/test/fixtures/projects.yml +++ b/test/fixtures/projects.yml @@ -12,3 +12,4 @@ project_1: state: published slug: presse-puree published_at: 2016-04-04 15:39:08.267614000 Z + status_id: 1 \ No newline at end of file diff --git a/test/fixtures/projects_project_categories.yml b/test/fixtures/projects_project_categories.yml new file mode 100644 index 000000000..6dac837e3 --- /dev/null +++ b/test/fixtures/projects_project_categories.yml @@ -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 diff --git a/test/fixtures/settings.yml b/test/fixtures/settings.yml index eb21fa7f8..e0ce32209 100644 --- a/test/fixtures/settings.yml +++ b/test/fixtures/settings.yml @@ -586,3 +586,27 @@ setting_99: name: home_css created_at: 2023-04-05 09:16:08.000511500 Z updated_at: 2023-04-05 09:16:08.000511500 Z + +setting_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 diff --git a/test/frontend/__fixtures__/settings.ts b/test/frontend/__fixtures__/settings.ts index 03dd134be..262b47f67 100644 --- a/test/frontend/__fixtures__/settings.ts +++ b/test/frontend/__fixtures__/settings.ts @@ -825,6 +825,30 @@ export const settings: Array = [ value: 'https://www.sleede.com/', last_update: '2022-12-23T14:39:12+0100', 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' } ]; diff --git a/test/integration/project_categories_test.rb b/test/integration/project_categories_test.rb new file mode 100644 index 000000000..dc2872e26 --- /dev/null +++ b/test/integration/project_categories_test.rb @@ -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 diff --git a/test/integration/projects_test.rb b/test/integration/projects_test.rb new file mode 100644 index 000000000..60477000f --- /dev/null +++ b/test/integration/projects_test.rb @@ -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 \ No newline at end of file diff --git a/test/models/project_category_test.rb b/test/models/project_category_test.rb new file mode 100644 index 000000000..59a705c80 --- /dev/null +++ b/test/models/project_category_test.rb @@ -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 diff --git a/test/models/project_test.rb b/test/models/project_test.rb new file mode 100644 index 000000000..452ce3806 --- /dev/null +++ b/test/models/project_test.rb @@ -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 diff --git a/test/services/project_to_markdown_test.rb b/test/services/project_to_markdown_test.rb new file mode 100644 index 000000000..388f615f1 --- /dev/null +++ b/test/services/project_to_markdown_test.rb @@ -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