Initialisation du repo

This commit is contained in:
FyloZ 2019-08-14 16:50:42 -04:00
commit bbe436c758
117 changed files with 7713 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
HELP.md
/target/
!.mvn/wrapper/maven-wrapper.jar
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
/build/
### VS Code ###
.vscode/

114
.mvn/wrapper/MavenWrapperDownloader.java vendored Normal file
View File

@ -0,0 +1,114 @@
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Properties;
public class MavenWrapperDownloader {
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL =
"https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties existsByName, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: : " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output direcrory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

1
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.0/apache-maven-3.6.0-bin.zip

BIN
TestProd.pdf Normal file

Binary file not shown.

286
mvnw vendored Normal file
View File

@ -0,0 +1,286 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
# TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
wget "$jarUrl" -O "$wrapperJarPath"
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
curl -o "$wrapperJarPath" "$jarUrl"
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

161
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,161 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
echo Found %WRAPPER_JAR%
) else (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
echo Finished downloading %WRAPPER_JAR%
)
@REM End of extension
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

80
pom.xml Normal file
View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>fyloz.trial</groupId>
<artifactId>ColorRecipesExplorer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ColorRecipesExplorer</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- H2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
<!-- PDF -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.10</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

BIN
recipes.mv.db Normal file

Binary file not shown.

View File

@ -0,0 +1,57 @@
package fyloz.trial.ColorRecipesExplorer;
import fyloz.trial.ColorRecipesExplorer.core.io.FileHandler;
import fyloz.trial.ColorRecipesExplorer.web.StringBank;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
@SpringBootApplication
public class ColorRecipesExplorerApplication {
public static final Logger logger = LoggerFactory.getLogger(ColorRecipesExplorerApplication.class);
// Chemin du fichier contenant les utilisateurs
public static final String USERS_FILE_NAME = "passwords";
public static void main(String[] args) {
logger.info("Le fichier des utilisateurs se situe à: " + new File(StringBank.UPLOAD_LOCATION + "/" + USERS_FILE_NAME).getAbsolutePath());
loadPasswords();
SpringApplication.run(ColorRecipesExplorerApplication.class, args);
}
/**
* Charge les mots de passes contenus dans le fichier.
* <p>
* Un mot de passe correspond à une ligne dans le fichier passwords.txt.
*
* @throws IOException
*/
private static void loadPasswords() {
FileHandler fileHandler = new FileHandler(USERS_FILE_NAME, FileHandler.FileContext.OTHERS, FileHandler.FileExtension.TXT);
if (!fileHandler.isValid()) {
fileHandler.createFile();
}
try {
List<String> fileContent = Files.readAllLines(fileHandler.getPath());
if (fileContent.size() < 1) {
logger.warn("Aucun mot de passe trouvé. Il sera impossible d'utiliser certaines fonctionnalitées de l'application.");
}
for (String line : fileContent) {
PasswordValidator.addPassword(line);
}
} catch (IOException e) {
logger.error("Une erreur est survenue lors du chargement du fichier des utilisateurs", e);
logger.warn("Il sera impossible d'utiliser certaines fonctionnalitées de l'application.");
}
}
}

View File

@ -0,0 +1,17 @@
package fyloz.trial.ColorRecipesExplorer;
import java.util.ArrayList;
import java.util.List;
public class PasswordValidator {
private static List<String> passwords = new ArrayList<>();
public static boolean isValid(String password) {
return passwords.contains(password);
}
public static void addPassword(String password) {
passwords.add(password);
}
}

View File

@ -0,0 +1,37 @@
package fyloz.trial.ColorRecipesExplorer.core.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InjectionPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.MethodParameter;
import java.lang.reflect.Field;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
@Configuration
public class LoggingConfiguration {
/**
* Injecte un Logger dans les méthodes qui @Autowired un Logger
*
* @param ip Le point d'injection
* @return Le Logger à injecter.
*/
@Bean
@Scope("prototype")
public Logger logger(final InjectionPoint ip) {
return LoggerFactory.getLogger(of(ip.getMethodParameter())
.<Class>map(MethodParameter::getContainingClass)
.orElseGet(() ->
ofNullable(ip.getField())
.map(Field::getDeclaringClass)
.orElseThrow(IllegalArgumentException::new)
)
);
}
}

View File

@ -0,0 +1,134 @@
package fyloz.trial.ColorRecipesExplorer.core.io;
import fyloz.trial.ColorRecipesExplorer.ColorRecipesExplorerApplication;
import org.slf4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.UPLOAD_LOCATION;
public class FileHandler {
protected String name;
protected Logger logger = ColorRecipesExplorerApplication.logger;
private FileContext context;
private FileExtension extension;
private File file;
public FileHandler(String name, FileContext context, FileExtension extension) {
this.context = context;
this.extension = extension;
setName(name);
file = getFile();
}
public boolean createFile() {
File parent = file.getParentFile();
if (!isValid()) {
if ((!parent.exists() || !parent.isDirectory()) && !parent.mkdirs()) {
return false;
}
try {
logger.info("Création du fichier " + file.getAbsolutePath());
return file.createNewFile();
} catch (IOException e) {
logger.error("Erreur à la création d'un fichier", e);
return false;
}
} else {
logger.warn("Tentative de création du fichier existant " + file.getAbsolutePath());
return false;
}
}
public byte[] readFile() {
if (isValid()) {
try {
return Files.readAllBytes(getPath());
} catch (IOException e) {
logger.error("Erreur lors de la lecture d'un fichier", e);
}
}
logger.warn("Tentative de lecture du fichier invalide " + file.getAbsolutePath());
return null;
}
public boolean deleteFile() {
if (isValid()) {
logger.info("Suppression du fichier " + file.getAbsolutePath());
return file.delete();
}
logger.warn("Tentative de suppression du fichier invalide " + file.getAbsolutePath());
return false;
}
public boolean isValid() {
return file.exists() && file.isFile();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
file = getFile();
}
public FileContext getContext() {
return context;
}
public Path getPath() {
return Paths.get(String.format("%s/%s/%s%s", UPLOAD_LOCATION, context.getPath(), name, extension.getExtension()));
}
public File getFile() {
return getPath().toFile();
}
public enum FileContext {
IMAGE("images"),
SIMDUT("simdut"),
PDF("pdf"),
OTHERS("");
private String path;
FileContext(String path) {
this.path = path;
}
public String getPath() {
return path;
}
}
public enum FileExtension {
JPEG("jpeg"),
PDF("pdf"),
TXT("txt"),
DB("db");
private String extension;
FileExtension(String extension) {
this.extension = extension;
}
public String getExtension() {
return "." + extension;
}
}
}

View File

@ -0,0 +1,49 @@
package fyloz.trial.ColorRecipesExplorer.core.io;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import java.util.List;
import java.util.stream.Collectors;
public class ImageHandler extends FileHandler {
private Recipe recipe;
private int index = 0;
private RecipeService recipeService;
public ImageHandler(Recipe recipe, RecipeService recipeService) {
super(String.format("%s_%s", recipe.getRecipeID(), recipe.getRecipeCode()), FileContext.IMAGE, FileExtension.JPEG);
this.recipe = recipe;
this.recipeService = recipeService;
}
public ImageHandler(String name, RecipeService recipeService) {
super(name.replace(".jpeg", ""), FileContext.IMAGE, FileExtension.JPEG);
String[] nameParts = name.split("-");
index = Integer.parseInt(nameParts[1].replace(".jpeg", ""));
}
@Override
public boolean createFile() {
if (recipe != null) {
List<String> existingImages = recipeService.getImageFiles(recipe);
if (existingImages != null) {
List<Integer> existingImagesIndexes = existingImages.stream().map(n -> Integer.parseInt(n.replace(name + "-", "").replace(".jpeg", ""))).collect(Collectors.toList());
while (existingImagesIndexes.contains(index)) {
index++;
}
}
setName(String.format("%s-%s", getName(), index));
return super.createFile();
}
logger.warn("Tentative de créer une image sans recette");
return false;
}
}

View File

@ -0,0 +1,13 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Company;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CompanyDao extends JpaRepository<Company, Integer> {
void deleteByCompanyID(int companyID);
Company findByCompanyName(String companyName);
}

View File

@ -0,0 +1,12 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MaterialDao extends JpaRepository<Material, Integer> {
Material findByMaterialID(int materialID);
Material findByMaterialCode(String materialCode);
}

View File

@ -0,0 +1,10 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.MaterialType;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MaterialTypeDao extends JpaRepository<MaterialType, Integer> {
MaterialType findByMaterialTypeName(String name);
}

View File

@ -0,0 +1,16 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Mix;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MixDao extends JpaRepository<Mix, Integer> {
Mix findByMixID(int mixID);
List<Mix> findAllByRecipe(Recipe recipe);
}

View File

@ -0,0 +1,15 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.MixQuantity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MixQuantityDao extends JpaRepository<MixQuantity, Integer> {
MixQuantity findByMixQuantityID(int mixQuantityID);
List<MixQuantity> findAllByMaterial(Material material);
}

View File

@ -0,0 +1,15 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.MixType;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MixTypeDao extends JpaRepository<MixType, Integer> {
MixType findByTypeID(int id);
MixType findByTypeName(String typeName);
MixType findByMaterial(Material material);
}

View File

@ -0,0 +1,18 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Company;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface RecipeDao extends JpaRepository<Recipe, Integer> {
List<Recipe> findAllByCompany(Company company);
Recipe findByRecipeCode(String recipeCode);
Recipe findByRecipeID(int recipeID);
}

View File

@ -0,0 +1,15 @@
package fyloz.trial.ColorRecipesExplorer.dao;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.model.RecipeStep;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface StepDao extends JpaRepository<RecipeStep, Integer> {
RecipeStep findByStepID(int stepID);
List<RecipeStep> findAllByRecipe(Recipe recipe);
}

View File

@ -0,0 +1,44 @@
package fyloz.trial.ColorRecipesExplorer.factory.files;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import fyloz.trial.ColorRecipesExplorer.core.io.FileHandler;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
public class PdfFactory {
protected Document document;
private String fileName;
private FileHandler fileHandler;
public PdfFactory(String fileName) {
this(fileName, FileHandler.FileContext.OTHERS);
}
public PdfFactory(String fileName, FileHandler.FileContext context) {
this.fileName = fileName;
fileHandler = new FileHandler(fileName, context, FileHandler.FileExtension.PDF);
}
public PdfFactory open() throws FileNotFoundException, DocumentException {
document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(fileHandler.getPath().toString()));
document.open();
return this;
}
public void write() {
document.close();
}
public FileHandler getFileHandler() {
return fileHandler;
}
}

View File

@ -0,0 +1,59 @@
package fyloz.trial.ColorRecipesExplorer.factory.files;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfWriter;
import fyloz.trial.ColorRecipesExplorer.ColorRecipesExplorerApplication;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class TouchUpKitPdfFactory {
private String kitName;
private ByteArrayOutputStream pdfContent;
private Document document;
public TouchUpKitPdfFactory(String kitName) {
this.kitName = kitName.toUpperCase();
pdfContent = new ByteArrayOutputStream();
document = new Document();
}
public TouchUpKitPdfFactory build() throws DocumentException {
ColorRecipesExplorerApplication.logger.info(String.format("Génération d'un PDF pour le kit de retouche '%s'", kitName));
PdfWriter.getInstance(document, pdfContent);
Font descFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, 48, BaseColor.BLACK);
Font kitNameFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD, kitName.length() > 22 ? 32 : 38, BaseColor.BLACK);
Paragraph descFr = new Paragraph("KIT DE RETOUCHE", descFont);
Paragraph descEn = new Paragraph("TOUCH UP KIT", descFont);
Paragraph touchUpKitName = new Paragraph(kitName, kitNameFont);
descFr.setAlignment(Element.ALIGN_CENTER);
descEn.setAlignment(Element.ALIGN_CENTER);
touchUpKitName.setAlignment(Element.ALIGN_CENTER);
document.open();
document.add(descFr);
document.add(descEn);
document.add(touchUpKitName);
document.close();
return this;
}
public byte[] toByteArray() {
byte[] content = pdfContent.toByteArray();
try {
pdfContent.close();
} catch (IOException e) {
ColorRecipesExplorerApplication.logger.error("Une erreur est survenue lors de la fermeture d'un ByteArrayOutputStream", e);
}
return content;
}
}

View File

@ -0,0 +1,5 @@
package fyloz.trial.ColorRecipesExplorer.model;
public abstract class BeanModel {
public abstract Integer getID();
}

View File

@ -0,0 +1,68 @@
package fyloz.trial.ColorRecipesExplorer.model;
import org.hibernate.validator.constraints.Length;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "companies")
public class Company extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private int companyID;
@Length(min = 2, max = 50)
@Column(unique = true)
@NotNull
private String companyName;
public Company() {
}
public Company(@Length(min = 2, max = 50) @NotNull String companyName) {
this.companyName = companyName;
}
public int getCompanyID() {
return companyID;
}
public void setCompanyID(int companyID) {
this.companyID = companyID;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
@Override
public Integer getID() {
return companyID;
}
@Override
public String toString() {
return "Company{" +
"companyID=" + companyID +
", companyName='" + companyName + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Company company = (Company) o;
return companyID == company.companyID &&
Objects.equals(companyName, company.companyName);
}
}

View File

@ -0,0 +1,110 @@
package fyloz.trial.ColorRecipesExplorer.model;
import org.hibernate.annotations.ColumnDefault;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "materials")
public class Material extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer materialID = 0;
@NotNull
private String materialCode;
@NotNull
private float inventoryQuantity = 0;
@NotNull
@ColumnDefault("false")
private boolean isMixType = false;
@ManyToOne
@NotNull
private MaterialType materialType;
public Material() {
}
public Material(@NotNull String materialCode, @NotNull float inventoryQuantity, @NotNull boolean isMixType, MaterialType materialType) {
this.materialCode = materialCode;
this.inventoryQuantity = inventoryQuantity;
this.isMixType = isMixType;
this.materialType = materialType;
}
public Integer getMaterialID() {
return materialID;
}
public void setMaterialID(Integer materialID) {
this.materialID = materialID;
}
public String getMaterialCode() {
return materialCode;
}
public void setMaterialCode(String materialCode) {
this.materialCode = materialCode;
}
public float getInventoryQuantity() {
return inventoryQuantity;
}
public void setInventoryQuantity(float inventoryQuantity) {
this.inventoryQuantity = inventoryQuantity;
}
public boolean isMixType() {
return isMixType;
}
public void setMixType(boolean mixType) {
isMixType = mixType;
}
public MaterialType getMaterialType() {
return materialType;
}
public void setMaterialType(MaterialType materialType) {
this.materialType = materialType;
}
@Override
public Integer getID() {
return materialID;
}
@Override
public String toString() {
return "Material{" +
"materialID=" + materialID +
", materialCode='" + materialCode + '\'' +
", inventoryQuantity=" + inventoryQuantity +
", isMixType=" + isMixType +
", materialType=" + materialType +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Material material = (Material) o;
return Float.compare(material.inventoryQuantity, inventoryQuantity) == 0 &&
isMixType == material.isMixType &&
Objects.equals(materialID, material.materialID) &&
Objects.equals(materialCode, material.materialCode) &&
Objects.equals(materialType, material.materialType);
}
}

View File

@ -0,0 +1,104 @@
package fyloz.trial.ColorRecipesExplorer.model;
import org.hibernate.annotations.ColumnDefault;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Entity
public class MaterialType extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer materialTypeID;
@Column(unique = true)
@NotNull
private String materialTypeName;
@NotNull
private String prefix;
@NotNull
@ColumnDefault("false")
private Boolean usePercentages = false;
@OneToMany
@JoinColumn(name = "material_type")
private List<Material> materials;
public MaterialType() {
}
public MaterialType(@NotNull String materialTypeName, @NotNull String prefix, @NotNull Boolean usePercentages) {
this.materialTypeName = materialTypeName;
this.prefix = prefix.toUpperCase();
this.usePercentages = usePercentages;
}
public Integer getMaterialTypeID() {
return materialTypeID;
}
public void setMaterialTypeID(Integer materialTypeID) {
this.materialTypeID = materialTypeID;
}
public String getMaterialTypeName() {
return materialTypeName;
}
public void setMaterialTypeName(String materialTypeName) {
this.materialTypeName = materialTypeName;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix.toUpperCase();
}
public Boolean getUsePercentages() {
return usePercentages;
}
public void setUsePercentages(Boolean usePercentages) {
this.usePercentages = usePercentages;
}
public List<Material> getMaterials() {
return materials;
}
@Override
public Integer getID() {
return materialTypeID;
}
@Override
public String toString() {
return "MaterialType{" +
"materialTypeID=" + materialTypeID +
", materialTypeName='" + materialTypeName + '\'' +
", prefix='" + prefix + '\'' +
", usePercentages=" + usePercentages +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MaterialType that = (MaterialType) o;
return Objects.equals(materialTypeID, that.materialTypeID) &&
Objects.equals(materialTypeName, that.materialTypeName) &&
Objects.equals(prefix, that.prefix) &&
Objects.equals(usePercentages, that.usePercentages);
}
}

View File

@ -0,0 +1,107 @@
package fyloz.trial.ColorRecipesExplorer.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name = "mixes")
public class Mix extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Basic
private Integer mixID = 0;
@NotNull
@ManyToOne
private Recipe recipe;
@NotNull
@ManyToOne
private MixType mixType;
@OneToMany(mappedBy = "mix")
private List<MixQuantity> mixQuantities;
// Casier
private String location;
public Mix() {
}
public Mix(@NotNull Recipe recipe, @NotNull MixType mixType, List<MixQuantity> mixQuantities) {
this.recipe = recipe;
this.mixType = mixType;
this.mixQuantities = mixQuantities;
}
public Recipe getRecipe() {
return recipe;
}
public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
public Integer getMixID() {
return mixID;
}
public void setMixID(Integer mixID) {
this.mixID = mixID;
}
public MixType getMixType() {
return mixType;
}
public void setMixType(MixType mixType) {
this.mixType = mixType;
}
public List<MixQuantity> getMixQuantities() {
return mixQuantities;
}
public void setMixQuantities(List<MixQuantity> mixQuantities) {
this.mixQuantities = mixQuantities;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
@Override
public Integer getID() {
return mixID;
}
@Override
public String toString() {
return "Mix{" +
"mixID=" + mixID +
// ", recipe=" + recipe +
", mixType=" + mixType +
// ", mixQuantities=" + mixQuantities +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Mix mix = (Mix) o;
return mixID.equals(mix.mixID) &&
Objects.equals(recipe, mix.recipe) &&
Objects.equals(mixType, mix.mixType) &&
Objects.equals(mixQuantities, mix.mixQuantities);
}
}

View File

@ -0,0 +1,96 @@
package fyloz.trial.ColorRecipesExplorer.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "mixQuantities")
public class MixQuantity extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
@Basic
private Integer mixQuantityID = 0;
@JsonIgnore
@ManyToOne
private Mix mix;
@ManyToOne
private Material material;
@NotNull
private Float quantity;
public MixQuantity() {
}
public MixQuantity(Mix mix, Material material, @NotNull Float quantity) {
this.mix = mix;
this.material = material;
this.quantity = quantity;
}
public Integer getMixQuantityID() {
return mixQuantityID;
}
public void setMixQuantityID(Integer mixQuantityID) {
this.mixQuantityID = mixQuantityID;
}
public Mix getMix() {
return mix;
}
public void setMix(Mix mix) {
this.mix = mix;
}
public Material getMaterial() {
return material;
}
public void setMaterial(Material material) {
this.material = material;
}
public Float getQuantity() {
return quantity;
}
public void setQuantity(Float quantity) {
this.quantity = quantity;
}
@Override
public Integer getID() {
return mixQuantityID;
}
@Override
public String toString() {
return "MixQuantity{" +
"mixQuantityID=" + mixQuantityID +
", mix=" + mix +
", material=" + material +
", quantity=" + quantity +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MixQuantity that = (MixQuantity) o;
return mixQuantityID.equals(that.mixQuantityID) &&
quantity == that.quantity &&
Objects.equals(mix, that.mix) &&
Objects.equals(material, that.material);
}
}

View File

@ -0,0 +1,79 @@
package fyloz.trial.ColorRecipesExplorer.model;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "mixTypes")
public class MixType extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private int typeID;
@NotNull
@Column(unique = true)
private String typeName;
@NotNull
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "materialid")
private Material material;
public MixType() {
}
public MixType(@NotNull String typeName, @NotNull Material material) {
this.typeName = typeName;
this.material = material;
}
public int getTypeID() {
return typeID;
}
public void setTypeID(int typeID) {
this.typeID = typeID;
}
public String getTypeName() {
return typeName;
}
public void setTypeName(String typeName) {
this.typeName = typeName;
}
public Material getMaterial() {
return material;
}
public void setMaterial(Material material) {
this.material = material;
}
@Override
public Integer getID() {
return typeID;
}
@Override
public String toString() {
return "MixType{" +
"typeID=" + typeID +
", typeName='" + typeName + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MixType mixType = (MixType) o;
return typeID == mixType.typeID &&
Objects.equals(typeName, mixType.typeName);
}
}

View File

@ -0,0 +1,176 @@
package fyloz.trial.ColorRecipesExplorer.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.validator.constraints.Length;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;
@Entity
@Table(name = "recipes")
public class Recipe extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer recipeID = 0;
@Length(min = 2)
@NotNull
private String recipeCode;
@ManyToOne
@NotNull
private Company company;
@NotNull
private String recipeDescription;
@NotNull
private int sample;
private String approbationDate;
private String remark;
private String note;
@JsonIgnore
@OneToMany(mappedBy = "recipe")
private List<Mix> recipeMixes;
@OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL)
private List<RecipeStep> recipeSteps;
public Recipe() {
}
public Recipe(@Length(min = 2) String recipeCode, @NotNull Company company, String recipeDescription, @NotNull int sample, @NotNull String approbationDate, String remark, String note) {
this.recipeCode = recipeCode;
this.company = company;
this.recipeDescription = recipeDescription;
this.sample = sample;
this.approbationDate = approbationDate;
this.remark = remark;
this.note = note;
}
public Integer getRecipeID() {
return recipeID;
}
public void setRecipeID(Integer recipeID) {
this.recipeID = recipeID;
}
public String getRecipeCode() {
return recipeCode;
}
public void setRecipeCode(String recipeCode) {
this.recipeCode = recipeCode;
}
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
public String getRecipeDescription() {
return recipeDescription;
}
public void setRecipeDescription(String recipeDescription) {
this.recipeDescription = recipeDescription;
}
public int getSample() {
return sample;
}
public void setSample(int sample) {
this.sample = sample;
}
public String getApprobationDate() {
return approbationDate;
}
public void setApprobationDate(String approbationDate) {
this.approbationDate = approbationDate;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public List<Mix> getRecipeMixes() {
return recipeMixes;
}
public void setRecipeMixes(List<Mix> recipeMixes) {
this.recipeMixes = recipeMixes;
}
public List<RecipeStep> getRecipeSteps() {
return recipeSteps;
}
public void setRecipeSteps(List<RecipeStep> recipeSteps) {
this.recipeSteps = recipeSteps;
}
@Override
public Integer getID() {
return recipeID;
}
@Override
public String toString() {
return "Recipe{" +
"recipeID=" + recipeID +
", recipeCode=" + recipeCode +
", company=" + company +
", recipeDescription='" + recipeDescription +
", sample=" + sample +
", approbationDate=" + approbationDate +
", remark='" + remark +
", note='" + note +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Recipe recipe = (Recipe) o;
return recipeID.equals(recipe.recipeID) &&
recipeCode.equals(recipe.recipeCode) &&
sample == recipe.sample &&
Objects.equals(company, recipe.company) &&
Objects.equals(recipeDescription, recipe.recipeDescription) &&
Objects.equals(approbationDate, recipe.approbationDate) &&
Objects.equals(remark, recipe.remark) &&
Objects.equals(note, recipe.note) &&
Objects.equals(recipeMixes, recipe.recipeMixes) &&
Objects.equals(recipeSteps, recipe.recipeSteps);
}
}

View File

@ -0,0 +1,80 @@
package fyloz.trial.ColorRecipesExplorer.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "steps")
public class RecipeStep extends BeanModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private int stepID;
@ManyToOne
@JsonIgnore
private Recipe recipe;
@NotNull
private String stepMessage;
public RecipeStep() {
}
public RecipeStep(Recipe recipe, @NotNull String stepMessage) {
this.recipe = recipe;
this.stepMessage = stepMessage;
}
public int getStepID() {
return stepID;
}
public void setStepID(int stepID) {
this.stepID = stepID;
}
public String getStepMessage() {
return stepMessage;
}
public void setStepMessage(String stepMessage) {
this.stepMessage = stepMessage;
}
public Recipe getRecipe() {
return recipe;
}
public void setRecipe(Recipe recipe) {
this.recipe = recipe;
}
@Override
public Integer getID() {
return stepID;
}
@Override
public String toString() {
return "RecipeStep{" +
"stepID=" + stepID +
", stepMessage='" + stepMessage + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RecipeStep recipeStep = (RecipeStep) o;
return stepID == recipeStep.stepID &&
Objects.equals(stepMessage, recipeStep.stepMessage) &&
Objects.equals(recipe, recipeStep.recipe);
}
}

View File

@ -0,0 +1,12 @@
package fyloz.trial.ColorRecipesExplorer.model.form;
import org.springframework.web.multipart.MultipartFile;
public class MaterialForm {
private String materialCode;
private int inventoryQuantity;
private MultipartFile file;
}

View File

@ -0,0 +1,37 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.dao.CompanyDao;
import fyloz.trial.ColorRecipesExplorer.dao.RecipeDao;
import fyloz.trial.ColorRecipesExplorer.model.Company;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CompanyService extends GenericService<Company> {
private RecipeDao recipeDao; // Utilise le Dao pour éviter une erreur au démarrage, citant une récursion (CompanyService -> RecipeService -> CompanyService -> ...)
@Autowired
public CompanyService(CompanyDao companyDao, RecipeDao recipeDao) {
super(companyDao);
this.recipeDao = recipeDao;
}
/**
* Vérifie si une bannière est liée à une ou plusieurs recettes en faisant une requête dans la base de données,
* pour retourner s'il y a des résultats ou non.
*
* @return Si la bannière est liée à une ou plusieurs recettes.
*/
public boolean isLinkedToRecipes(Company company) {
return !recipeDao.findAllByCompany(company).isEmpty();
}
public boolean deleteIfNotLinked(Company company) {
if (!isLinkedToRecipes(company)) {
return super.delete(company);
}
return false;
}
}

View File

@ -0,0 +1,119 @@
package fyloz.trial.ColorRecipesExplorer.services;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import fyloz.trial.ColorRecipesExplorer.ColorRecipesExplorerApplication;
import fyloz.trial.ColorRecipesExplorer.model.BeanModel;
import org.slf4j.Logger;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public abstract class GenericService<T extends BeanModel> implements IGenericService<T> {
protected Logger logger = ColorRecipesExplorerApplication.logger;
protected JpaRepository<T, Integer> dao;
public GenericService(JpaRepository<T, Integer> dao) {
this.dao = dao;
}
@Override
public T getByID(int id) {
return dao.findById(id).get();
}
@Override
public List<T> getAll() {
return dao.findAll();
}
@Override
public T save(T entity) {
if (isValidForCreation(entity)) {
return dao.save(entity);
}
return null;
}
@Override
public boolean saveAll(List<T> entities) {
for (T e : entities) {
if (save(e) == null) {
return false;
}
}
return true;
}
@Override
public T update(T entity) {
if (exists(entity)) {
return dao.save(entity);
}
return null;
}
@Override
public boolean delete(T entity) {
if (exists(entity)) {
dao.delete(entity);
return true;
}
return false;
}
@Override
public boolean deleteAll(List<T> entities) {
for (T e : entities) {
if (!delete(e)) {
return false;
}
}
return true;
}
/**
* Vérifie si une entité est valide pour la création dans la base de données.
*
* @param entity L'entité à vérifier.
* @return Si l'entité est valide pour la création.
*/
public boolean isValidForCreation(T entity) {
return entity != null && !exists(entity);
}
/**
* Vérifie si une entité existe dans la base de données.
*
* @param entity L'entité à vérifier
* @return Si l'entité existe.
*/
@Override
public boolean exists(T entity) {
return dao.findById(entity.getID()).isPresent();
}
/**
* Transforme un objet en Json.
*
* @param obj L'objet à transformer
* @return L'objet sous forme de Json.
*/
public String asJson(Object obj) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
logger.error("Une erreur est survenue lors de la transformation d'un objet en Json", e);
return null;
}
}
}

View File

@ -0,0 +1,78 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.model.BeanModel;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface IGenericService<T extends BeanModel> {
/**
* Récupère toutes les entités du même type dans la base de données.
*
* @return Une liste de toutes les entités du même type.
*/
List<T> getAll();
/**
* Récupère l'entité correspondant à l'identifiant spécifié dans la base de données.
*
* @param id L'identifiant de l'entité
* @return L'entité correspondant à l'identifiant.
*/
T getByID(int id);
/**
* Sauvegarde une entité dans la base de données.
* <p>
* La sauvegarde sera exécutée si l'entité est valide pour la création.
*
* @param entity L'entité à sauvegarder
* @return Si la sauvegarde a fonctionné.
*/
T save(T entity);
/**
* Sauvegarde des entités dans la base de données.
* <p>
* Les sauvegardes seront exécutée si toutes les entités sont valides pour la création.
*
* @param entities La liste d'entités à sauvegarder
* @return Si la sauvegarde a fonctionné.
*/
@Transactional
boolean saveAll(List<T> entities);
/**
* Met à jour une entité dans la base de données.
* <p>
* La mise à jour sera exécutée si l'entité existe dans la BDD.
*
* @param entity L'entité à mettre à jour
* @return Si la mise à jour a fonctionné.
*/
T update(T entity);
/**
* Supprime une entité dans la base de données.
* <p>
* La suppression sera exécutée si l'entité existe dans la BDD.
*
* @param entity L'entité à supprimer
* @return Si la suppression a fonctionné.
*/
boolean delete(T entity);
/**
* Supprime des entités dans la base de données.
* <p>
* Les suppressions seront exécutée si toutes les entités existes dans la BDD.
*
* @param entities La liste d'entités à supprimer
* @return Si la suppression a fonctionné.
*/
@Transactional
boolean deleteAll(List<T> entities);
boolean exists(T entity);
}

View File

@ -0,0 +1,46 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.Mix;
import fyloz.trial.ColorRecipesExplorer.model.MixQuantity;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class InventoryService {
private MaterialService materialService;
public InventoryService(MaterialService materialService) {
this.materialService = materialService;
}
public String checkQuantities(Mix mix, Map<Material, Float> quantities) {
for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) {
if (!material.isMixType()) {
float quantity = quantities.get(material);
if (quantity > material.getInventoryQuantity()) {
return String.format("%s-%s", mix.getMixID(), material.getMaterialID());
}
}
}
return null;
}
public boolean useMix(Mix mix, Map<Material, Float> quantities) {
for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) {
if (!material.isMixType()) {
float quantity = quantities.get(material);
material.setInventoryQuantity(material.getInventoryQuantity() - quantity);
if (materialService.update(material) == null) return false;
}
}
return true;
}
}

View File

@ -0,0 +1,98 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.core.io.FileHandler;
import fyloz.trial.ColorRecipesExplorer.dao.MaterialDao;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
@Service
public class MaterialService extends GenericService<Material> {
private MixQuantityService mixQuantityService;
@Autowired
public MaterialService(MaterialDao materialDao, MixQuantityService mixQuantityService) {
super(materialDao);
this.mixQuantityService = mixQuantityService;
}
/**
* Vérifie si un produit est lié à un ou plusieurs mélanges.
*
* @param material Le produit à vérifier.
* @return Si le produit est lié à d'autres mélanges.
*/
public boolean isLinkedToMixes(Material material) {
return !mixQuantityService.getByMaterial(material).isEmpty();
}
public boolean deleteIfNotLinked(Material material) {
if (!isLinkedToMixes(material)) {
return super.delete(material);
}
return false;
}
/**
* Crée le fichier SIMDUT sur le disque et y transfert le contenu du MultipartFile passé en paramètre.
* <p>
* Cette méthode retournera true si le simdut est null ou si le transfert du fichier vers le disque a fonctionné.
* Cette méthode retournera false si la création du fichier échoue ou si une erreur survient lors du transfert.
*
* @param simdut Le contenu du fichier SIMDUT
* @param material Le produit du SIMDUT
* @return Si le fichier SIMDUT à bien été créé ou non.
*/
public boolean addSimdut(MultipartFile simdut, Material material) {
if (simdut.getSize() <= 0) return true;
FileHandler fileHandler = getFilesServiceForMaterial(material);
if (!fileHandler.createFile()) {
return false;
}
try {
simdut.transferTo(new File(fileHandler.getFile().getAbsolutePath()));
return true;
} catch (IOException e) {
logger.error("Erreur lors du transfert d'un fichier SIMDUT vers le disque", e);
return false;
}
}
/**
* Supprime le fichier SIMDUT pour un produit.
* <p>
* Cette méthode retournera true si le fichier SIMDUT du produit n'existe pas ou si la suppression est faîtes.
*
* @param material Le produit dont on veut supprimer le fichier SIMDUT
* @return Si la suppression du fichier SIMDUT s'est effectuée.
*/
public boolean removeSimdut(Material material) {
FileHandler fileHandler = getFilesServiceForMaterial(material);
if (fileHandler.getFile().exists()) {
return fileHandler.deleteFile();
}
return true;
}
/**
* Crée un FileHandler pour le produit passé en paramètre.
*
* @param material Le produit dont on veut créer un FileHandler
* @return Le FileHandler correspondant au produit.
*/
private FileHandler getFilesServiceForMaterial(Material material) {
String filename = String.format("%s_%s", material.getMaterialID(), material.getMaterialCode());
return new FileHandler(filename, FileHandler.FileContext.SIMDUT, FileHandler.FileExtension.PDF);
}
}

View File

@ -0,0 +1,27 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.dao.MaterialTypeDao;
import fyloz.trial.ColorRecipesExplorer.model.MaterialType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MaterialTypeService extends GenericService<MaterialType> {
private MaterialTypeDao materialTypeDao;
@Autowired
public MaterialTypeService(MaterialTypeDao materialTypeDao) {
super(materialTypeDao);
this.materialTypeDao = materialTypeDao;
}
@Override
public boolean isValidForCreation(MaterialType entity) {
return entity != null && !existsByName(entity);
}
public boolean existsByName(MaterialType entity) {
return materialTypeDao.findByMaterialTypeName(entity.getMaterialTypeName()) != null;
}
}

View File

@ -0,0 +1,31 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.dao.MixQuantityDao;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.MixQuantity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MixQuantityService extends GenericService<MixQuantity> {
private MixQuantityDao mixQuantityDao;
@Autowired
public MixQuantityService(MixQuantityDao mixQuantityDao) {
super(mixQuantityDao);
this.mixQuantityDao = mixQuantityDao;
}
/**
* Récupère la liste des MixQuantity liées à un produit.
*
* @param material Le produit
* @return La page à afficher.
*/
public List<MixQuantity> getByMaterial(Material material) {
return mixQuantityDao.findAllByMaterial(material);
}
}

View File

@ -0,0 +1,113 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.dao.MixDao;
import fyloz.trial.ColorRecipesExplorer.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.ERROR_SAVING;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.MATERIAL_NOT_FOUND;
@Service
public class MixService extends GenericService<Mix> {
private MaterialService materialService;
private MixQuantityService mixQuantityService;
@Autowired
public MixService(MixDao mixDao, MaterialService materialService, MixQuantityService mixQuantityService) {
super(mixDao);
this.materialService = materialService;
this.mixQuantityService = mixQuantityService;
}
/**
* Crée un mélange, ainsi que tous ses sous-éléments.
* <p>
* materials & quantities doivent avoir le même ordre.
*
* @param materials La liste des identifiants des matériaux
* @param quantities La liste des quantités de matériaux
* @param recipe La recette associée au mélange
* @param mixType Le type du mélange
* @return Le message d'erreur, s'il y a lieu
*/
@Transactional
public String create(List<Integer> materials, List<Float> quantities, Recipe recipe, MixType mixType) {
// Crée le mélange en premier pour avoir accès à son ID pour les autres éléments
Mix mix = new Mix(recipe, mixType, null);
if ((mix = save(mix)) != null) {
List<MixQuantity> mixQuantities = createMixQuantities(mix, materials, quantities);
if (mixQuantities == null) return MATERIAL_NOT_FOUND;
if (mixQuantityService.saveAll(mixQuantities)) return null;
}
return ERROR_SAVING;
}
/**
* Modifie un mélange, ainsi que ses sous-éléments.
* <p>
* materials & quantities doivent avoir le même ordre.
*
* @param mix Le mélange à modifier
* @param materials La liste des identifiants des matériaux
* @param quantities La liste des quantités de matériaux
* @return Si le mélange a été modifier.
*/
public boolean edit(Mix mix, List<Integer> materials, List<Float> quantities) {
List<MixQuantity> mixQuantities = createMixQuantities(mix, materials, quantities);
if (mixQuantities == null) return false;
// Supprime les anciens MixQuantity pour éviter les doublons et les entrées inutiles dans la base de données
if (!mixQuantityService.deleteAll(mix.getMixQuantities())) {
return false;
}
return mixQuantityService.saveAll(mixQuantities);
}
public boolean deleteMix(Mix mix) {
if (mixQuantityService.deleteAll(mix.getMixQuantities())) {
return super.delete(mix);
}
return false;
}
/**
* Crée une liste de MixQuantity, pour la création d'un mélange
*
* @param mix Le mélange associé aux MixQuantity
* @param materials Les matériaux
* @param quantities Les quantités
* @return La liste des MixQuantity créés.
*/
private List<MixQuantity> createMixQuantities(Mix mix, List<Integer> materials, List<Float> quantities) {
List<MixQuantity> mixQuantities = new ArrayList<>();
int i = 0;
for (int materialID : materials) {
Material material = materialService.getByID(materialID);
if (material == null) {
return null;
}
float associatedQuantity = quantities.get(i);
MixQuantity quantity = new MixQuantity(mix, material, associatedQuantity);
mixQuantities.add(quantity);
i++;
}
return mixQuantities;
}
}

View File

@ -0,0 +1,39 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.dao.MixTypeDao;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.MixType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class MixTypeService extends GenericService<MixType> {
private MixTypeDao mixTypeDao;
@Autowired
public MixTypeService(MixTypeDao mixTypeDao) {
super(mixTypeDao);
this.mixTypeDao = mixTypeDao;
}
public MixType getByName(String name) {
return mixTypeDao.findByTypeName(name);
}
public MixType getByMaterial(Material material) {
return mixTypeDao.findByMaterial(material);
}
public MixType createByName(String name) {
Material mixTypeMaterial = new Material(name, 0, true, null);
MixType mixType = new MixType(name, mixTypeMaterial);
mixType = save(mixType);
if (mixType == null) {
return getByName(name);
}
return mixType;
}
}

View File

@ -0,0 +1,145 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.core.io.FileHandler;
import fyloz.trial.ColorRecipesExplorer.dao.RecipeDao;
import fyloz.trial.ColorRecipesExplorer.model.*;
import fyloz.trial.ColorRecipesExplorer.web.StringBank;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import java.io.File;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class RecipeService extends GenericService<Recipe> {
private RecipeDao recipeDao;
private CompanyService companyService;
private StepService stepService;
private MixService mixService;
@Autowired
public RecipeService(RecipeDao recipeDao, CompanyService companyService, StepService stepService, MixService mixService) {
super(recipeDao);
this.recipeDao = recipeDao;
this.companyService = companyService;
this.stepService = stepService;
this.mixService = mixService;
}
/**
* Récupère la liste des recettes associées à une compagnie.
*
* @param company La compagnie.
* @return La liste des recettes associées à cette compagnie.
*/
public List<Recipe> getByCompany(Company company) {
return recipeDao.findAllByCompany(company);
}
/**
* Crée et assigne des étapes à une recette, en se basant sur l'entrée de utilisateur.
*
* @param recipe La recette à assigner les étapes
* @param input L'entrée de l'utilisateur, contenant les étapes (key = "step_*")
* @return La recette avec les étapes.
*/
public Recipe createAndSetSteps(Recipe recipe, MultiValueMap<String, Object> input) {
List<RecipeStep> steps = new ArrayList<>();
for (String key : input.keySet()) {
Object field = input.get(key);
if (key.startsWith("step_") && field instanceof LinkedList) {
steps.add(new RecipeStep(recipe, ((LinkedList) field).get(0).toString()));
}
}
List<RecipeStep> recipeSteps = recipe.getRecipeSteps();
if (recipeSteps != null) {
stepService.deleteAll(recipeSteps);
}
recipe.setRecipeSteps(steps);
save(recipe);
return recipe;
}
/**
* Supprime complètement une recette ainsi que les éléments qui y sont attachés de la base de données.
*
* @param recipe La recette à supprimer
*/
public void deleteRecipe(Recipe recipe) {
List<Mix> mixes = recipe.getRecipeMixes();
mixService.deleteAll(mixes);
stepService.deleteAll(recipe.getRecipeSteps());
delete(recipe);
FileHandler fileHandler;
for (String image : getImageFiles(recipe)) {
fileHandler = new FileHandler(image.replace(".jpeg", ""), FileHandler.FileContext.IMAGE, FileHandler.FileExtension.JPEG);
fileHandler.deleteFile();
}
}
/**
* Récupère une liste triée des mélanges pour une recette.
*
* @param recipe La recette dont on veut récupérer les mélanges
* @return Une liste triée des mélanges.
*/
public List<Mix> getSortedMixes(Recipe recipe) {
List<Mix> mixes = recipe.getRecipeMixes();
mixes.sort(Comparator.comparing(Mix::getMixID));
return mixes;
}
/**
* Récupère toutes les recettes dans la base de données et les classes par compagnie.
*
* @return Un Map contenant les recettes classées par compagnie.
*/
public Map<Company, List<Recipe>> getRecipesByCompany() {
List<Company> companies = companyService.getAll();
List<Recipe> recipes = getAll();
Map<Company, List<Recipe>> mappedRecipes = new HashMap<>();
for (Company company : companies) {
mappedRecipes.put(company, recipes.stream().filter(r -> r.getCompany().equals(company)).collect(Collectors.toList()));
}
return mappedRecipes;
}
/**
* Récupère le nom des images liées à une recette.
*
* @param recipe La recette dont on veut récupérer les images
* @return Une liste contenant le nom des images liées à la recette.
*/
public List<String> getImageFiles(Recipe recipe) {
int recipeID = recipe.getRecipeID();
String recipeCode = recipe.getRecipeCode();
String fileName = String.format("%s_%s", recipeID, recipeCode);
File imageLocation = new File(StringBank.IMAGES_LOCATION);
File[] result = imageLocation.listFiles((d, n) -> n.startsWith(fileName) && n.endsWith("jpeg"));
if (result == null) return null;
return Arrays.stream(result).map(File::getName).collect(Collectors.toList());
}
/**
* Récupère les types de mélanges associés à la recette.
*
* @param recipe La recette dont on veut récupérer les types de mélange
* @return Une liste contenant les types de mélanges.
*/
public List<MixType> getAssociatedMixesTypes(Recipe recipe) {
return recipe.getRecipeMixes().stream().map(Mix::getMixType).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,15 @@
package fyloz.trial.ColorRecipesExplorer.services;
import fyloz.trial.ColorRecipesExplorer.dao.StepDao;
import fyloz.trial.ColorRecipesExplorer.model.RecipeStep;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class StepService extends GenericService<RecipeStep> {
@Autowired
public StepService(StepDao stepDao) {
super(stepDao);
}
}

View File

@ -0,0 +1,57 @@
package fyloz.trial.ColorRecipesExplorer.web;
public class PagesPaths {
// Autres
public static final String INDEX = "index";
public static final String SIMDUT_FILES = "simdut/{materialID}";
public static final String PASSWORD_VALIDATION = "password/valid";
public static final String TOUCHUP = "touchup";
// Images
public static final String IMAGES_FILES = "images/{image}";
public static final String ADD_IMAGE = "images/add";
public static final String ADD_IMAGE_SPECIFIC = "images/add/{recipeID}";
public static final String DELETE_IMAGE = "images/deleteIfNotLinked";
// Inventaire
public static final String INVENTORY = "inventory";
public static final String USE_INVENTORY = "inventory/use";
// Recettes
public static final String EXPLORER_RECIPE = "recipe/explore";
public static final String EXPLORER_RECIPE_SPECIFIC = "recipe/explore/{recipeID}";
public static final String CREATOR_RECIPE = "recipe/creator";
public static final String CREATOR_RECIPE_SUCCESS = "recipe/created";
public static final String EDITOR_RECIPE = "recipe/editor";
public static final String EDITOR_RECIPE_SPECIFIC = "recipe/editor/{recipeID}";
public static final String EDITOR_RECIPE_EDITOR = "recipe/edit";
public static final String REMOVER_RECIPE = "recipe/remover";
public static final String REMOVER_RECIPE_SPECIFIC = "recipe/remover/{recipeID}";
// Compagnies
public static final String CREATOR_COMPANY = "company/creator";
public static final String CREATOR_COMPANY_SUCCESS = "company/created";
public static final String REMOVER_COMPANY = "company/remover";
public static final String REMOVER_COMPANY_SPECIFIC = "company/remover/{companyID}";
// Matériaux
public static final String CREATOR_MATERIAL = "material/creator";
public static final String EDIT_MATERIAL_SIMDUT = "material/simdut";
public static final String EDIT_MATERIAL_SIMDUT_SPECIFIC = "material/simdut/{materialID}";
public static final String CREATOR_MATERIAL_SUCCESS = "material/created";
public static final String REMOVER_MATERIAL = "material/remover";
public static final String REMOVER_MATERIAL_SPECIFIC = "material/remover/{materialID}";
public static final String EDITOR_MATERIAL = "material/editor";
public static final String EDITOR_MATERIAL_SPECIFIC = "material/editor/{materialID}";
public static final String EDITOR_MATERIAL_EDITOR = "material/edit";
// Types de matériaux
public static final String CREATOR_MATERIAL_TYPE = "materialType/creator";
// Mélanges
public static final String CREATOR_MIX = "mix/creator";
public static final String CREATOR_MIX_SPECIFIC = "mix/creator/{recipeID}";
public static final String EDITOR_MIX = "mix/editor";
public static final String EDITOR_MIX_SPECIFIC = "mix/editor/{mixID}";
public static final String REMOVER_MIX_SPECIFIC = "mix/remover/{mixID}";
}

View File

@ -0,0 +1,54 @@
package fyloz.trial.ColorRecipesExplorer.web;
public class StringBank {
public static final String SUCCESS_USING_MATERIALS = "Les quantités de chaque produits utilisés ont été déduites de l'inventaire";
public static final String SUCCESS_SAVING_RECIPE_INFORMATIONS = "Les informations de la recette ont été sauvegardées";
public static final String ERROR_SAVING = "Une erreur est survenue lors de l'enregistrement";
public static final String ERROR_SAVING_IMAGE = "Une erreur est survenue lors de l'enregistrement de l'image";
public static final String ERROR_SAVING_SIMDUT = "Une erreur est survenue lors de l'enregistrement du fichier SIMDUT";
public static final String AUTH_ERROR = "Votre mot de passe n'est pas valide";
// À formatter
public static final String RECIPE_NOT_FOUND = "Aucune recette ayant l'identifiant '%s' n'a été trouvée";
public static final String MIX_NOT_FOUND = "Aucun mélange ayant l'identifiant '%s' n'a été trouvé";
public static final String MATERIAL_NOT_FOUND = "Aucun produit ayant l'identifiant '%s' n'a été trouvé";
public static final String MATERIAL_ALREADY_EXIST = "Il y a déjà un produit s'appellant '%s'";
public static final String MATERIAL_TYPE_ALREADY_EXIST = "Il y a déjà un type de produit s'appellant '%s'";
public static final String COMPANY_NOT_FOUND = "Aucune bannière ayant l'identifiant '%s' n'a été trouvée";
public static final String COMPANY_ALREADY_EXIST = "Il y a déjà une bannière s'appellant '%s'";
public static final String MATERIAL_LINKED = "Le produit '%s' est lié à une ou plusieurs recettes, veuillez les supprimer d'abord";
public static final String COMPANY_LINKED = "La bannière '%s' est liée à une ou plusieurs recettes, veuillez les supprimer d'abord";
public static final String MIX_NOT_ASSOCIATED_WITH_RECIPE = "Le mélange ayant l'identifiant '%s' n'est pas associé à la recette ayant l'identifiant '%s'";
public static final String NOT_ENOUGH_MATERIAL = "Il n'y a pas assez de '%s' en inventaire pour cette recette";
public static final String MIX_TYPE_ALREADY_USED = "Cette recette contient déjà un mélange du type '%s'";
// Types de réponse
public static final String RESPONSE_ERROR = "error";
public static final String RESPONSE_SUCCESS = "success";
public static final String RESPONSE_REASON = "reason";
// Types d'attributs communs
public static final String MATERIALS = "materials";
public static final String MATERIAL = "material";
public static final String MATERIAL_ID = "materialID";
public static final String MATERIAL_CODE = "materialCode";
public static final String MATERIAL_TYPES = "materialTypes";
public static final String RECIPES = "recipes";
public static final String RECIPE = "recipe";
public static final String RECIPE_ID = "recipeID";
public static final String RECIPE_CODE = "recipeCode";
public static final String MIXES = "mixes";
public static final String MIX = "mix";
public static final String MIX_ID = "mixID";
public static final String MIX_TYPE = "mixType";
public static final String MIX_TYPES = "mixTypes";
public static final String COMPANIES = "companies";
public static final String COMPANY = "company";
public static final String COMPANY_ID = "companyID";
public static final String UPLOAD_LOCATION = System.getProperty("upload.location");
public static final String IMAGES_LOCATION = UPLOAD_LOCATION + "/images";
public static final String SIMDUTS_LOCATION = UPLOAD_LOCATION + "/simdut/";
}

View File

@ -0,0 +1,147 @@
package fyloz.trial.ColorRecipesExplorer.web.controller;
import fyloz.trial.ColorRecipesExplorer.ColorRecipesExplorerApplication;
import fyloz.trial.ColorRecipesExplorer.core.io.ImageHandler;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.*;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class ImageFilesController {
private RecipeService recipeService;
@Autowired
public ImageFilesController(RecipeService recipeService) {
this.recipeService = recipeService;
}
/**
* Récupère l'image voulue sous forme JPEG.
*
* @param image Le nom de l'image
* @return L'image en JPEG.
*/
@GetMapping(IMAGES_FILES)
public ResponseEntity<byte[]> getImage(@PathVariable String image) {
ImageHandler imageHandler = new ImageHandler(image, recipeService);
byte[] content = imageHandler.readFile();
if (content == null) return ResponseEntity.notFound().build();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_JPEG);
return new ResponseEntity<>(content, headers, HttpStatus.OK);
}
/**
* Affiche la page pour ajouter une image pour une recette.
* Cette méthode requiert l'identifiant de la recette dans l'URL.
* <p>
* Modèle de la page:
* - recipeID: Contient l'identifiant de la recette
*
* @param model Le Model injecté par Thymeleaf
* @param recipeID L'identifiant de la recette
* @return La page à afficher.
*/
@GetMapping(ADD_IMAGE_SPECIFIC)
public String showPage(Model model, @PathVariable int recipeID) {
model.addAttribute(RECIPE_ID, recipeID);
return ADD_IMAGE;
}
/**
* Permet à l'utilisateur d'uploader une image.
* <p>
* L'upload échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action.
* - La recette n'existe pas
* - Une erreur est survenue lors de la création du fichier de l'image
* - Une erreur est survenue lors du transfert de l'image vers le fichier
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipeCode: Contient la couleur de la recette
* - recipeID: Contient l'identifiant de la recette
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param recipeID L'identifiant de la recette
* @param image L'image uploadée
* @return La page à afficher.
*/
@PostMapping(ADD_IMAGE)
public String addImage(Model model, int recipeID, MultipartFile image) {
Recipe recipe = recipeService.getByID(recipeID);
if (recipe == null) {
model.addAttribute(RESPONSE_ERROR, RECIPE_NOT_FOUND);
return showPage(model, recipeID);
}
ImageHandler imageHandler = new ImageHandler(recipe, recipeService);
if (!imageHandler.createFile()) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING_IMAGE);
return showPage(model, recipeID);
}
model.addAttribute(RECIPE_CODE, recipe.getRecipeCode());
model.addAttribute(RECIPE_ID, recipe.getRecipeID());
try {
// Si je n'utilise pas le path, il cherche un fichier dans les tmp ?
image.transferTo(new File(imageHandler.getFile().getAbsolutePath()));
return "redirect:/" + EDITOR_RECIPE_SPECIFIC.replace("{recipeID}", String.valueOf(recipeID));
} catch (IOException e) {
ColorRecipesExplorerApplication.logger.error("Erreur inconnue lors de la création d'une image", e);
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING_IMAGE);
return showPage(model, recipeID);
}
}
/**
* Permet à l'utilisateur de supprimer une image.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Une erreur est survenue lors de la suppression du fichier
* <p>
* Réponse de la méthode:
* - error: Contient le message d'erreur, s'il y a lieu
* <p>
* REQUIERT UNE AUTORISATION
*
* @param form Le formulaire contenant les informations sur l'opération
* @return La réponse sous forme de Map (-> Json)
*/
@PostMapping(value = DELETE_IMAGE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, String> deleteImage(@RequestBody Map<String, String> form) {
Map<String, String> response = new HashMap<>();
ImageHandler imageHandler = new ImageHandler(form.get("image"), recipeService);
if (!imageHandler.deleteFile()) {
response.put(RESPONSE_ERROR, ERROR_SAVING_IMAGE);
}
return response;
}
}

View File

@ -0,0 +1,68 @@
package fyloz.trial.ColorRecipesExplorer.web.controller;
import fyloz.trial.ColorRecipesExplorer.PasswordValidator;
import fyloz.trial.ColorRecipesExplorer.model.Company;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.CompanyService;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.INDEX;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.PASSWORD_VALIDATION;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.RECIPES;
@Controller
public class IndexController {
private CompanyService companyService;
private RecipeService recipeService;
@Autowired
public IndexController(CompanyService companyService, RecipeService recipeService) {
this.companyService = companyService;
this.recipeService = recipeService;
}
/**
* Affiche la page d'acceuil.
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping({INDEX, "/"})
public String showPage(Model model) {
List<Company> companies = companyService.getAll();
Map<Company, List<Recipe>> recipes = new HashMap<>();
for (Company company : companies) {
recipes.put(company, recipeService.getByCompany(company));
}
model.addAttribute(RECIPES, recipes);
return INDEX;
}
/**
* Valide un mot de passe reçu dans une requête HTTP POST, dans le champ "password".
*
* @param data Corps de la requête HTTP
* @return Si le mot de passe est valide.
*/
@PostMapping(value = PASSWORD_VALIDATION, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public boolean validatePassword(@RequestBody Map<String, Object> data) {
return PasswordValidator.isValid((String) data.get("password"));
}
}

View File

@ -0,0 +1,140 @@
package fyloz.trial.ColorRecipesExplorer.web.controller;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.Mix;
import fyloz.trial.ColorRecipesExplorer.model.MixQuantity;
import fyloz.trial.ColorRecipesExplorer.services.InventoryService;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import fyloz.trial.ColorRecipesExplorer.services.MaterialTypeService;
import fyloz.trial.ColorRecipesExplorer.services.MixService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.INVENTORY;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.USE_INVENTORY;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class InventoryController {
private InventoryService inventoryService;
private MaterialService materialService;
private MixService mixService;
private MaterialTypeService materialTypeService;
@Autowired
public InventoryController(InventoryService inventoryService, MaterialService materialService, MixService mixService, MaterialTypeService materialTypeService) {
this.inventoryService = inventoryService;
this.materialService = materialService;
this.mixService = mixService;
this.materialTypeService = materialTypeService;
}
/**
* Affiche la page de l'inventaire.
* <p>
* Modèle de la page:
* - materials: Contient la liste de tous les matériaux
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(INVENTORY)
public String getInventory(Model model) {
model.addAttribute(MATERIALS, materialService.getAll().stream().filter(m -> !m.isMixType()).collect(Collectors.toList()));
model.addAttribute(MATERIAL_TYPES, materialTypeService.getAll());
return INVENTORY;
}
/**
* Déduit les quantités utilisées de chaque matériaux présents dans le corps de la requête (JSON) de l'inventaire.
* Le JSON dans le corps de la requête doit prendre cette forme:
* {
* "mixID 1": {
* "materialID 1": "quantité 1",
* "materialID 2": "quantité 2",
* "materialID 3": "quantité 3"
* },
* "mixID 2": {
* "materialID 1": "quantité 1"
* },
* ..
* }
* S'il y a une erreur, l'opération est annulée et aucun changement n'est fait dans l'inventaire.
* <p>
* Réponse de la méthode:
* - error: Contient le message d'erreur, s'il y a lieu
* - reason: Contient la raison de l'erreur (le champ fautif)
* - success: Contient le message à affiche lors du succès de la méthode
*
* @param form Le JSON contenant les quantités à utiliser.
* @return Une réponse sous forme de Map (-> Json).
*/
@PostMapping(value = USE_INVENTORY, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@Transactional
public Map<String, String> consumeMaterials(@RequestBody Map<String, Object> form) {
Map<String, String> response = new HashMap<>();
List<Mix> mixes = new ArrayList<>();
Map<Mix, Map<Material, Float>> quantities = new HashMap<>();
for (String mixIDStr : form.keySet()) {
int mixID = Integer.parseInt(mixIDStr);
Mix mix = mixService.getByID(mixID);
if (mix == null) {
response.put(RESPONSE_ERROR, String.format(MIX_NOT_FOUND, mixID));
return response;
}
mixes.add(mix);
Map<String, String> formMaterials = (Map<String, String>) form.get(mixIDStr);
Map<Material, Float> mixQuantities = new HashMap<>();
for (Material material : mix.getMixQuantities().stream().map(MixQuantity::getMaterial).collect(Collectors.toList())) {
String materialIDAsString = String.valueOf(material.getMaterialID());
if (formMaterials.containsKey(materialIDAsString)) {
mixQuantities.put(material, Float.parseFloat(formMaterials.get(materialIDAsString)));
}
}
quantities.put(mix, mixQuantities);
}
for (Mix mix : mixes) {
String errorCode = inventoryService.checkQuantities(mix, quantities.get(mix));
if (errorCode != null) {
response.put(RESPONSE_ERROR, String.format(NOT_ENOUGH_MATERIAL, materialService.getByID(Integer.parseInt(errorCode.split("-")[1])).getMaterialCode()));
response.put(RESPONSE_REASON, errorCode);
return response;
}
}
for (Mix mix : mixes) {
if (!inventoryService.useMix(mix, quantities.get(mix))) {
response.put(RESPONSE_ERROR, ERROR_SAVING);
return response;
}
}
response.put(RESPONSE_SUCCESS, SUCCESS_USING_MATERIALS);
return response;
}
}

View File

@ -0,0 +1,14 @@
package fyloz.trial.ColorRecipesExplorer.web.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.TOUCHUP;
@Controller
public class OthersController {
@GetMapping(TOUCHUP)
public String getTouchUpPdf() {
return "redirect:/pdf/touchup.pdf";
}
}

View File

@ -0,0 +1,118 @@
package fyloz.trial.ColorRecipesExplorer.web.controller;
import fyloz.trial.ColorRecipesExplorer.model.Mix;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.MixService;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.*;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class RecipeExplorerController {
private RecipeService recipeService;
private MixService mixService;
@Autowired
public RecipeExplorerController(RecipeService recipeService, MixService mixService) {
this.recipeService = recipeService;
this.mixService = mixService;
}
/**
* Affiche la recette.
* Cette méthode requiert l'identifiant de la recette à affiche dans l'URL.
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipe: Contient la recette
* - mixes: Contient les mélanges associées à la recette
* - images: Contient la liste des noms des images associées à la recette
*
* @param model Le Model injecté par Thymeleaf
* @param recipeID L'identifiant de la recette
* @return La page à afficher.
*/
@GetMapping(EXPLORER_RECIPE_SPECIFIC)
public String showRecipe(Model model, @PathVariable int recipeID) {
Recipe recipe = recipeService.getByID(recipeID);
if (recipe == null) {
model.addAttribute(RESPONSE_ERROR, String.format(RECIPE_NOT_FOUND, recipeID));
return INDEX;
}
List<Mix> mixes = recipe.getRecipeMixes();
mixes.sort(Comparator.comparing(Mix::getMixID));
model.addAttribute(RECIPE, recipe);
model.addAttribute(MIXES, mixes);
model.addAttribute("images", recipeService.getImageFiles(recipe));
return EXPLORER_RECIPE;
}
/**
* Sauvegarde les informations de la recette.
* <p>
* Certaines parties de la sauvegarde échouera si:
* - La recette n'existe pas
* - Un des mélanges n'existe pas
* - Un des mélanges n'est pas associé à la recette
* <p>
* Réponse de la méthode:
* - error: Contient le message d'erreur, s'il y a lieu
* - success: Contient le message à afficher lors du succès de la méthode
*
* @param form Le formulaire contenant les informations de la recette
* @return La réponse sous forme de Map (-> Json)
*/
@PostMapping(value = EXPLORER_RECIPE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, String> saveRecipeInformations(@RequestBody Map<String, Object> form) {
int recipeID = Integer.parseInt(form.get("recipeID").toString());
Map<String, String> location = (Map<String, String>) form.get("locations");
String note = form.get("note").toString();
Map<String, String> response = new HashMap<>();
Recipe recipe = recipeService.getByID(recipeID);
if (recipe == null) {
response.put(RESPONSE_ERROR, String.format(RECIPE_NOT_FOUND, recipeID));
} else {
recipe.setNote(note);
recipeService.save(recipe);
}
for (String mixIDStr : location.keySet()) {
int mixID = Integer.parseInt(mixIDStr);
Mix mix = mixService.getByID(mixID);
if (mix == null) {
response.put(RESPONSE_ERROR, String.format(MIX_NOT_FOUND, mixID));
} else if (mix.getRecipe().getRecipeID() != recipeID) {
response.put(RESPONSE_ERROR, String.format(MIX_NOT_ASSOCIATED_WITH_RECIPE, mixID, recipeID));
} else {
mix.setLocation(location.get(mixIDStr));
mixService.update(mix);
}
}
if (response.get(RESPONSE_ERROR) == null) {
response.put(RESPONSE_SUCCESS, SUCCESS_SAVING_RECIPE_INFORMATIONS);
}
return response;
}
}

View File

@ -0,0 +1,53 @@
package fyloz.trial.ColorRecipesExplorer.web.controller;
import fyloz.trial.ColorRecipesExplorer.core.io.FileHandler;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.SIMDUT_FILES;
@Controller
public class SIMDUTFilesController {
private MaterialService materialService;
@Autowired
public SIMDUTFilesController(MaterialService materialService) {
this.materialService = materialService;
}
/**
* Récupère le fichier SIMDUT voulu sous forme PDF.
* Cette méthode requiert l'identifiant du produit dans l'URL.
*
* @param materialID L'identifiant du produit
* @return Le fichier SIMDUT en PDF.
*/
@GetMapping(SIMDUT_FILES)
public ResponseEntity<byte[]> getFile(@PathVariable int materialID) {
Material material = materialService.getByID(materialID);
if (material == null) {
return ResponseEntity.notFound().build();
}
HttpHeaders headers = new HttpHeaders();
FileHandler fileHandler = new FileHandler(String.format("%s_%s", materialID, material.getMaterialCode()), FileHandler.FileContext.SIMDUT, FileHandler.FileExtension.PDF);
if (!fileHandler.isValid()) {
return ResponseEntity.notFound().build();
}
byte[] fileContent = fileHandler.readFile();
headers.setContentType(MediaType.APPLICATION_PDF);
return new ResponseEntity<>(fileContent, headers, HttpStatus.OK);
}
}

View File

@ -0,0 +1,71 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.creators;
import fyloz.trial.ColorRecipesExplorer.model.Company;
import fyloz.trial.ColorRecipesExplorer.services.CompanyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_COMPANY;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_COMPANY_SUCCESS;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class CompanyCreatorController {
private CompanyService companyService;
@Autowired
public CompanyCreatorController(CompanyService companyService) {
this.companyService = companyService;
}
/**
* Affiche la page de création d'une bannière.
*
* @return La page à afficher.
*/
@GetMapping(CREATOR_COMPANY)
public String showCreationPage() {
return CREATOR_COMPANY;
}
/**
* Permet à l'utilisateur de créer un bannière.
* <p>
* La création échouera si:
* - L'utilisateur n'est pas autorisé à effectuer cette action
* - La bannière existe déjà
* - Une erreur est survenue lors de la sauvegarde dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - companyName: Contient le nom de la bannière
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le modèle injecté par Thymeleaf
* @param company La bannière à créer
* @return La page à afficher.
*/
@PostMapping(value = CREATOR_COMPANY, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String createCompany(Model model, @Valid Company company) {
if (companyService.isValidForCreation(company)) {
if ((company = companyService.save(company)) != null) {
model.addAttribute("companyName", company.getCompanyName());
return CREATOR_COMPANY_SUCCESS;
} else {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
}
} else {
model.addAttribute(RESPONSE_ERROR, String.format(COMPANY_ALREADY_EXIST, company.getCompanyName()));
}
return showCreationPage();
}
}

View File

@ -0,0 +1,82 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.creators;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import fyloz.trial.ColorRecipesExplorer.services.MaterialTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_MATERIAL;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_MATERIAL_SUCCESS;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class MaterialCreatorController {
private MaterialService materialService;
private MaterialTypeService materialTypeService;
@Autowired
public MaterialCreatorController(MaterialService materialService, MaterialTypeService materialTypeService) {
this.materialService = materialService;
this.materialTypeService = materialTypeService;
}
/**
* Affiche la page de création d'un produit.
*
* @return La page à afficher.
*/
@GetMapping(CREATOR_MATERIAL)
public String showCreationPage(Model model) {
model.addAttribute(MATERIAL_TYPES, materialTypeService.getAll());
return CREATOR_MATERIAL;
}
/**
* Permet à l'utilisateur de créer un produit.
* <p>
* La création échouera si:
* - L'utilisateur n'est pas autorisé à effectuer cette action
* - Le produit existe déjà
* - Une erreur est survenue lors de la sauvegarde dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - materialCode: Contient le code de produit du produit créé
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le modèle injecté par Thymeleaf
* @param material Le produit à créer
* @param simdut Le fichier SIMDUT du produit (optionnel)
* @return La page à afficher.
*/
@PostMapping(value = CREATOR_MATERIAL, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String create(Model model, @Valid Material material, MultipartFile simdut) {
if (!materialService.exists(material)) {
if ((material = materialService.save(material)) != null) {
model.addAttribute(MATERIAL_CODE, material.getMaterialCode());
if (!materialService.addSimdut(simdut, material)) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING_SIMDUT);
}
return CREATOR_MATERIAL_SUCCESS;
} else {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
}
} else {
model.addAttribute(RESPONSE_ERROR, String.format(MATERIAL_ALREADY_EXIST, material.getMaterialCode()));
}
return showCreationPage(model);
}
}

View File

@ -0,0 +1,46 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.creators;
import fyloz.trial.ColorRecipesExplorer.model.MaterialType;
import fyloz.trial.ColorRecipesExplorer.services.MaterialTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_MATERIAL_TYPE;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.INDEX;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class MaterialTypeCreatorController {
private MaterialTypeService materialTypeService;
@Autowired
public MaterialTypeCreatorController(MaterialTypeService materialTypeService) {
this.materialTypeService = materialTypeService;
}
@GetMapping(CREATOR_MATERIAL_TYPE)
public String showPage() {
return CREATOR_MATERIAL_TYPE;
}
@PostMapping(CREATOR_MATERIAL_TYPE)
public String create(Model model, @Valid MaterialType materialType) {
if (materialTypeService.isValidForCreation(materialType)) {
if (materialTypeService.save(materialType) != null) {
return "redirect:/" + INDEX;
} else {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
}
} else {
model.addAttribute(RESPONSE_ERROR, String.format(MIX_TYPE_ALREADY_USED, materialType.getMaterialTypeName()));
}
return showPage();
}
}

View File

@ -0,0 +1,126 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.creators;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.MixType;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import fyloz.trial.ColorRecipesExplorer.services.MixService;
import fyloz.trial.ColorRecipesExplorer.services.MixTypeService;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.*;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class MixCreatorController {
private MixService mixService;
private RecipeService recipeService;
private MaterialService materialService;
private MixTypeService mixTypeService;
@Autowired
public MixCreatorController(MixService mixService, RecipeService recipeService, MaterialService materialService, MixTypeService mixTypeService) {
this.mixService = mixService;
this.recipeService = recipeService;
this.materialService = materialService;
this.mixTypeService = mixTypeService;
}
/**
* Affiche la page de création d'un mélange.
* Cette méthode requiert l'identifiant d'une recette dans l'URL.
*
* @param model Le Model injecté par Thymeleaf
* @param recipeID L'identifiant de la recette
* @return La page à afficher.
*/
@GetMapping(CREATOR_MIX_SPECIFIC)
public String showCreationPage(Model model, @PathVariable int recipeID) {
Recipe recipe = recipeService.getByID(recipeID);
if (recipe == null) {
return "redirect:/" + EDITOR_RECIPE;
}
List<MixType> associatedMixTypes = recipeService.getAssociatedMixesTypes(recipe);
// Récupère seulement les produits qui ne sont pas des types de mélange OU que ce type existe dans la recette
List<Material> materials = materialService.getAll().stream().filter(m -> !m.isMixType() || associatedMixTypes.contains(mixTypeService.getByMaterial(m))).collect(Collectors.toList());
model.addAttribute(RECIPE, recipe);
model.addAttribute(MATERIALS, materials);
model.addAttribute("materialsJson", materialService.asJson(materials)); // Ajoute les matériaux sous forme de JSON pour utiliser la liste en Javascript
return CREATOR_MIX;
}
/**
* Permet à l'utilisateur de créer un mélange.
* <p>
* La création échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - La recette n'existe pas
* - Un des produits n'existe pas
* - Une erreur est survenue lors de la sauvegarde du type de mélange
* - Une erreur est survenue lors de la sauvegarde du mélange
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param form La formulaire du mélange entré par l'utilisateur
* @return La page à afficher.
*/
@PostMapping(value = CREATOR_MIX, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String createMix(Model model, @RequestBody MultiValueMap<String, String> form) {
String mixTypeName = form.getFirst(MIX_TYPE);
int recipeID = Integer.parseInt(form.getFirst(RECIPE_ID));
List<Integer> materials = new ArrayList<>();
List<Float> quantities = new ArrayList<>();
// Trie les valeurs du formulaire de création du mélange
for (String key : form.keySet()) {
String value = form.getFirst(key);
if (key.startsWith("product")) materials.add(Integer.parseInt(value));
else if (key.startsWith("quantity") && !value.isEmpty()) quantities.add(Float.parseFloat(value));
}
Recipe recipe = recipeService.getByID(recipeID);
if (recipe == null) {
model.addAttribute(RESPONSE_ERROR, String.format(RECIPE_NOT_FOUND, recipeID));
return EDITOR_RECIPE;
}
MixType mixType = mixTypeService.createByName(mixTypeName);
if (mixType == null) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
return EDITOR_RECIPE;
} else if (recipeService.getAssociatedMixesTypes(recipe).contains(mixType)) {
model.addAttribute(RESPONSE_ERROR, MIX_TYPE_ALREADY_USED);
return EDITOR_RECIPE;
}
String errorMessage = mixService.create(materials, quantities, recipe, mixType);
if (errorMessage != null) {
model.addAttribute(RESPONSE_ERROR, errorMessage);
return showCreationPage(model, recipeID);
}
return "redirect:/" + EDITOR_RECIPE_SPECIFIC.replaceAll("\\{" + RECIPE_ID + "}", String.valueOf(recipeID));
}
}

View File

@ -0,0 +1,70 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.creators;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.CompanyService;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_RECIPE;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.CREATOR_RECIPE_SUCCESS;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class RecipeCreatorController {
private RecipeService recipeService;
private CompanyService companyService;
@Autowired
public RecipeCreatorController(RecipeService recipeService, CompanyService companyService) {
this.recipeService = recipeService;
this.companyService = companyService;
}
/**
* Affiche la page de création d'une recette.
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(CREATOR_RECIPE)
public String showCreationPage(Model model) {
model.addAttribute(COMPANIES, companyService.getAll());
return CREATOR_RECIPE;
}
/**
* Permet à l'utilisateur de créer une nouvelle recette.
* <p>
* La création échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Une erreur est survenue lors de la sauvegarde dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param recipe La recette à créer
* @return La page à afficher.
*/
@PostMapping(value = CREATOR_RECIPE)
public String createRecipe(Model model, @Valid Recipe recipe) {
if ((recipe = recipeService.save(recipe)) != null) {
model.addAttribute(RECIPE_CODE, recipe.getRecipeCode());
model.addAttribute(RECIPE_ID, recipe.getRecipeID());
return CREATOR_RECIPE_SUCCESS;
} else {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
return showCreationPage(model);
}
}
}

View File

@ -0,0 +1,153 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.editors;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import fyloz.trial.ColorRecipesExplorer.services.MaterialTypeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import java.util.stream.Collectors;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.*;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class MaterialEditorController {
private MaterialService materialService;
private MaterialTypeService materialTypeService;
@Autowired
public MaterialEditorController(MaterialService materialService, MaterialTypeService materialTypeService) {
this.materialService = materialService;
this.materialTypeService = materialTypeService;
}
/**
* Affiche la page de tous les produits.
* <p>
* Modèle de la page:
* - materials: La liste de tous les produits
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(EDITOR_MATERIAL)
public String listMaterials(Model model) {
model.addAttribute(MATERIALS, materialService.getAll().stream().filter(m -> !m.isMixType()).collect(Collectors.toList()));
return EDITOR_MATERIAL;
}
/**
* Affiche la page d'édition d'un produit.
* La méthode requiert l'identifiant du produit à modifier dans l'URL.
*
* @param model Le Model injecté par Thymeleaf
* @param materialID L'identifiant du produit à modifier
* @return La page à afficher.
*/
@GetMapping(EDITOR_MATERIAL_SPECIFIC)
public String showEditPage(Model model, @PathVariable int materialID) {
Material material = materialService.getByID(materialID);
if (material == null) {
model.addAttribute(RESPONSE_ERROR, String.format(MATERIAL_NOT_FOUND, materialID));
return listMaterials(model);
}
model.addAttribute(MATERIAL, material);
model.addAttribute(MATERIAL_TYPES, materialTypeService.getAll());
return EDITOR_MATERIAL_EDITOR;
}
/**
* Affiche la page d'upload d'un fichier SIMDUT pour un produit
* Cette méthode requiert l'identifiant du produit dans l'URL
* <p>
* Modèle de la page:
* - materialID: Contient l'identifiant du produit
*
* @param model Le Model injecté par Thymeleaf
* @param materialID L'identifiant du produit à modifier le SIMDUT
* @return La page à afficher.
*/
@GetMapping(value = EDIT_MATERIAL_SIMDUT_SPECIFIC)
public String chooseSIMDUTFile(Model model, @PathVariable int materialID) {
model.addAttribute(MATERIAL_ID, materialID);
return EDIT_MATERIAL_SIMDUT;
}
/**
* Sauvegarde le fichier SIMDUT d'un produit.
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param materialID L'identifiant du produit
* @param simdut Le fichier SIMDUT uploadé
* @return La page à afficher.
*/
@PostMapping(value = EDIT_MATERIAL_SIMDUT, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String saveSIMDUT(Model model, int materialID, MultipartFile simdut) {
Material material = materialService.getByID(materialID);
if (material == null) {
model.addAttribute(RESPONSE_ERROR, MATERIAL_NOT_FOUND);
return chooseSIMDUTFile(model, materialID);
}
if (!materialService.removeSimdut(material) || !materialService.addSimdut(simdut, material)) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING_SIMDUT);
return chooseSIMDUTFile(model, materialID);
}
return "redirect:/material/editor/" + materialID;
}
/**
* Permet à l'utilisateur de modifier un produit.
* <p>
* La modification échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Il n'y a aucun produit avec l'identifiant passé en paramètre
* - Une erreur est survenue lors de la mise à jour dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - materialCode: Contient le code du produit mit à jour
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param material Le produit à mettre à jour
* @return La page à afficher.
*/
@PostMapping(value = EDITOR_MATERIAL, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String saveEditedMaterial(Model model, Material material) {
int materialID = material.getMaterialID();
if (materialService.getByID(materialID) == null) {
model.addAttribute(RESPONSE_ERROR, String.format(MATERIAL_NOT_FOUND, materialID));
}
if ((material = materialService.update(material)) != null) {
model.addAttribute(MATERIAL_CODE, material.getMaterialCode());
} else {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
return showEditPage(model, materialID);
}
return listMaterials(model);
}
}

View File

@ -0,0 +1,136 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.editors;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.model.Mix;
import fyloz.trial.ColorRecipesExplorer.model.MixType;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import fyloz.trial.ColorRecipesExplorer.services.MixService;
import fyloz.trial.ColorRecipesExplorer.services.MixTypeService;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.List;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.*;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class MixEditorController {
private MixService mixService;
private MaterialService materialService;
private RecipeService recipeService;
private MixTypeService mixTypeService;
@Autowired
public MixEditorController(MixService mixService, MaterialService materialService, RecipeService recipeService, MixTypeService mixTypeService) {
this.mixService = mixService;
this.materialService = materialService;
this.recipeService = recipeService;
this.mixTypeService = mixTypeService;
}
/**
* Affiche la page d'édition d'un mélange.
* Cette méthode requiert l'identifiant du mélange à modifier dans l'URL.
*
* @param model Le Model injecté par Thymeleaf
* @param mixID L'identifiant du mélange à modifier
* @return La page à afficher.
*/
@GetMapping(EDITOR_MIX_SPECIFIC)
public String showPage(Model model, @PathVariable int mixID) {
Mix mix = mixService.getByID(mixID);
// Renvoie l'utilisateur à la page d'édition des recettes si le mélange n'est pas trouvé
if (mix == null) {
return "redirect:/" + EDITOR_RECIPE;
}
List<Material> materials = new ArrayList<>();
List<MixType> associatedMixTypes = recipeService.getAssociatedMixesTypes(mix.getRecipe());
for (Material material : materialService.getAll()) {
if (!material.isMixType() || (!mix.getMixType().getMaterial().equals(material) && associatedMixTypes.contains(mixTypeService.getByMaterial(material)))) {
materials.add(material);
}
}
model.addAttribute(MIX, mix);
model.addAttribute("mixJson", mixService.asJson(mix));
model.addAttribute(RECIPE_CODE, mix.getRecipe().getRecipeCode());
model.addAttribute("materialsJson", materialService.asJson(materials));
return EDITOR_MIX_SPECIFIC.replaceAll("/\\{" + MIX_ID + "}", "");
}
/**
* Permet à l'utilisateur de modifier un mélange.
* <p>
* La mise à jour échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Le mélange n'existe pas
* - Un des produits n'existe pas
* - Une erreur est survenue lors de la mise à jour dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param form Le formulaire du mélange entré par l'utilisateur
* @return La page à afficher.
*/
@PostMapping(value = EDITOR_MIX, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String saveMix(Model model, @RequestBody MultiValueMap<String, String> form) {
int mixID = Integer.parseInt(form.getFirst(MIX_ID));
Mix mix = mixService.getByID(mixID);
if (mix == null) {
model.addAttribute(RESPONSE_ERROR, String.format(MIX_NOT_FOUND, mixID));
return showPage(model, mixID);
}
List<Integer> materials = new ArrayList<>();
List<Float> quantities = new ArrayList<>();
for (String key : form.keySet()) {
String value = form.getFirst(key);
if (key.startsWith("product")) materials.add(Integer.parseInt(value));
else if (key.startsWith("quantity")) quantities.add(Float.parseFloat(value));
}
if (!mixService.edit(mix, materials, quantities)) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
return showPage(model, mixID);
}
return "redirect:/" + EDITOR_RECIPE_SPECIFIC.replace("{recipeID}", String.valueOf(mix.getRecipe().getRecipeID()));
}
@GetMapping(REMOVER_MIX_SPECIFIC)
public String deleteMix(Model model, @PathVariable int mixID) {
Mix mix = mixService.getByID(mixID);
if (mix == null) {
model.addAttribute(RESPONSE_ERROR, MIX_NOT_FOUND);
return showPage(model, mixID);
}
if (!mixService.deleteMix(mix)) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING);
return showPage(model, mixID);
}
return "redirect:/" + EDITOR_RECIPE_SPECIFIC.replace("{recipeID}", String.valueOf(mix.getRecipe().getRecipeID()));
}
}

View File

@ -0,0 +1,116 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.editors;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.CompanyService;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.*;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class RecipeEditorController {
private RecipeService recipeService;
private CompanyService companyService;
@Autowired
public RecipeEditorController(RecipeService recipeService, CompanyService companyService) {
this.recipeService = recipeService;
this.companyService = companyService;
}
/**
* Affiche la page listant toutes les recettes.
* <p>
* Modèle de la page:
* - recipes: Contient un Map de toutes les recettes triées par compagnie
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(EDITOR_RECIPE)
public String listRecipes(Model model) {
model.addAttribute(RECIPES, recipeService.getRecipesByCompany());
return EDITOR_RECIPE;
}
/**
* Affiche la page d'édition d'une recette.
* Cette méthode requiert l'identifiant de la recette dans l'URL.
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipe: Contient la recette
* - recipeJSON: Contient la recette sous forme de JSON
* - companies: Contient la liste des compagnies
* - mixes: Contient la liste des mélanges de la recettes, classées
* - images: Contient la liste des noms des images liées à la recette
*
* @param model Le Model injecté par Thymeleaf
* @param recipeID L'identifiant de la recette
* @return La page à afficher.
*/
@GetMapping(EDITOR_RECIPE_SPECIFIC)
public String showEditPage(Model model, @PathVariable int recipeID) {
Recipe recipe = recipeService.getByID(recipeID);
if (recipe == null) {
model.addAttribute(RESPONSE_ERROR, String.format(RECIPE_NOT_FOUND, recipeID));
return listRecipes(model);
}
model.addAttribute(RECIPE, recipe);
model.addAttribute("recipeJSON", recipeService.asJson(recipe));
model.addAttribute(COMPANIES, companyService.getAll());
model.addAttribute(MIXES, recipeService.getSortedMixes(recipe));
model.addAttribute("images", recipeService.getImageFiles(recipe));
return EDITOR_RECIPE_EDITOR;
}
/**
* Permet à l'utilisateur de modifier une recette.
* <p>
* La mise à jour échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - La recette n'existe pas
* - Une erreur est survenue lors de la mise à jour dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipeCode: Contient la couleur d'une recette
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param recipe La recette à modifier
* @param form Le formulaire entré par l'utilisateur
* @return La page à afficher.
*/
@PostMapping(value = EDITOR_RECIPE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public String saveRecipe(Model model, @Valid Recipe recipe, @RequestBody MultiValueMap<String, Object> form) {
int recipeID = recipe.getRecipeID();
if (recipeService.getByID(recipeID) == null) {
model.addAttribute(RESPONSE_ERROR, String.format(RECIPE_NOT_FOUND, recipeID));
return listRecipes(model);
}
recipe = recipeService.createAndSetSteps(recipe, form);
model.addAttribute(RECIPE_CODE, recipe.getRecipeCode());
return listRecipes(model);
}
}

View File

@ -0,0 +1,77 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.removers;
import fyloz.trial.ColorRecipesExplorer.model.Company;
import fyloz.trial.ColorRecipesExplorer.services.CompanyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.REMOVER_COMPANY;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.REMOVER_COMPANY_SPECIFIC;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class CompanyRemoverController {
private CompanyService companyService;
@Autowired
public CompanyRemoverController(CompanyService companyService) {
this.companyService = companyService;
}
/**
* Affiche la page listant toutes les bannières.
* <p>
* Modèle de la page:
* - companies: Contient la liste de toutes les bannières
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher
*/
@GetMapping(REMOVER_COMPANY)
public String showPage(Model model) {
model.addAttribute(COMPANIES, companyService.getAll());
return REMOVER_COMPANY;
}
/**
* Permet à l'utilisateur de supprimer une bannière.
* La méthode requiert l'identifiant de la bannière à supprimer dans l'URL.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Il n'y a aucune bannière avec cet identifiant
* - La bannière est associée à une ou plusieurs recettes
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - successCompanyName: Contient le nom de la bannière supprimée
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param companyID L'identifiant de la bannière
* @return La page à afficher
*/
@PostMapping(REMOVER_COMPANY_SPECIFIC)
public String removeCompany(Model model, @PathVariable int companyID) {
Company company = companyService.getByID(companyID);
if (companyService.exists(company)) {
if (companyService.deleteIfNotLinked(company)) {
model.addAttribute("successCompanyName", company.getCompanyName());
} else {
model.addAttribute(RESPONSE_ERROR, String.format(COMPANY_LINKED, company.getCompanyName()));
}
} else {
model.addAttribute(RESPONSE_ERROR, String.format(COMPANY_NOT_FOUND, companyID));
}
return showPage(model);
}
}

View File

@ -0,0 +1,80 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.removers;
import fyloz.trial.ColorRecipesExplorer.model.Material;
import fyloz.trial.ColorRecipesExplorer.services.MaterialService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.stream.Collectors;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.REMOVER_MATERIAL;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.REMOVER_MATERIAL_SPECIFIC;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class MaterialRemoverController {
private MaterialService materialService;
@Autowired
public MaterialRemoverController(MaterialService materialService) {
this.materialService = materialService;
}
/**
* Affiche la page de suppression des produits
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(REMOVER_MATERIAL)
public String showPage(Model model) {
model.addAttribute(MATERIALS, materialService.getAll().stream().filter(m -> !m.isMixType()).collect(Collectors.toList()));
return REMOVER_MATERIAL;
}
/**
* Permet à l'utilisateur de supprimer un produit.
* Cette méthode requiert l'identifiant du produit dans l'URL.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - Le produit n'existe pas
* - Le produit est lié à d'autres mélanges
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - materialCode: Contient le code de produit du produit supprimé
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param materialID L'identifiant du produit à supprimer
* @return La page à afficher.
*/
@PostMapping(REMOVER_MATERIAL_SPECIFIC)
public String removeMaterial(Model model, @PathVariable int materialID) {
Material material = materialService.getByID(materialID);
if (material == null) {
model.addAttribute(RESPONSE_ERROR, String.format(MATERIAL_NOT_FOUND, materialID));
} else {
if (materialService.deleteIfNotLinked(material)) {
model.addAttribute(MATERIAL_CODE, material.getMaterialCode());
if (!materialService.removeSimdut(material)) {
model.addAttribute(RESPONSE_ERROR, ERROR_SAVING_SIMDUT);
}
} else {
model.addAttribute(RESPONSE_ERROR, String.format(MATERIAL_LINKED, material.getMaterialCode()));
}
}
return showPage(model);
}
}

View File

@ -0,0 +1,71 @@
package fyloz.trial.ColorRecipesExplorer.web.controller.removers;
import fyloz.trial.ColorRecipesExplorer.model.Recipe;
import fyloz.trial.ColorRecipesExplorer.services.RecipeService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.REMOVER_RECIPE;
import static fyloz.trial.ColorRecipesExplorer.web.PagesPaths.REMOVER_RECIPE_SPECIFIC;
import static fyloz.trial.ColorRecipesExplorer.web.StringBank.*;
@Controller
public class RecipeRemoverController {
private RecipeService recipeService;
public RecipeRemoverController(RecipeService recipeService) {
this.recipeService = recipeService;
}
/**
* Affiche la liste de toutes les recettes.
*
* @param model Le Model injecté par Thymeleaf
* @return La page à afficher.
*/
@GetMapping(REMOVER_RECIPE)
public String listRecipes(Model model) {
model.addAttribute(RECIPES, recipeService.getRecipesByCompany());
return REMOVER_RECIPE;
}
/**
* Permet à l'utilisateur de supprimer une recette.
* Cette méthode requiert l'identifiant de la recette dans l'URL.
* <p>
* La suppression échouera si:
* - L'utilisateur n'est pas autorisé à exécuter cette action
* - La recette n'existe pas
* - Une erreur est survenue lors de la suppression dans la base de données
* <p>
* Modèle de la page:
* - error: Contient le message d'erreur, s'il y a lieu
* - recipeCode: Contient la couleur de la recette
* <p>
* REQUIERT UNE AUTORISATION
*
* @param model Le Model injecté par Thymeleaf
* @param recipeID L'identifiant de la recette
* @return La page à afficher.
*/
@PostMapping(REMOVER_RECIPE_SPECIFIC)
public String removeRecipe(Model model, @PathVariable int recipeID) {
Recipe recipe = recipeService.getByID(recipeID);
// Affiche un erreur si le recette n'est pas trouvée
if (recipe == null) {
model.addAttribute(RESPONSE_ERROR, String.format(RECIPE_NOT_FOUND, recipeID));
return listRecipes(model);
}
recipeService.deleteRecipe(recipe);
model.addAttribute(RECIPE_CODE, recipe.getRecipeCode());
return listRecipes(model);
}
}

View File

@ -0,0 +1,26 @@
# Tomcat
server.port=8080
# Favicon
spring.mvc.favicon.enabled=false
# H2
spring.datasource.url=jdbc:h2:file:./recipes
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.datasource.driver-class-name=org.h2.Driver
# JPA
#spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=false
# Thymeleaf
spring.thymeleaf.template-loader-path=classpath:/templates
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false
# Max file size.
spring.servlet.multipart.max-file-size=10MB
# Max request size.
spring.servlet.multipart.max-request-size=15MB
# DEBUG
logging.level.org.springframework.web=DEBUG
logging.level.com.zaxxer.hikari=DEBUG
spring.h2.console.enabled=true

View File

@ -0,0 +1,2 @@
INSERT INTO MATERIAL_TYPE
VALUES (0, 'Aucun', '', FALSE);

View File

@ -0,0 +1,8 @@
.form {
display: inline-block;
margin-top: 10px;
}
td:not(.centerTd) {
text-align: right;
}

View File

@ -0,0 +1,109 @@
body {
margin: 0;
font-family: 'Open Sans', sans-serif;
}
h1 {
text-decoration: underline;
}
header {
background-color: black;
height: 70px;
text-align: center;
width: 100%;
}
header img {
float: right;
}
section {
text-align: center;
}
nav {
overflow: hidden;
background-color: black;
}
nav a {
float: left;
font-size: 18px;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
.dropdown {
margin-top: 22px;
float: left;
overflow: hidden;
}
.dropdown .dropbtn {
font-size: 16px;
border: none;
outline: none;
color: white;
padding: 14px 16px;
background-color: inherit;
font-family: inherit;
margin: 0;
}
nav a:hover, .dropdown:hover .dropbtn {
background-color: #0d0d0d;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #0d0d0d;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}
.dropdown-content a {
float: none;
color: white;
padding: 12px 16px;
text-decoration: none;
display: block;
text-align: left;
}
.dropdown-content a:hover {
background-color: #1a1a1a;
}
.dropdown:hover .dropdown-content {
display: block;
}
.error {
color: red;
}
.success {
color: green;
}
.error, .success {
font-weight: bold;
}
.mainTable {
border-spacing: 30px;
}
.mainTableEndButtons {
text-align: center;
}
.nosimdut td {
background-color: #ffc299;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 252 B

View File

@ -0,0 +1,51 @@
<nav>
<img alt="logo" src="/logo.png"/>
<div class="dropdown">
<button class="dropbtn" onclick="document.location.href='/'">
Liste
</button>
</div>
<div class="dropdown">
<button class="dropbtn">
Ajouter
</button>
<div class="dropdown-content">
<a href="/company/creator">Bannière</a>
<a href="/material/creator">Produit</a>
<a href="/recipe/creator">Recette</a>
<a href="/materialType/creator">Type de produit</a>
</div>
</div>
<div class="dropdown">
<button class="dropbtn">
Modifier
</button>
<div class="dropdown-content">
<a href="/material/editor">Produit</a>
<a href="/recipe/editor">Recette</a>
</div>
</div>
<div class="dropdown">
<button class="dropbtn">
Supprimer
</button>
<div class="dropdown-content">
<a href="/company/remover">Bannière</a>
<a href="/material/remover">Produit</a>
<a href="/recipe/remover">Recette</a>
</div>
</div>
<div class="dropdown">
<button class="dropbtn" onclick="document.location.href='/inventory'">
Inventaire
</button>
</div>
<div class="dropdown">
<button class="dropbtn">
Autres
</button>
<div class="dropdown-content">
<a href="/touchup">PDF Kits de retouche</a>
</div>
</div>
</nav>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,144 @@
(() => {
const body = document.querySelector("body");
const errorP = document.querySelector(".error");
// Ajoute Axios
const axiosElement = document.createElement("script");
axiosElement.src = "/js/libs/axios.min.js";
body.appendChild(axiosElement);
axiosElement.onload = () => {
// Ajoute le header
axios.get('/header.html')
.then(r => {
const header = document.createElement("header");
header.innerHTML = r.data;
body.insertBefore(header, document.querySelector("section"));
})
.catch(e => {
console.log(e);
errorP.innerHTML = "Une erreur est survenue lors de la récupération de l'entête";
});
// Vérifie si les SIMDUTs sont présents
document.querySelectorAll(".materialCode").forEach(e => {
const materialID = e.getAttribute("data-materialID");
axios.get(`/simdut/${materialID}`)
.catch(err => {
if (err.response.status === 404) {
e.parentElement.classList.add("nosimdut");
e.parentElement.title = "Aucun fichier SIMDUT trouvé";
} else {
console.log(e);
}
});
});
};
document.querySelectorAll(".returnIndex").forEach((e) => {
e.addEventListener("click", () => {
document.location.href = "/";
});
});
document.querySelectorAll(".materialCode").forEach((e) => {
const materialID = e.getAttribute("data-materialID");
e.addEventListener("click", () => {
window.open("/simdut/" + materialID, "_blank");
});
});
document.querySelectorAll("img").forEach((e) => {
e.addEventListener("click", () => {
window.open(e.src, "_blank");
});
});
document.querySelectorAll(".requireAuth").forEach((e) => {
e.onsubmit = () => {
checkPassword(e);
return false;
};
});
document.querySelectorAll(".requireAuth-remover").forEach((e) => {
e.querySelectorAll(".remover").forEach(elem => {
elem.addEventListener("click", () => {
e.action += elem.getAttribute("data-recipeID");
checkPassword(e);
});
});
});
document.querySelectorAll(".companyTabTitle").forEach(e => {
e.addEventListener("click", () => {
const companyName = e.getAttribute("data-companyName");
const table = document.getElementById("recipes_" + companyName);
if (table.style.display === "none") {
table.style.display = "table";
} else {
table.style.display = "none";
}
});
});
// Ajoute le favicon
let faviconElement = document.createElement("link");
faviconElement.rel = "icon";
faviconElement.href = "/favicon.png";
document.querySelector("head").appendChild(faviconElement);
function checkPassword(form) {
errorP.innerHTML = "";
const password = prompt("Quel est votre mot de passe?");
let data = {};
data.password = password;
axios.post("/password/valid", data)
.then(r => {
console.log(r);
if (r.data) {
form.submit();
} else {
errorP.innerHTML = "Votre mot de passe n'est pas valide";
}
})
.catch(e => {
errorP.innerHTML = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
console.log(e);
});
}
})();
const lTomL = 1000;
const galTomL = 3785.41;
// Change les unités selon la sélection de l'utilisateur
function changeUnits(unitSelect, quantitiesSelector, unitsSelector) {
document.querySelectorAll(unitsSelector).forEach(e => {
e.innerText = unitSelect.value;
// Modifie la quantitée
const quantityElem = e.parentElement.parentElement.querySelector(quantitiesSelector);
const originalQuantity = parseInt(quantityElem.dataset.quantityml);
switch (unitSelect.value) {
case "L":
quantityElem.innerText = originalQuantity / lTomL;
break;
case "gal":
quantityElem.innerText = originalQuantity / galTomL;
break;
default:
quantityElem.innerText = originalQuantity;
break;
}
// Arrondi à deux décimaux
quantityElem.innerText = Math.round(quantityElem.innerText * 100) / 100;
});
}

View File

@ -0,0 +1,150 @@
(() => {
// Ajoute Axios
const axiosElement = document.createElement("script");
axiosElement.src = "/js/libs/axios.min.js";
body.appendChild(axiosElement);
// Ajoute jQuery
const jqueryElement = document.createElement("script");
jqueryElement.src = "/js/libs/jquery-3.4.1.min.js";
body.appendChild(jqueryElement);
const body = $("body");
const errorP = $(".error");
axiosElement.onload = () => {
// Ajoute le header
axios.get('/header.html')
.then(r => {
const header = document.createElement("header");
header.innerHTML = r.data;
body.insertBefore(header, document.querySelector("section"));
})
.catch(e => {
console.log(e);
errorP.innerHTML = "Une erreur est survenue lors de la récupération de l'entête";
});
// Vérifie si les SIMDUTs sont présents
document.querySelectorAll(".materialCode").forEach(e => {
const materialID = e.getAttribute("data-materialID");
axios.get(`/simdut/${materialID}`)
.catch(err => {
if (err.response.status === 404) {
e.parentElement.classList.add("nosimdut");
e.parentElement.title = "Aucun fichier SIMDUT trouvé";
} else {
console.log(e);
}
});
});
};
document.querySelectorAll(".returnIndex").forEach((e) => {
e.addEventListener("click", () => {
document.location.href = "/";
});
});
document.querySelectorAll(".materialCode").forEach((e) => {
const materialID = e.getAttribute("data-materialID");
e.addEventListener("click", () => {
window.open("/simdut/" + materialID, "_blank");
});
});
document.querySelectorAll("img").forEach((e) => {
e.addEventListener("click", () => {
window.open(e.src, "_blank");
});
});
document.querySelectorAll(".requireAuth").forEach((e) => {
e.onsubmit = () => {
checkPassword(e);
return false;
};
});
document.querySelectorAll(".requireAuth-remover").forEach((e) => {
e.querySelectorAll(".remover").forEach(elem => {
elem.addEventListener("click", () => {
e.action += elem.getAttribute("data-recipeID");
checkPassword(e);
});
});
});
document.querySelectorAll(".companyTabTitle").forEach(e => {
e.addEventListener("click", () => {
const companyName = e.getAttribute("data-companyName");
const table = document.getElementById("recipes_" + companyName);
if (table.style.display === "none") {
table.style.display = "table";
} else {
table.style.display = "none";
}
});
});
// Ajoute le favicon
let faviconElement = document.createElement("link");
faviconElement.rel = "icon";
faviconElement.href = "/favicon.png";
document.querySelector("head").appendChild(faviconElement);
function checkPassword(form) {
errorP.innerHTML = "";
const password = prompt("Quel est votre mot de passe?");
let data = {};
data.password = password;
axios.post("/password/valid", data)
.then(r => {
console.log(r);
if (r.data) {
form.submit();
} else {
errorP.innerHTML = "Votre mot de passe n'est pas valide";
}
})
.catch(e => {
errorP.innerHTML = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
console.log(e);
});
}
})();
const lTomL = 1000;
const galTomL = 3785.41;
// Change les unités selon la sélection de l'utilisateur
function changeUnits(unitSelect, quantitiesSelector, unitsSelector) {
document.querySelectorAll(unitsSelector).forEach(e => {
e.innerText = unitSelect.value;
// Modifie la quantitée
const quantityElem = e.parentElement.parentElement.querySelector(quantitiesSelector);
const originalQuantity = parseInt(quantityElem.dataset.quantityml);
switch (unitSelect.value) {
case "L":
quantityElem.innerText = originalQuantity / lTomL;
break;
case "gal":
quantityElem.innerText = originalQuantity / galTomL;
break;
default:
quantityElem.innerText = originalQuantity;
break;
}
// Arrondi à deux décimaux
quantityElem.innerText = Math.round(quantityElem.innerText * 100) / 100;
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'une bannière</title>
<link href="/css/main.css" rel="stylesheet"/>
</head>
<body>
<section>
<h1>Ajout d'une bannière</h1>
<p th:text="'La bannière ' + ${companyName} + ' à été enregistrée.'"></p>
<button class="returnIndex">Retour</button>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'une bannière</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1>Ajout d'une bannière</h1>
<div class="form">
<form action="/company/creator" class="requireAuth" method="POST">
<table>
<tr>
<td><b><label for="companyName">Nom de la bannière: </label></b></td>
<td><input id="companyName" name="companyName" type="text"/></td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Supprimer des bannières</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
<style>
table {
margin: auto;
}
th {
padding-right: 50px;
}
td {
text-align: left;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<p class="success" th:if="${successCompanyName != null}"
th:text="'La bannière \'' + ${successCompanyName} + '\' a bien été supprimée.'"></b>
</p>
<h1>Supprimer des bannières</h1>
<form action="/company/remover/" class="requireAuth-remover" method="POST">
<th:block th:if="${!companies.empty}">
<table id="companiesList">
<tr>
<th>Nom de la bannière</th>
</tr>
<th:block th:each="company : ${companies}">
<tr>
<td th:text="${company.companyName}"></td>
<td>
<button class="remover" th:data-code="${company.companyName}"
th:data-recipeID="${company.companyID}"
type="button">Supprimer
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
</form>
<th:block th:if="${companies.empty}">
<b class="error">Aucune bannière n'a été trouvée.</b>
</th:block>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout une image</title>
<link href="/css/main.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<form action="/images/add" class="requireAuth" enctype="multipart/form-data" method="POST">
<input name="recipeID" th:value="${recipeID}" type="hidden"/>
<input id="image" name="image" required type="file"/>
<br/>
<input type="submit" value="Sauvegarde"/>
</form>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Explorateur de recettes de couleur</title>
<link href="/css/main.css" rel="stylesheet"/>
<style>
h1 {
margin-bottom: 0;
}
th {
padding: 15px;
}
td {
padding: 0 20px;
}
.centerCell {
border-left-color: #e6e6e6;
border-left-style: solid;
border-left-width: 2px;
border-right-color: #e6e6e6;
border-right-style: solid;
border-right-width: 2px;
}
table {
border-collapse: collapse;
margin: 0px auto;
text-align: center;
}
section {
text-align: center;
}
.recipeDescription {
max-width: 400px;
}
.unapproved {
background-color: #fff0b3;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<div>
<th:block th:if="${!recipes.empty}">
<th:block th:each="company : ${recipes.keySet()}">
<h1 class="companyTabTitle" th:data-companyName="${company.companyName}"
th:text="${company.companyName}"></h1>
<th:block th:if="${!recipes.get(company).empty}">
<table style="display:none" th:id="'recipes_' + ${company.companyName}">
<tr>
<th>Couleur</th>
<th>Description</th>
<th>Échantillon</th>
</tr>
<th:block th:each="recipe : ${recipes.get(company)}">
<tr class="recipeRow" th:data-approbationDate="${recipe.approbationDate}">
<td th:text="${recipe.recipeCode}"></td>
<td class="centerCell recipeDescription" th:text="${recipe.recipeDescription}"></td>
<td class="centerCell" th:text="${recipe.sample}"></td>
<td>
<button class="gotoRecipe" th:data-recipeID="${recipe.recipeID}" type="button">
Voir
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
</th:block>
</th:block>
<th:block th:if="${recipes.empty}">
<b class="error">Aucune bannière n'a été trouvée.</b>
</th:block>
</div>
</section>
<script src="/js/main.js"></script>
<script>
(() => {
document.querySelectorAll(".gotoRecipe").forEach(e => {
e.addEventListener("click", () => {
const recipeID = e.getAttribute("data-recipeID");
document.location.href = "/recipe/explore/" + recipeID;
});
});
document.querySelectorAll(".recipeRow").forEach(e => {
const approbationDate = e.getAttribute("data-approbationDate");
if (approbationDate === null) {
e.classList.add("unapproved");
e.title = "Cette recette n'est pas approuvée";
}
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Inventaire</title>
<link href="/css/main.css" rel="stylesheet"/>
<style>
td, th {
min-width: 100px;
}
table {
margin: 50px auto;
text-align: left;
}
section {
text-align: center;
}
.materialRow:hover td {
background-color: #f0f0f0;
font-weight: bolder;
}
.lowQuantity td {
background-color: #fff0b3;
}
.hidden, .hiddenWrongType {
display: none;
}
</style>
</head>
<body>
<section>
<th:block th:each="materialType : ${materialTypes}"
th:if="${materialType.materialTypeName.equalsIgnoreCase('aucun')}">
<input id="anyTypeId" th:value="${materialType.materialTypeID}" type="hidden"/>
</th:block>
<h1>Inventaire</h1>
<th:block th:if="${!materials.empty}">
<table>
<!--Titres des colonnes et options de recherche-->
<tr>
<th>Produit</th>
<th>Quantité</th>
<!--Unités de la quantité-->
<td></td>
<th>Type</th>
<td></td>
<!--Options-->
<td rowspan="5">
<label for="lowQuantity">Quantité faible: </label>
<input id="lowQuantity" min="0" onchange="checkLowQuantity(this.value)" step="0.01"
style="width: 65px" type="number"
value="100"/>
<br/>
<input id="hideOthers" onchange="hide(this.checked)" type="checkbox"/><label for="hideOthers">Cacher
les autres produits</label>
<br/>
<br/>
<label for="materialTypeSelect">Voir seulement: </label>
<select id="materialTypeSelect" name="materialTypeSelect" onchange="hideMaterialTypes(this)">
<th:block th:each="materialType : ${materialTypes}">
<option th:text="${materialType.materialTypeName}"
th:value="${materialType.materialTypeID}"></option>
</th:block>
</select>
<br/>
<br/>
<label for="units">Unités: </label>
<select id="units" name="units"
onchange="changeUnits(this, '.inventoryQuantity', '.inventoryQuantityUnits')">
<option selected value="mL">Millilitres</option>
<option value="L">Litres</option>
<option value="gal">Gallons</option>
</select>
</td>
</tr>
<!--Produits-->
<th:block th:each="material : ${materials}">
<tr class="materialRow"
th:data-materialType="${material.materialType.materialTypeID}">
<td class="materialCode" th:data-materialID="${material.materialID}"
th:text="${material.materialCode}"></td>
<td class="inventoryQuantity" th:data-quantityML="${material.inventoryQuantity}"
th:text="${material.inventoryQuantity}"></td>
<td><span class="inventoryQuantityUnits">mL</span></td>
<td class="materialType" th:text="${material.materialType.materialTypeName}"></td>
<td>
<button class="modifyMaterial" th:data-materialID="${material.materialID}" type="button">
Modifier
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
<th:block th:if="${materials.empty}">
<b class="error">Aucun produit n'a été trouvé.</b>
</th:block>
</section>
<script>
(() => {
// Rajoute un événement sur les boutons pour modifier les produits pour rediriger l'utilisateur vers la page de modification
document.querySelectorAll(".modifyMaterial").forEach(e => {
e.addEventListener("click", () => {
document.location.href = `/material/editor/${e.dataset.materialid}`;
});
});
})();
// Ajoute la classe "lowQuantity" au produits qui ont une quantité en inventaire inférieur à la quantité limite définie
function checkLowQuantity(value) {
document.querySelectorAll(".materialRow").forEach(e => {
if (parseFloat(e.querySelector(".inventoryQuantity").innerHTML) < value) {
e.classList.add("lowQuantity");
} else {
e.classList.remove("lowQuantity");
}
hide(document.querySelector("#hideOthers").checked);
});
}
// Cache ou dévoile les produits qui ont la classe "lowQuantity", dépendamment du paramètre "checked"
function hide(checked) {
if (checked) {
document.querySelectorAll(".materialRow").forEach(e => {
if (!e.classList.contains("lowQuantity")) {
e.classList.add("hidden");
} else {
e.classList.remove("hidden");
}
});
} else {
document.querySelectorAll(".hidden").forEach(e => {
e.classList.remove("hidden");
});
}
}
// Cache les produits qui ne sont pas du type de produit spécifié dans le paramètre "select", si le type de produit n'est pas "Aucun"
function hideMaterialTypes(select) {
const value = select.value;
const anyValue = document.querySelector("#anyTypeId").value;
document.querySelectorAll(".materialRow").forEach(e => {
if (value !== anyValue && e.dataset.materialtype !== value) {
e.classList.add("hiddenWrongType");
} else {
e.classList.remove("hiddenWrongType");
}
});
}
</script>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'un produit</title>
<link href="/css/main.css" rel="stylesheet"/>
</head>
<body>
<section>
<h1>Ajout d'un produit</h1>
<p th:text="'Le produit ' + ${materialCode} + ' a été enregistré.'"></p>
<button class="returnIndex">Retour</button>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'un produit</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1>Ajout d'un produit</h1>
<div class="form">
<form action="/material/creator" class="requireAuth" enctype="multipart/form-data" method="POST">
<table>
<tr>
<td><b><label for="materialCode">Code du produit: </label></b></td>
<td><input id="materialCode" name="materialCode" type="text"/></td>
<td></td>
</tr>
<tr>
<td><b><label for="inventoryQuantity">Quantité en inventaire: </label></b></td>
<td><input data-unit="mL" id="quantity" min="0" name="quantity"
onchange="calculateMilliliters(this.value, this.dataset.unit)" step="0.01" type="number">
</td>
<!--Contient la quantité en millilitres-->
<input id="inventoryQuantity" name="inventoryQuantity" type="hidden"/>
<td>
<select id="units" name="units"
onchange="switchUnits(this)">
<option selected value="mL">Millilitres</option>
<option value="L">Litres</option>
<option value="gal">Gallons</option>
</select>
</td>
</tr>
<tr>
<td><b><label for="materialType">Type de produit: </label></b></td>
<td>
<select id="materialType" name="materialType">
<th:block th:each="materialType : ${materialTypes}">
<option th:text="${materialType.materialTypeName}"
th:value="${materialType.materialTypeID}"></option>
</th:block>
</select>
</td>
</tr>
<tr>
<td><b><label for="simdut">Fichier SIMDUT: </label></b></td>
<td><input id="simdut" name="simdut" type="file"></td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
<script>
function switchUnits(unitSelect) {
const quantityElem = document.querySelector("#quantity");
quantityElem.dataset.unit = unitSelect.value;
calculateMilliliters(quantityElem.value, unitSelect.value);
}
function calculateMilliliters(quantity, unit) {
let convertedQuantity = quantity;
switch (unit) {
case "L":
convertedQuantity = quantity * lTomL;
break;
case "gal":
convertedQuantity = quantity * galTomL;
break;
default:
break;
}
document.querySelector("#inventoryQuantity").value = convertedQuantity;
}
</script>
</body>
</html>

View File

@ -0,0 +1,109 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="'Modification de ' + ${material.materialCode}"></title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1 class="materialCode" th:text="'Modification de ' + ${material.materialCode}"></h1>
<div class="form">
<form action="/material/editor" class="requireAuth" method="POST">
<input id="materialID" name="materialID" th:value="${material.materialID}" type="hidden"/>
<input name="materialCode" th:value="${material.materialCode}" type="hidden"/>
<table>
<tr>
<td><b><label for="inventoryQuantity">Quantité en inventaire: </label></b></td>
<td><input data-unit="mL" id="quantity" min="0" name="quantity"
onchange="calculateMilliliters(this.value, this.dataset.unit)" step="0.01"
th:value="${material.inventoryQuantity}" type="number"></td>
<!--Contient la quantité en millilitres-->
<input id="inventoryQuantity" name="inventoryQuantity" th:value="${material.inventoryQuantity}"
type="hidden"/>
<td>
<select id="units" name="units"
onchange="switchUnits(this)">
<option selected value="mL">Millilitres</option>
<option value="L">Litres</option>
<option value="gal">Gallons</option>
</select>
</td>
</tr>
<tr>
<td><label for="materialType">Type de produit: </label></td>
<td><select id="materialType" name="materialType">
<th:block th:each="materialType : ${materialTypes}">
<option th:attrappend="selected=${materialType.materialTypeName.equalsIgnoreCase('aucun') || materialType.materialTypeID == material.materialType.materialTypeID}"
th:text="${materialType.materialTypeName}"
th:value="${materialType.materialTypeID}"></option>
</th:block>
</select></td>
</tr>
<tr>
<td>
<button id="showSIMDUT" type="button">Fichier SIMDUT</button>
</td>
<td>
<button id="editSIMDUT" type="button">Modifier</button>
</td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
<script>
(() => {
const materialID = document.querySelector("#materialID").value;
document.querySelector("#showSIMDUT").addEventListener("click", () => {
window.open(`/simdut/${materialID}`);
});
document.querySelector("#editSIMDUT").addEventListener("click", () => {
document.location.href = `/material/simdut/${materialID}`;
});
})();
function switchUnits(unitSelect) {
const quantityElem = document.querySelector("#quantity");
quantityElem.dataset.unit = unitSelect.value;
calculateMilliliters(quantityElem.value, unitSelect.value);
}
function calculateMilliliters(quantity, unit) {
let convertedQuantity = quantity;
switch (unit) {
case "L":
convertedQuantity = quantity * lTomL;
break;
case "gal":
convertedQuantity = quantity * galTomL;
break;
default:
break;
}
document.querySelector("#inventoryQuantity").value = convertedQuantity;
}
</script>
</body>
</html>

View File

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Modifier un produit</title>
<link href="/css/main.css" rel="stylesheet"/>
<style>
table {
margin: auto;
text-align: left;
}
th, td {
min-width: 100px;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<p class="success" th:if="${materialCode != null}"
th:text="'Le produit \'' + ${materialCode} + '\' a bien été sauvegardé.'"></b>
</p>
<h1>Modifier des produits</h1>
<th:block th:if="${!materials.empty}">
<table>
<tr>
<th>Produit</th>
<th>Type</th>
</tr>
<th:block th:each="material : ${materials}">
<tr>
<td class="materialCode" th:data-materialID="${material.materialID}"
th:text="${material.materialCode}"></td>
<td th:text="${material.materialType.materialTypeName}"></td>
<td>
<button class="editor" th:data-materialID="${material.materialID}" type="button">Modifier
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
<th:block th:if="${materials.empty}">
<b class="error">Aucun produit n'a été trouvé</b>
</th:block>
</section>
<script src="/js/main.js"></script>
<script>
/*<![CDATA[*/
(() => {
document.querySelectorAll(".editor").forEach((e) => {
e.addEventListener("click", () => {
const materialID = e.getAttribute("data-materialID");
document.location.href = "/material/editor/" + materialID;
});
});
})();
/*]]>*/
</script>
</body>
</html>

View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Supprimer des produits</title>
<link href="/css/main.css" rel="stylesheet"/>
<style>
table {
margin: auto;
text-align: left;
}
td {
min-width: 100px;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<p class="success" th:if="${materialCode != null}"
th:text="'Le produit \'' + ${materialCode} + '\' a bien été supprimée.'"></b>
</p>
<h1>Supprimer des produits</h1>
<form action="/material/remover/" class="requireAuth-remover" method="POST">
<th:block th:if="${!materials.empty}">
<table id="materialsList">
<tr>
<th>Produit</th>
<th>Type</th>
</tr>
<th:block th:each="material : ${materials}">
<tr>
<td class="materialCode" th:data-materialID="${material.materialID}"
th:text="${material.materialCode}"></td>
<td th:text="${material.materialType.materialTypeName}"></td>
<td>
<button class="remover" th:data-code="${material.materialCode}"
th:data-recipeID="${material.materialID}"
type="button">Supprimer
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
</form>
<th:block th:if="${materials.empty}">
<b class="error">Aucun produit n'a été trouvé.</b>
</th:block>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'un produit</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
</head>
<body>
<section>
<h1>Ajout d'un produit</h1>
<div class="form">
<form action="/material/simdut" class="requireAuth" enctype="multipart/form-data" method="POST">
<input name="materialID" th:value="${materialID}" type="hidden"/>
<label for="simdut">Choisissez le fichier SIMDUT du produit:</label>
<input id="simdut" name="simdut" type="file"/>
<br/>
<br/>
<input type="submit" value="Enregistrer"/>
</form>
</div>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'un type de produit</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1>Ajout d'un type de produit</h1>
<div class="form">
<form action="/materialType/creator" class="requireAuth" method="POST">
<table>
<tr>
<td><label for="materialTypeName">Nom du type de produit: </label></td>
<td><input id="materialTypeName" name="materialTypeName" required type="text"></td>
</tr>
<tr>
<td><label for="prefix">Préfixe (3 caractères): </label></td>
<td><input id="prefix" maxlength="3" minlength="3" name="prefix" required type="text"></td>
</tr>
<tr>
<td><label for="usePercentages">Utiliser les pourcentages: </label></td>
<td><input id="usePercentages" name="usePercentages" type="checkbox"></td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'un mélange</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
<style>
table {
text-align: center;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1>Ajout d'un mélange pour<br>la recette <span th:text="${recipe.recipeCode}"></span></h1>
<div class="form">
<form action="/mix/creator" class="requireAuth" method="POST">
<label for="mixType">Type de mélange: </label><input id="mixType" name="mixType" placeholder="ex: Teinture"
required type="text"/>
<!-- Information nécessaire à la création des mélanges -->
<input id="recipeID" name="recipeID" th:value="${recipe.recipeID}" type="hidden"/>
<table>
<tr>
<td>
<!-- Produits -->
<table id="products">
<tr>
<th>Produit</th>
<th>Quantité
<button type="button">+</button>
</th>
</tr>
<tr id="row_0">
<td><select class="materialList" name="product_0">
<th:block th:each="material : ${materials}">
<option th:class="'material_' + ${material.materialID}"
th:classappend="${material.materialType.getUsePercentages()} ? usePercents"
th:text="${!material.materialType.materialTypeName.equalsIgnoreCase('aucun')} ? '[' + ${material.materialType.prefix} + '] ' + ${material.materialCode} : ${material.materialCode}"
th:value="${material.materialID}">
</option>
</th:block>
</select></td>
<td><input class="quantity" min="0" name="userQuantity" step="0.01" type="number"/>
<span class="quantityUnit">mL</span></td>
<td>
<button data-remove="0" onclick="removeRow(this)" type="button">Retirer</button>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
<script>
/*<![CDATA[*/
(() => {
// Compteurs des produits
let productNbr = 1;
// Récupère les matériaux sous forme JSON depuis le thymeleaf et le valide.
const productsText = "[[${materialsJson}]]";
const products = JSON.parse(productsText.replace(/&quot;/g, '"'));
document.querySelector("#products button").addEventListener("click", () => {
let select = document.createElement("select");
select.name = `product_${productNbr}`;
products.forEach(p => {
let option = document.createElement("option");
option.value = p.materialID;
// Rajoute le préfixe du type de produit si le type de produit n'est pas <<Aucun>>
option.innerHTML = (p.materialType.materialTypeName.toLowerCase() !== 'aucun' ? `[${p.materialType.prefix}] ` : "") + p.materialCode;
select.appendChild(option);
});
let productSelection = document.createElement("td");
productSelection.appendChild(select);
let input = document.createElement("input");
input.type = "number";
input.name = `quantity_${productNbr}`;
input.step = "0.01";
let quantity = document.createElement("td");
quantity.appendChild(input);
let removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.dataset.remove = productNbr;
removeButton.innerHTML = "Retirer";
removeButton.onclick = removeRow(removeButton);
let removeColumn = document.createElement("td");
removeColumn.appendChild(removeButton);
let row = document.createElement("tr");
row.id = `row_${productNbr}`;
row.appendChild(productSelection);
row.appendChild(quantity);
row.appendChild(removeColumn);
document.querySelector("#products tbody").appendChild(row);
productNbr++;
});
document.querySelectorAll(".materialList").forEach(e => {
e.addEventListener("change", () => {
});
});
})();
function removeRow(e) {
e.addEventListener("click", () => {
document.querySelector(`#row_${e.dataset.remove}`).remove();
});
}
function switchUnits(unitSelect) {
document.querySelectorAll(".")
}
/*]]*/
</script>
</body>
</html>

View File

@ -0,0 +1,195 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Modifier un mélange</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
<style>
table {
text-align: center;
}
</style>
</head>
<body th:with="nbrProducts = 0">
<th:block th:with="nbrSteps = 0">
<section>
<p class="error" th:text="${error}"></p>
<h1>Modifier un mélange pour<br>la recette <span
th:text="${recipeCode} + ' (' + ${mix.mixType.typeName} + ')'"></span></h1>
<button id="removeMix" type="button">Supprimer</button>
<br/>
<br/>
<div class="form">
<form action="/mix/editor" class="requireAuth" method="POST">
<!-- Information nécessaire à la création des mélanges -->
<input name="mixID" th:value="${mix.mixID}" type="hidden"/>
<table>
<tr>
<td colspan="2">
<table id="products">
<tr>
<th>Produit</th>
<th>Quantité
<button type="button">+</button>
</th>
</tr>
</table>
</td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
<script>
/*<![CDATA[*/
(() => {
// Compteurs des produits et des étapes
let productNbr = 0;
const mixText = "[[${mixJson}]]";
const mix = JSON.parse(mixText.replace(/&quot;/g, '"'));
// Récupère les matériaux sous forme JSON depuis le thymeleaf et le valide.
const materialsText = "[[${materialsJson}]]";
const materials = JSON.parse(materialsText.replace(/&quot;/g, '"').replace(/&#39;/g, '\'').replace(/\r\n/g, ""));
// Ajoute les produits déjà présents dans la recette
mix.mixQuantities.forEach((q) => {
let select = document.createElement("select");
select.name = `product_${productNbr}`;
materials.forEach(p => {
const materialID = p.materialID;
let option = document.createElement("option");
option.value = materialID;
option.selected = q.material.materialID === materialID;
// Rajoute le préfixe du type de produit si le type de produit n'est pas <<Aucun>>
option.innerHTML = (p.materialType.materialTypeName.toLowerCase() !== 'aucun' ? `[${p.materialType.prefix}] ` : "") + p.materialCode;
select.appendChild(option);
});
let input = document.createElement("input");
input.type = "number";
input.name = `quantity_${productNbr}`;
input.value = q.quantity;
input.step = 0.01;
let product = document.createElement("td");
product.appendChild(select);
let quantity = document.createElement("td");
quantity.appendChild(input);
let removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.dataset.remove = productNbr;
removeButton.innerHTML = "Retirer";
removeButton.onclick = removeRow(removeButton);
let removeColumn = document.createElement("td");
removeColumn.appendChild(removeButton);
let row = document.createElement("tr");
row.id = `row_${productNbr}`;
row.appendChild(product);
row.appendChild(quantity);
row.appendChild(removeColumn);
document.querySelector("#products tbody").appendChild(row);
productNbr++;
});
document.querySelector("#products button").addEventListener("click", () => {
let select = document.createElement("select");
select.name = `product_${productNbr}`;
materials.forEach(p => {
let option = document.createElement("option");
option.value = p.materialID;
// Rajoute le préfixe du type de produit si le type de produit n'est pas <<Aucun>>
option.innerHTML = (p.materialType.materialTypeName.toLowerCase() !== 'aucun' ? `[${p.materialType.prefix}] ` : "") + p.materialCode;
select.appendChild(option);
});
let input = document.createElement("input");
input.type = "number";
input.name = `quantity_${productNbr}`;
input.step = 0.01;
let product = document.createElement("td");
product.appendChild(select);
let quantity = document.createElement("td");
quantity.appendChild(input);
let removeButton = document.createElement("button");
removeButton.type = "button";
removeButton.dataset.remove = productNbr;
removeButton.innerHTML = "Retirer";
removeButton.onclick = removeRow(removeButton);
let removeColumn = document.createElement("td");
removeColumn.appendChild(removeButton);
let row = document.createElement("tr");
row.appendChild(product);
row.appendChild(quantity);
row.appendChild(removeButton);
document.querySelector("#products tbody").appendChild(row);
productNbr++;
});
document.querySelector("#removeMix").addEventListener("click", () => {
let errorP = document.querySelector(".error");
errorP.innerHTML = "";
const password = prompt("Quel est votre mot de passe?");
let data = {};
data.password = password;
axios.post("/password/valid", data)
.then(r => {
if (r.data) {
document.location.href = `/mix/remover/${mix.mixID}`;
} else {
errorP.innerHTML = "Votre mot de passe n'est pas valide";
}
})
.catch(e => {
errorP.innerHTML = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
console.log(e);
});
});
})();
function removeRow(e) {
e.addEventListener("click", () => {
document.querySelector(`#row_${e.dataset.remove}`).remove();
});
}
/*]]*/
</script>
</th:block>
</body>
</html>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'une recette</title>
<link href="/css/main.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1>Ajout d'une recette</h1>
<p th:text="'La recette ' + ${recipeCode} + ' à été enregistrée. Vous pouvez maintenant ajouter les ingrédients.'"></p>
<button th:onclick="'document.location.href=\'/recipe/editor/' + ${recipeID} + '\''">Continuer</button>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajout d'une recette</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1>Ajout d'une recette</h1>
<div class="form">
<form action="/recipe/creator" class="requireAuth" id="recipe-form" method="POST">
<table>
<tr>
<td><label for="recipeCode">Couleur: </label></td>
<td><input id="recipeCode" name="recipeCode" required type="text"/></td>
</tr>
<tr>
<td><label for="company">Bannière: </label></td>
<td><select id="company" name="company">
<th:block th:each="company : ${companies}">
<option th:text="${company.companyName}" th:value="${company.companyID}"></option>
</th:block>
</select></td>
</tr>
<tr>
<td><label for="recipeDescription">Courte description: </label></td>
<td><input id="recipeDescription" name="recipeDescription" required type="text"/></td>
</tr>
<tr>
<td><label for="sample">Échantillon: </label></td>
<td><input id="sample" name="sample" required type="number"/></td>
</tr>
<tr>
<td><label for="approbationDate">Date d'approbation: </label></td>
<td><input id="approbationDate" name="approbationDate" type="date"/></td>
</tr>
<tr>
<td><label for="remark">Remarque: </label></td>
<td><textarea cols="30" form="recipe-form" id="remark" name="remark" rows="10"></textarea></td>
</tr>
<tr>
<td>
<button class="returnIndex" type="button">Retour</button>
</td>
<td>
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,299 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="'Modification de ' + ${recipe.recipeCode}"></title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/forms.css" rel="stylesheet"/>
<style>
p {
display: inline;
}
.recipe table {
border: 1px solid black;
border-collapse: collapse;
}
.recipe td, .recipe th {
min-width: 100px;
text-align: center;
border: 1px solid black;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<h1 th:text="'Modification de ' + ${recipe.recipeCode}"></h1>
<button id="gotoRecipe" type="button">Voir</button>
<br/>
<br/>
<div class="form">
<form action="/recipe/editor" class="requireAuth" method="POST">
<input id="recipeID" name="recipeID" th:value="${recipe.recipeID}" type="hidden"/>
<input name="recipeCode" th:value="${recipe.recipeCode}" type="hidden"/>
<table class="mainTable">
<tr>
<td>
<table>
<tr>
<td><label for="company">Bannière: </label></td>
<td>
<select id="company" name="company">
<th:block th:each="company : ${companies}">
<option th:selected="${recipe.company.equals(company)}"
th:text="${company.companyName}"
th:value="${company.companyID}"></option>
</th:block>
</select>
</td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><label for="recipeDescription">Description: </label></td>
<td><input id="recipeDescription" name="recipeDescription"
th:value="${recipe.recipeDescription}"
type="text"/></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><label for="sample">Échantillon: </label></td>
<td><input id="sample" name="sample" th:value="${recipe.sample}" type="number"/></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><label for="approbationDate">Date d'approbation: </label></td>
<td><input id="approbationDate" name="approbationDate"
th:value="${recipe.approbationDate}"
type="date"/></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><label for="remark">Remarque: </label></td>
<td><textarea cols="30" id="remark" name="remark" rows="10"
th:text="${recipe.remark}"></textarea>
</td>
</tr>
</table>
</td>
<td>
<table>
<th:block th:each="mix : ${mixes}">
<tr>
<td>
<p th:text="${mix.mixType.typeName} + ':'"></p>
<th:block th:if="${mix != null}">
<button class="mixEditor" th:data-mixID="${mix.mixID}" type="button">
Modifier
</button>
</th:block>
</td>
<td>
<div class="recipe">
<table style="margin-left: 50px">
<!-- Produits -->
<tr>
<th>Produit</th>
<th>Type</th>
<th>Quantité</th>
</tr>
<th:block th:each="mixQuantity : ${mix.mixQuantities}"
th:with="material = ${mixQuantity.material}">
<tr>
<td th:classappend="${material.isMixType()} ? '' : materialCode"
th:data-materialID="${material.materialID}"
th:text="${material.materialCode}"></td>
<td>
<p th:text="${material.materialType.materialTypeName}"></p>
</td>
<td th:text="${mixQuantity.quantity}"></td>
</tr>
</th:block>
</table>
</div>
</td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
</th:block>
<tr>
<td></td>
<td>
<button id="newMix" type="button">Ajouter un mélange</button>
</td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td>
Étapes
</td>
<td>
<table id="steps">
<tr>
<th>
<button id="addStep" type="button">+</button>
</th>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
<!-- Images -->
<th:block th:each="image : ${images}">
<tr>
<td class="centerTD">
<img alt="Image supprimée ou corrompue" th:src="'/images/' + ${image}" width="400px"/>
</td>
<td>
<button class="deleteImg" th:data-image="${image}" type="button">Supprimer</button>
</td>
</tr>
</th:block>
<tr>
<td>
<button id="addImg" th:data-recipeID="${recipe.recipeID}" type="button">Ajouter une image
</button>
</td>
</tr>
<tr class="mainTableEndButtons">
<td class="centerTd">
<button class="returnIndex" type="button">Retour</button>
</td>
<td class="centerTd">
<button type="submit">Enregistrer</button>
</td>
</tr>
</table>
</form>
</div>
</section>
<script src="/js/main.js"></script>
<script>
/*[CDATA[*/
(() => {
const errorP = document.querySelector(".error");
document.querySelector("#gotoRecipe").addEventListener("click", () => {
window.open("/recipe/explore/" + document.querySelector("#recipeID").value, "_blank");
});
document.querySelectorAll(".mixEditor").forEach(e => {
e.addEventListener("click", () => {
const mixID = e.getAttribute("data-mixID");
document.location.href = "/mix/editor/" + mixID;
});
});
document.querySelector("#newMix").addEventListener("click", () => {
const recipeID = "[[${recipe.recipeID}]]";
document.location.href = "/mix/creator/" + recipeID;
});
document.querySelectorAll(".deleteImg").forEach(e => {
e.addEventListener("click", () => {
let data = {};
data['image'] = e.getAttribute("data-image");
data['password'] = prompt("Quel est votre mot de passe?");
errorP.innerHTML = "";
axios.post("/images/delete", data)
.then(r => {
const data = r.data;
if (data['error'] !== undefined) {
errorP.innerHTML = data['error'];
} else {
document.location.reload();
}
})
.catch(e => {
console.log(e);
errorP.innerHTML = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
});
});
});
document.querySelector("#addImg").addEventListener("click", () => {
document.location.href = "/images/add/[[${recipe.recipeID}]]";
});
let stepNbr = 0;
const recipeText = "[[${recipeJSON}]]";
const recipeJSON = JSON.parse(recipeText.replace(/&quot;/g, '"').replace(/&#39;/g, '\'').replace(/\r\n/g, ""));
// Ajoute les étapes déjà présentes dans la recette
recipeJSON.recipeSteps.forEach((s) => {
let input = document.createElement("input");
input.type = "text";
input.name = `step_${stepNbr}`;
input.value = s.stepMessage;
let column = document.createElement("td");
column.appendChild(input);
let row = document.createElement("tr");
row.appendChild(column);
document.querySelector("#steps tbody").appendChild(row);
stepNbr++;
});
document.querySelector("#addStep").addEventListener("click", () => {
let input = document.createElement("input");
input.type = "text";
input.name = `step_${stepNbr}`;
let column = document.createElement("td");
column.appendChild(input);
let row = document.createElement("tr");
row.appendChild(column);
document.querySelector("#steps tbody").appendChild(row);
stepNbr++;
});
})();
/*]]*/
</script>
</body>
</html>

View File

@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Modifier une recette</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/index.css" rel="stylesheet"/>
<style>
table {
margin: auto;
}
th {
padding-right: 50px;
}
td {
text-align: left;
}
.companyTabTitle {
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<p class="success" th:if="${recipeCode != null}"
th:text="'La recette pour la couleur ' + ${recipeCode} + ' a bien été sauvegardée.'"></p>
<h1>Modifier une recette</h1>
<th:block th:if="${!recipes.empty}">
<th:block th:each="company : ${recipes.keySet()}">
<h2 class="companyTabTitle" th:data-companyName="${company.companyName}"
th:text="${company.companyName}"></h2>
<th:block th:if="${!recipes.get(company).empty}">
<table style="display:none" th:id="'recipes_' + ${company.companyName}">
<tr>
<th>Couleur</th>
<th>Description</th>
<th>Échantillon</th>
</tr>
<th:block th:each="recipe : ${recipes.get(company)}">
<tr class="recipeRow" th:data-approbationDate="${recipe.approbationDate}">
<td th:text="${recipe.recipeCode}"></td>
<td class="centerCell recipeDescription" th:text="${recipe.recipeDescription}"></td>
<td class="centerCell" th:text="${recipe.sample}"></td>
<td>
<button class="editRecipe" th:data-code="${recipe.recipeCode}"
th:data-recipeID="${recipe.recipeID}" type="button">
Modifier
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
</th:block>
</th:block>
<th:block th:if="${recipes.empty}">
<b class="error">Aucune bannière n'a été trouvée.</b>
</th:block>
</section>
<script src="/js/main.js"></script>
<script>
(() => {
document.querySelectorAll(".editRecipe").forEach((e) => {
e.addEventListener("click", () => {
const recipeID = e.getAttribute("data-recipeID");
document.location.href = "/recipe/editor/" + recipeID;
});
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,373 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:text="'Couleur ' + ${recipe.recipeCode}"></title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/index.css" rel="stylesheet"/>
<style>
td, th {
width: 200px;
}
table {
margin: auto;
text-align: left;
}
section {
text-align: center;
}
p {
display: inline;
}
.mixes {
margin: 0;
border: 1px solid black;
border-collapse: collapse;
}
.mixes td, .mixes th {
border: 1px solid black;
min-width: 100px;
}
.recipeLocation {
width: 30px;
}
.notEnough td {
background-color: #ffb3b3;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<p class="success" th:text="${success}"></p>
<h1 th:text="'Couleur ' + ${recipe.recipeCode}"></h1>
<button id="modifyRecipe" type="button">Modifier</button>
<button id="useSubmit" type="button">Utiliser</button>
<br/>
<select id="unitsSelect" onchange="changeUnits(this, '.inventoryQuantity', '.inventoryQuantityUnits')">
<option selected value="mL">Millilitres</option>
<option value="L">Litres</option>
<option value="gal">Gallons</option>
</select>
<input id="recipeID" name="recipeID" th:value="${recipe.recipeID}" type="hidden"/>
<table class="mainTable">
<tr>
<td>
<table>
<tr>
<td><b>Couleur: </b></td>
<td th:text="${recipe.recipeCode}"></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><b>Bannière: </b></td>
<td th:text="${recipe.company.companyName}"></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><b>Description: </b></td>
<td th:text="${recipe.recipeDescription}"></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><b>Échantillon: </b></td>
<td th:text="${recipe.sample}"></td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><b>Date d'approbation: </b></td>
<th:block th:if="${recipe.approbationDate != ''}">
<td th:text="${recipe.approbationDate}"></td>
</th:block>
<th:block th:if="${recipe.approbationDate == ''}">
<td>Non approuvée</td>
</th:block>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><b>Remarque: </b></td>
<td th:text="${recipe.remark}">
</td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><b>Note: </b></td>
<td>
<textarea cols="30" id="note" name="note" rows="10" th:text="${recipe.note}"></textarea>
</td>
</tr>
</table>
</td>
<td>
<table style="border-spacing: 20px;">
<th:block th:each="mix : ${mixes}">
<tr>
<td><b th:text="${mix.mixType.typeName} + ':'"></b><br><br>
<label for="location"> Position: </label><input class="recipeLocation" id="location"
name="location"
th:data-mixID="${mix.mixID}"
th:value="${mix.location}"
type="text"/></td>
<td>
<table class="mixes" th:id="'recipe-' + ${mix.mixID}">
<tr>
<th>Produit</th>
<th>Type</th>
<th>Quantité</th>
<!-- Unités -->
<td></td>
<!-- Changement des quantités -->
<td></td>
</tr>
<!-- Produits -->
<th:block th:each="mixQuantity : ${mix.mixQuantities}"
th:with="material = ${mixQuantity.material}">
<tr th:id="'material-' + ${material.materialID}">
<td th:classappend="${material.isMixType()} ? '' : materialCode"
th:data-materialID="${material.materialID}"
th:text="${material.materialCode}"></td>
<td>
<p th:text="${material.materialType.materialTypeName}"></p>
</td>
<td>
<p class="inventoryQuantity"
th:data-quantityML="${mixQuantity.quantity}"
th:text="${mixQuantity.quantity}"></p>
</td>
<td>
<p class="inventoryQuantityUnits">mL</p>
</td>
<th:block th:if="${!material.isMixType()}">
<td><input class="quantityCustomizer" min="0" step="0.01"
th:data-materialID="${material.materialID}"
th:data-mixID="${mix.mixID}"
th:value="${mixQuantity.quantity}" type="number"/></td>
</th:block>
</tr>
</th:block>
</table>
</td>
<td>
<button class="useMixSubmit" type="button">Utiliser</button>
</td>
</tr>
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
</th:block>
<!-- Étapes -->
<th:block th:if="${!recipe.recipeSteps.isEmpty()}">
<tr>
<td>
<b>Étapes: </b>
</td>
<td>
<ol>
<th:block th:each="step : ${recipe.recipeSteps}">
<li th:text="${step.stepMessage}"></li>
</th:block>
</ol>
</td>
</tr>
</th:block>
</table>
</td>
</tr>
<tr class="mainTableEndButtons">
<td>
<button id="formSubmit" type="button">Sauvegarder</button>
</td>
</tr>
<!-- Images -->
<th:block th:each="image : ${images}">
<tr>
<td colspan="2" style="text-align: center;">
<img alt="Image supprimée ou corrompue" th:src="'/images/' + ${image}" width="400px"/>
</td>
</tr>
</th:block>
</table>
</section>
<script src="/js/main.js"></script>
<script>
(() => {
document.querySelector("#modifyRecipe").addEventListener("click", () => {
const recipeID = document.querySelector("#recipeID").value;
document.location.href = `/recipe/editor/${recipeID}`;
});
document.querySelectorAll(".quantityCustomizer").forEach((e) => {
// Modifie les quantités de tous les produits
e.addEventListener("change", () => {
const value = e.valueAsNumber;
const oldValue = e.defaultValue;
const mixID = e.dataset.mixid;
document.querySelectorAll(".quantityCustomizer").forEach((elem) => {
if (elem.dataset.mixid === mixID) {
const defaultValue = elem.defaultValue;
const newValue = (defaultValue * value) / oldValue;
elem.value = Math.round(newValue * 100) / 100;
}
});
});
});
const mixes = document.querySelectorAll(".mixes");
let maxWidth = 0;
mixes.forEach((e) => {
const width = e.style.width;
if (width > maxWidth) {
maxWidth = width;
}
});
mixes.forEach((e) => {
e.style.width = maxWidth;
});
document.querySelector("#formSubmit").addEventListener("click", () => {
let formData = {};
// Identifiant de la recette
formData['recipeID'] = document.querySelector("#recipeID").value;
// Position
formData['locations'] = {};
document.querySelectorAll(".recipeLocation").forEach((e) => {
formData['locations'][e.dataset.mixid] = e.value;
});
formData['note'] = document.querySelector("#note").value;
sendPost(formData, "/recipe/explore");
});
document.querySelector("#useSubmit").addEventListener("click", () => {
let formData = {};
document.querySelectorAll(".quantityCustomizer").forEach(e => {
const materialID = e.dataset.materialid;
const mixID = e.dataset.mixid;
if (formData[mixID] === undefined) {
formData[mixID] = {};
}
formData[mixID][materialID] = e.value;
});
document.querySelectorAll(".notEnough").forEach(e => {
e.classList.remove("notEnough");
});
sendPost(formData, "/inventory/use", r => {
const splitReason = r.split("-");
const mixID = splitReason[0];
const materialID = splitReason[1];
document.querySelector(`#recipe-${mixID}`).querySelector(`#material-${materialID}`).classList.add("notEnough");
});
});
document.querySelectorAll(".useMixSubmit").forEach(e => {
e.addEventListener("click", () => {
let formData = {};
e.parentElement.parentElement.querySelectorAll(".quantityCustomizer").forEach(elem => {
const materialID = elem.getAttribute("data-materialID");
const mixID = elem.getAttribute("data-mixID");
if (formData[mixID] === undefined) {
formData[mixID] = {};
}
formData[mixID][materialID] = elem.value;
});
document.querySelectorAll(".notEnough").forEach(elem => {
elem.classList.remove("notEnough");
});
sendPost(formData, "/inventory/use", r => {
const splitReason = r.split("-");
const mixID = splitReason[0];
const materialID = splitReason[1];
document.querySelector(`#recipe-${mixID}`).querySelector(`#material-${materialID}`).classList.add("notEnough");
});
});
});
})();
function sendPost(data, url, errorCallback) {
const successP = document.querySelector(".success");
const errorP = document.querySelector(".error");
successP.innerHTML = "";
errorP.innerHTML = "";
axios.post(url, data)
.then(r => {
const data = r.data;
if (data['success'] !== undefined) {
successP.innerHTML = data['success'];
} else if (data['error'] !== undefined) {
errorP.innerHTML = data['error'];
if (typeof errorCallback !== 'undefined') {
errorCallback(data['reason']);
}
}
})
.catch(e => {
console.log(e);
errorP.innerHTML = "Une erreur est survenue lors de l'envoie des informations vers le serveur.";
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Supprimer des recettes</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/index.css" rel="stylesheet"/>
<style>
table {
margin: auto;
}
th {
padding-right: 50px;
}
td {
text-align: left;
}
</style>
</head>
<body>
<section>
<p class="error" th:text="${error}"></p>
<p class="success" th:if="${recipeCode != null}"
th:text="'La recette pour la couleur ' + ${recipeCode} + ' a bien été supprimée.'"></p>
<h1>Supprimer une recette</h1>
<form action="/recipe/remover/" class="requireAuth-remover" method="POST">
<th:block th:if="${!recipes.empty}">
<th:block th:each="company : ${recipes.keySet()}">
<h2 class="companyTabTitle" th:data-companyName="${company.companyName}"
th:text="${company.companyName}"></h2>
<th:block th:if="${!recipes.get(company).empty}">
<table style="display:none" th:id="'recipes_' + ${company.companyName}">
<tr>
<th>Couleur</th>
<th>Description</th>
<th>Échantillon</th>
</tr>
<th:block th:each="recipe : ${recipes.get(company)}">
<tr class="recipeRow" th:data-approbationDate="${recipe.approbationDate}">
<td th:text="${recipe.recipeCode}"></td>
<td class="centerCell recipeDescription" th:text="${recipe.recipeDescription}"></td>
<td class="centerCell" th:text="${recipe.sample}"></td>
<td>
<button class="remover" th:data-code="${recipe.recipeCode}"
th:data-recipeID="${recipe.recipeID}" type="button">
Supprimer
</button>
</td>
</tr>
</th:block>
</table>
</th:block>
</th:block>
</th:block>
<th:block th:if="${recipes.empty}">
<b class="error">Aucune bannière n'a été trouvée.</b>
</th:block>
</form>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Génération du PDF d'un kit de retouche</title>
<link href="/css/main.css" rel="stylesheet"/>
<link href="/css/index.css" rel="stylesheet"/>
</head>
<body>
<section>
<h1>Génération du PDF d'un kit de retouche</h1>
<form action="/touchup" method="POST">
<label for="kitName"><b>Nom du kit de retouche</b></label>
<input id="kitName" name="kitName" type="text"/>
<br/>
<input type="submit" value="Générer"/>
</form>
</section>
<script src="/js/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,16 @@
package fyloz.trial.ColorRecipesExplorer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ColorRecipesExplorerApplicationTests {
@Test
public void contextLoads() {
}
}

Some files were not shown because too many files have changed in this diff Show More