Merge branch 'standard-http-errors' into 'master'
Introduction d'erreurs HTTP standards See merge request color-recipes-explorer/backend!23
This commit is contained in:
commit
b49b7dbb80
@ -4,7 +4,6 @@ plugins {
|
||||
id("java")
|
||||
id("org.jetbrains.kotlin.jvm") version "1.4.30"
|
||||
id("org.jetbrains.dokka") version "1.4.20"
|
||||
id("com.leobia.gradle.sassjavacompiler") version "0.2.1"
|
||||
id("org.springframework.boot") version "2.3.4.RELEASE"
|
||||
id("org.jetbrains.kotlin.plugin.spring") version "1.4.30"
|
||||
id("org.jetbrains.kotlin.plugin.jpa") version "1.4.30"
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service.files;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PdfBuilder;
|
||||
import dev.fyloz.colorrecipesexplorer.utils.PdfBuilder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
@ -15,7 +14,7 @@ public class TouchUpKitService {
|
||||
private static final String TOUCH_UP_EN = "TOUCH UP KIT";
|
||||
public static final int FONT_SIZE = 42;
|
||||
|
||||
private ResourceLoader resourceLoader;
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
@Autowired
|
||||
public TouchUpKitService(ResourceLoader resourceLoader) {
|
||||
|
@ -1,8 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service.files;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.model.Recipe;
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeService;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.XlsxExporter;
|
||||
import dev.fyloz.colorrecipesexplorer.model.Recipe;
|
||||
import dev.fyloz.colorrecipesexplorer.service.RecipeService;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.XlsxExporter;
|
||||
@ -19,8 +16,8 @@ import java.util.zip.ZipOutputStream;
|
||||
@Service
|
||||
public class XlsService {
|
||||
|
||||
private RecipeService recipeService;
|
||||
private Logger logger;
|
||||
private final RecipeService recipeService;
|
||||
private final Logger logger;
|
||||
|
||||
@Autowired
|
||||
public XlsService(RecipeService recipeService, Logger logger) {
|
||||
|
@ -16,14 +16,14 @@ public class PdfBuilder {
|
||||
|
||||
private static final String PATH_FONT_ARIAL_BOLD = "classpath:fonts/arialbd.ttf";
|
||||
|
||||
private PDFont font;
|
||||
private PDDocument document = new PDDocument();
|
||||
private PDPage page = new PDPage();
|
||||
private Collection<PdfLine> lines = new ArrayList<>();
|
||||
private boolean duplicated;
|
||||
private int fontSize;
|
||||
private int fontSizeBold;
|
||||
private int lineSpacing;
|
||||
private final PDFont font;
|
||||
private final PDDocument document = new PDDocument();
|
||||
private final PDPage page = new PDPage();
|
||||
private final Collection<PdfLine> lines = new ArrayList<>();
|
||||
private final boolean duplicated;
|
||||
private final int fontSize;
|
||||
private final int fontSizeBold;
|
||||
private final int lineSpacing;
|
||||
|
||||
public PdfBuilder(ResourceLoader resourceLoader, boolean duplicated, int fontSize) throws IOException {
|
||||
this.duplicated = duplicated;
|
||||
|
@ -1,22 +1,12 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Sheet;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Table;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.DescriptionCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.SectionTitleCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.TitleCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.util.Position;
|
||||
import dev.fyloz.colorrecipesexplorer.model.Mix;
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial;
|
||||
import dev.fyloz.colorrecipesexplorer.model.Recipe;
|
||||
import dev.fyloz.colorrecipesexplorer.model.RecipeStep;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Catalog;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Sheet;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Table;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.DescriptionCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.NoteCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.SectionTitleCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.TitleCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.util.Position;
|
||||
@ -27,7 +17,7 @@ import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class XlsxExporter {
|
||||
private Logger logger;
|
||||
private final Logger logger;
|
||||
|
||||
public XlsxExporter(Logger logger) {
|
||||
this.logger = logger;
|
||||
|
@ -1,10 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.builder;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.exception.InvalidCellTypeException;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
@ -18,9 +13,9 @@ import org.apache.poi.xssf.usermodel.XSSFFont;
|
||||
|
||||
public class CellBuilder {
|
||||
|
||||
private Document document;
|
||||
private Cell cell;
|
||||
private XSSFCell xcell;
|
||||
private final Document document;
|
||||
private final Cell cell;
|
||||
private final XSSFCell xcell;
|
||||
|
||||
public CellBuilder(Document document, Cell cell, int x, int y) {
|
||||
this.document = document;
|
||||
|
@ -1,10 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.builder;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Sheet;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.exception.InvalidCellTypeException;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Sheet;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
@ -15,8 +10,8 @@ import org.apache.poi.ss.util.CellRangeAddress;
|
||||
|
||||
public class SheetBuilder {
|
||||
|
||||
private Sheet sheet;
|
||||
private Iterable<Cell> cells;
|
||||
private final Sheet sheet;
|
||||
private final Iterable<Cell> cells;
|
||||
|
||||
public SheetBuilder(Sheet sheet) {
|
||||
this.sheet = sheet;
|
||||
|
@ -1,8 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
@ -14,7 +11,7 @@ import java.util.List;
|
||||
|
||||
public class Catalog {
|
||||
|
||||
private List<String> content;
|
||||
private final List<String> content;
|
||||
|
||||
public Catalog() {
|
||||
content = new ArrayList<>();
|
||||
@ -39,7 +36,7 @@ public class Catalog {
|
||||
|
||||
private class CatalogValueCell extends Cell implements IBiggerCell, IFontCell {
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
public CatalogValueCell(String value) {
|
||||
super(CellType.STRING);
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.exception.InvalidCellTypeException;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.builder.SheetBuilder;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.exception.InvalidCellTypeException;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
@ -12,8 +11,8 @@ public class Document extends XSSFWorkbook {
|
||||
public static final int WIDTH = 8;
|
||||
private static final XSSFWorkbookType WORKBOOK_TYPE = XSSFWorkbookType.XLSX;
|
||||
|
||||
private Sheet sheet;
|
||||
private Logger logger;
|
||||
private final Sheet sheet;
|
||||
private final Logger logger;
|
||||
|
||||
public Document(String name, Logger logger) {
|
||||
super(WORKBOOK_TYPE);
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCell;
|
||||
@ -11,11 +10,11 @@ import java.util.*;
|
||||
|
||||
public class Sheet {
|
||||
|
||||
private Document document;
|
||||
private XSSFSheet xsheet;
|
||||
private final Document document;
|
||||
private final XSSFSheet xsheet;
|
||||
|
||||
private Map<Integer, XSSFRow> rows;
|
||||
private List<Cell> registeredCells;
|
||||
private final Map<Integer, XSSFRow> rows;
|
||||
private final List<Cell> registeredCells;
|
||||
|
||||
Sheet(Document document, XSSFSheet xsheet) {
|
||||
this.document = document;
|
||||
|
@ -1,10 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.exception.IndexOutOfTableBoundsException;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.Cell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
@ -19,16 +14,16 @@ import java.util.*;
|
||||
|
||||
public class Table {
|
||||
|
||||
private static DecimalFormat format = new DecimalFormat("#.###");
|
||||
private static final DecimalFormat format = new DecimalFormat("#.###");
|
||||
|
||||
private int width;
|
||||
private int height;
|
||||
private int cellWidth;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final int cellWidth;
|
||||
|
||||
private Map<Position, Cell> content;
|
||||
private final Map<Position, Cell> content;
|
||||
|
||||
private List<TableSubNameCell> columns;
|
||||
private List<TableSubNameCell> rows;
|
||||
private final List<TableSubNameCell> columns;
|
||||
private final List<TableSubNameCell> rows;
|
||||
|
||||
public Table(int width, int height, String name) {
|
||||
this.width = width;
|
||||
@ -140,9 +135,9 @@ public class Table {
|
||||
|
||||
private final IndexedColors COLOR = IndexedColors.GREY_40_PERCENT;
|
||||
|
||||
private String name;
|
||||
private int width;
|
||||
private int height = 2;
|
||||
private final String name;
|
||||
private final int width;
|
||||
private final int height = 2;
|
||||
|
||||
public TableNameCell(String name, int width) {
|
||||
super(CellType.STRING);
|
||||
@ -175,8 +170,8 @@ public class Table {
|
||||
private class TableSubNameCell extends Cell implements IFontCell, IBiggerCell, IColoredCell {
|
||||
|
||||
private String name;
|
||||
private int width;
|
||||
private int height = 2;
|
||||
private final int width;
|
||||
private final int height = 2;
|
||||
|
||||
public TableSubNameCell(String name, int width) {
|
||||
super(CellType.STRING);
|
||||
@ -212,9 +207,9 @@ public class Table {
|
||||
|
||||
private class TableStringValueCell extends Cell implements IFontCell, IBiggerCell {
|
||||
|
||||
private String value;
|
||||
private int width;
|
||||
private int height = 2;
|
||||
private final String value;
|
||||
private final int width;
|
||||
private final int height = 2;
|
||||
|
||||
public TableStringValueCell(String value, int width) {
|
||||
super(CellType.STRING);
|
||||
@ -246,9 +241,9 @@ public class Table {
|
||||
|
||||
private class TableNumValueCell extends Cell implements IFontCell, IBiggerCell {
|
||||
|
||||
private double value;
|
||||
private int width;
|
||||
private int height = 2;
|
||||
private final double value;
|
||||
private final int width;
|
||||
private final int height = 2;
|
||||
|
||||
public TableNumValueCell(double value, int width) {
|
||||
super(CellType.NUMERIC);
|
||||
|
@ -1,8 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component.cells;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
@ -17,7 +14,7 @@ public class DescriptionCell extends Cell implements IColoredCell, IBiggerCell,
|
||||
private static final IndexedColors BG_COLOR_NAME = IndexedColors.GREY_40_PERCENT;
|
||||
private static final short FONT_HEIGHT = 18;
|
||||
|
||||
private DescriptionCellType type;
|
||||
private final DescriptionCellType type;
|
||||
private String valueStr;
|
||||
private double valueNbr;
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component.cells;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
@ -9,7 +7,7 @@ import org.apache.poi.ss.usermodel.CellType;
|
||||
|
||||
public class NoteCell extends Cell implements IBiggerCell, IFontCell {
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
public NoteCell(String value) {
|
||||
super(CellType.STRING);
|
||||
|
@ -1,8 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component.cells;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
@ -19,7 +16,7 @@ public class SectionTitleCell extends Cell implements IColoredCell, IBiggerCell,
|
||||
private static final IndexedColors FONT_COLOR = IndexedColors.WHITE;
|
||||
private static final int FONT_HEIGHT = 24;
|
||||
|
||||
private String title;
|
||||
private final String title;
|
||||
|
||||
public SectionTitleCell(String title) {
|
||||
super(CellType.STRING);
|
||||
|
@ -1,8 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.component.cells;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IFontCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Document;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IBiggerCell;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.cells.styles.IColoredCell;
|
||||
@ -18,7 +15,7 @@ public class TitleCell extends Cell implements IColoredCell, IBiggerCell, IFontC
|
||||
private static final IndexedColors FONT_COLOR = IndexedColors.WHITE;
|
||||
private static IndexedColors BACKGROUND_COLOR = IndexedColors.BLACK;
|
||||
|
||||
private String title;
|
||||
private final String title;
|
||||
|
||||
public TitleCell(String title) {
|
||||
super(TYPE);
|
||||
|
@ -1,6 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.xlsx.util;
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Sheet;
|
||||
import dev.fyloz.colorrecipesexplorer.xlsx.component.Sheet;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
@ -28,11 +27,7 @@ public class CellUtils {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cell.getCellType() == CellType.STRING && cell.getStringCellValue().trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return cell.getCellType() == CellType.STRING && cell.getStringCellValue().trim().isEmpty();
|
||||
}
|
||||
|
||||
public static boolean isCellInMergedCell(Sheet sheet, int x, int y) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.fyloz.colorrecipesexplorer
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.CreProperties
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication
|
||||
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
@ -9,9 +9,9 @@ import org.springframework.boot.runApplication
|
||||
|
||||
@SpringBootApplication(exclude = [LiquibaseAutoConfiguration::class])
|
||||
@EnableConfigurationProperties(
|
||||
MaterialTypeProperties::class,
|
||||
CreProperties::class,
|
||||
DatabaseUpdaterProperties::class
|
||||
MaterialTypeProperties::class,
|
||||
CreProperties::class,
|
||||
DatabaseUpdaterProperties::class
|
||||
)
|
||||
class ColorRecipesExplorerApplication
|
||||
|
||||
|
@ -20,18 +20,18 @@ class DataSourceConfiguration {
|
||||
@Bean(name = ["dataSource"])
|
||||
@ConfigurationProperties(prefix = "spring.datasource")
|
||||
fun customDataSource(
|
||||
logger: Logger,
|
||||
environment: Environment,
|
||||
databaseUpdaterProperties: DatabaseUpdaterProperties
|
||||
logger: Logger,
|
||||
environment: Environment,
|
||||
databaseUpdaterProperties: DatabaseUpdaterProperties
|
||||
): DataSource {
|
||||
val databaseUrl: String = environment.getProperty("spring.datasource.url")!!
|
||||
|
||||
runDatabaseVersionCheck(logger, databaseUrl, databaseUpdaterProperties)
|
||||
|
||||
return DataSourceBuilder
|
||||
.create()
|
||||
.url(databaseUrl) // Hikari won't start without that
|
||||
.build()
|
||||
.create()
|
||||
.url(databaseUrl) // Hikari won't start without that
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,24 +75,24 @@ fun runDatabaseUpdate(logger: Logger, database: CreDatabase) {
|
||||
}
|
||||
|
||||
fun getDatabase(
|
||||
databaseUrl: String,
|
||||
databaseUpdaterProperties: DatabaseUpdaterProperties,
|
||||
logger: Logger
|
||||
databaseUrl: String,
|
||||
databaseUpdaterProperties: DatabaseUpdaterProperties,
|
||||
logger: Logger
|
||||
): CreDatabase {
|
||||
val databaseName =
|
||||
(DATABASE_NAME_REGEX.find(databaseUrl) ?: throw DatabaseVersioningException.InvalidUrl(databaseUrl)).value
|
||||
(DATABASE_NAME_REGEX.find(databaseUrl) ?: throw DatabaseVersioningException.InvalidUrl(databaseUrl)).value
|
||||
|
||||
return CreDatabase(
|
||||
databaseContext(
|
||||
properties = databaseUpdaterProperties(
|
||||
targetVersion = SUPPORTED_DATABASE_VERSION,
|
||||
url = databaseUrl.removeSuffix(databaseName),
|
||||
dbName = databaseName,
|
||||
username = databaseUpdaterProperties.username,
|
||||
password = databaseUpdaterProperties.password
|
||||
),
|
||||
logger
|
||||
)
|
||||
databaseContext(
|
||||
properties = databaseUpdaterProperties(
|
||||
targetVersion = SUPPORTED_DATABASE_VERSION,
|
||||
url = databaseUrl.removeSuffix(databaseName),
|
||||
dbName = databaseName,
|
||||
username = databaseUpdaterProperties.username,
|
||||
password = databaseUpdaterProperties.password
|
||||
),
|
||||
logger
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ fun throwUnsupportedDatabaseVersion(version: Int, logger: Logger) {
|
||||
logger.error("Version $version of the database is not supported; Only version $SUPPORTED_DATABASE_VERSION is currently supported; Update this application to use the database.")
|
||||
} else {
|
||||
logger.error(
|
||||
"""Version $version of the database is not supported; Only version $SUPPORTED_DATABASE_VERSION is currently supported.
|
||||
"""Version $version of the database is not supported; Only version $SUPPORTED_DATABASE_VERSION is currently supported.
|
||||
|You can update the database to the supported version by either:
|
||||
| - Setting the environment variable '$ENV_VAR_ENABLE_DATABASE_UPDATE_NAME' to '1' to update the database automatically
|
||||
| - Updating the database manually with the database manager utility (https://git.fyloz.dev/color-recipes-explorer/database-manager)
|
||||
@ -122,5 +122,5 @@ class DatabaseUpdaterProperties {
|
||||
sealed class DatabaseVersioningException(message: String) : Exception(message) {
|
||||
class InvalidUrl(url: String) : DatabaseVersioningException("Invalid database url: $url")
|
||||
class UnsupportedDatabaseVersion(version: Int) :
|
||||
DatabaseVersioningException("Unsupported database version: $version; Only version $SUPPORTED_DATABASE_VERSION is currently supported")
|
||||
DatabaseVersioningException("Unsupported database version: $version; Only version $SUPPORTED_DATABASE_VERSION is currently supported")
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import org.springframework.core.annotation.Order
|
||||
@Configuration
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
class InitialDataLoader(
|
||||
private val materialTypeService: MaterialTypeService,
|
||||
private val materialTypeProperties: MaterialTypeProperties
|
||||
private val materialTypeService: MaterialTypeService,
|
||||
private val materialTypeProperties: MaterialTypeProperties
|
||||
) : ApplicationListener<ApplicationReadyEvent> {
|
||||
override fun onApplicationEvent(event: ApplicationReadyEvent) =
|
||||
materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes)
|
||||
materialTypeService.saveSystemTypes(materialTypeProperties.systemTypes)
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package dev.fyloz.colorrecipesexplorer.config
|
||||
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.Employee
|
||||
import dev.fyloz.colorrecipesexplorer.model.EmployeeLoginRequest
|
||||
import dev.fyloz.colorrecipesexplorer.model.EmployeePermission
|
||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeService
|
||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeServiceImpl
|
||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsService
|
||||
import dev.fyloz.colorrecipesexplorer.service.EmployeeUserDetailsServiceImpl
|
||||
import io.jsonwebtoken.ExpiredJwtException
|
||||
import io.jsonwebtoken.Jwts
|
||||
@ -51,11 +52,11 @@ import javax.servlet.http.HttpServletResponse
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
@EnableConfigurationProperties(SecurityConfigurationProperties::class)
|
||||
class WebSecurityConfig(
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
@Lazy val userDetailsService: EmployeeUserDetailsServiceImpl,
|
||||
@Lazy val employeeService: EmployeeServiceImpl,
|
||||
val environment: Environment,
|
||||
val logger: Logger
|
||||
val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
@Lazy val userDetailsService: EmployeeUserDetailsServiceImpl,
|
||||
@Lazy val employeeService: EmployeeServiceImpl,
|
||||
val environment: Environment,
|
||||
val logger: Logger
|
||||
) : WebSecurityConfigurerAdapter() {
|
||||
var debugMode = false
|
||||
|
||||
@ -65,36 +66,36 @@ class WebSecurityConfig(
|
||||
|
||||
@Bean
|
||||
fun passwordEncoder() =
|
||||
BCryptPasswordEncoder()
|
||||
BCryptPasswordEncoder()
|
||||
|
||||
@Bean
|
||||
override fun authenticationManagerBean(): AuthenticationManager =
|
||||
super.authenticationManagerBean()
|
||||
super.authenticationManagerBean()
|
||||
|
||||
@Bean
|
||||
fun corsConfigurationSource() =
|
||||
UrlBasedCorsConfigurationSource().apply {
|
||||
registerCorsConfiguration("/**", CorsConfiguration().apply {
|
||||
allowedOrigins = listOf("http://localhost:4200") // Angular development server
|
||||
allowedMethods = listOf(
|
||||
HttpMethod.GET.name,
|
||||
HttpMethod.POST.name,
|
||||
HttpMethod.PUT.name,
|
||||
HttpMethod.DELETE.name,
|
||||
HttpMethod.OPTIONS.name,
|
||||
HttpMethod.HEAD.name
|
||||
)
|
||||
allowCredentials = true
|
||||
}.applyPermitDefaultValues())
|
||||
}
|
||||
UrlBasedCorsConfigurationSource().apply {
|
||||
registerCorsConfiguration("/**", CorsConfiguration().apply {
|
||||
allowedOrigins = listOf("http://localhost:4200") // Angular development server
|
||||
allowedMethods = listOf(
|
||||
HttpMethod.GET.name,
|
||||
HttpMethod.POST.name,
|
||||
HttpMethod.PUT.name,
|
||||
HttpMethod.DELETE.name,
|
||||
HttpMethod.OPTIONS.name,
|
||||
HttpMethod.HEAD.name
|
||||
)
|
||||
allowCredentials = true
|
||||
}.applyPermitDefaultValues())
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
fun initWebSecurity() {
|
||||
fun createUser(
|
||||
credentials: SecurityConfigurationProperties.SystemUserCredentials?,
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
permissions: List<EmployeePermission>
|
||||
credentials: SecurityConfigurationProperties.SystemUserCredentials?,
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
permissions: List<EmployeePermission>
|
||||
) {
|
||||
Assert.notNull(credentials, "No root user has been defined.")
|
||||
credentials!!
|
||||
@ -102,14 +103,14 @@ class WebSecurityConfig(
|
||||
Assert.notNull(credentials.password, "The root user has no password defined.")
|
||||
if (!employeeService.existsById(credentials.id!!)) {
|
||||
employeeService.save(
|
||||
Employee(
|
||||
id = credentials.id!!,
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
password = passwordEncoder().encode(credentials.password!!),
|
||||
isSystemUser = true,
|
||||
permissions = permissions.toMutableSet()
|
||||
)
|
||||
Employee(
|
||||
id = credentials.id!!,
|
||||
firstName = firstName,
|
||||
lastName = lastName,
|
||||
password = passwordEncoder().encode(credentials.password!!),
|
||||
isSystemUser = true,
|
||||
permissions = permissions.toMutableSet()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -121,37 +122,37 @@ class WebSecurityConfig(
|
||||
|
||||
override fun configure(http: HttpSecurity) {
|
||||
http
|
||||
.headers().frameOptions().disable()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.addFilter(
|
||||
JwtAuthenticationFilter(
|
||||
authenticationManager(),
|
||||
employeeService,
|
||||
securityConfigurationProperties
|
||||
)
|
||||
.headers().frameOptions().disable()
|
||||
.and()
|
||||
.csrf().disable()
|
||||
.addFilter(
|
||||
JwtAuthenticationFilter(
|
||||
authenticationManager(),
|
||||
employeeService,
|
||||
securityConfigurationProperties
|
||||
)
|
||||
.addFilter(
|
||||
JwtAuthorizationFilter(
|
||||
userDetailsService,
|
||||
securityConfigurationProperties,
|
||||
authenticationManager()
|
||||
)
|
||||
)
|
||||
.addFilter(
|
||||
JwtAuthorizationFilter(
|
||||
userDetailsService,
|
||||
securityConfigurationProperties,
|
||||
authenticationManager()
|
||||
)
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
|
||||
if (!debugMode) {
|
||||
http.authorizeRequests()
|
||||
.antMatchers("/api/login").permitAll()
|
||||
.antMatchers("/api/logout").authenticated()
|
||||
.antMatchers("/api/employee/current").authenticated()
|
||||
.anyRequest().authenticated()
|
||||
.antMatchers("/api/login").permitAll()
|
||||
.antMatchers("/api/logout").authenticated()
|
||||
.antMatchers("/api/employee/current").authenticated()
|
||||
.anyRequest().authenticated()
|
||||
} else {
|
||||
http
|
||||
.cors()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("**").permitAll()
|
||||
.cors()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("**").permitAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,9 +160,9 @@ class WebSecurityConfig(
|
||||
@Component
|
||||
class RestAuthenticationEntryPoint : AuthenticationEntryPoint {
|
||||
override fun commence(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
authException: AuthenticationException
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
authException: AuthenticationException
|
||||
) = response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized")
|
||||
}
|
||||
|
||||
@ -170,9 +171,9 @@ const val defaultGroupCookieName = "Default-Group"
|
||||
val blacklistedJwtTokens = mutableListOf<String>()
|
||||
|
||||
class JwtAuthenticationFilter(
|
||||
private val authManager: AuthenticationManager,
|
||||
private val employeeService: EmployeeService,
|
||||
private val securityConfigurationProperties: SecurityConfigurationProperties
|
||||
private val authManager: AuthenticationManager,
|
||||
private val employeeService: EmployeeService,
|
||||
private val securityConfigurationProperties: SecurityConfigurationProperties
|
||||
) : UsernamePasswordAuthenticationFilter() {
|
||||
private var debugMode = false
|
||||
|
||||
@ -187,10 +188,10 @@ class JwtAuthenticationFilter(
|
||||
}
|
||||
|
||||
override fun successfulAuthentication(
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
chain: FilterChain,
|
||||
authResult: Authentication
|
||||
request: HttpServletRequest,
|
||||
response: HttpServletResponse,
|
||||
chain: FilterChain,
|
||||
authResult: Authentication
|
||||
) {
|
||||
val jwtSecret = securityConfigurationProperties.jwtSecret
|
||||
val jwtDuration = securityConfigurationProperties.jwtDuration
|
||||
@ -201,17 +202,17 @@ class JwtAuthenticationFilter(
|
||||
val expirationMs = System.currentTimeMillis() + jwtDuration!!
|
||||
val expirationDate = Date(expirationMs)
|
||||
val token = Jwts.builder()
|
||||
.setSubject(employeeId)
|
||||
.setExpiration(expirationDate)
|
||||
.signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray())
|
||||
.compact()
|
||||
.setSubject(employeeId)
|
||||
.setExpiration(expirationDate)
|
||||
.signWith(SignatureAlgorithm.HS512, jwtSecret!!.toByteArray())
|
||||
.compact()
|
||||
response.addHeader("Access-Control-Expose-Headers", "X-Authentication-Expiration")
|
||||
var bearerCookie =
|
||||
"$authorizationCookieName=Bearer$token; Max-Age=${jwtDuration / 1000}; HttpOnly; SameSite=strict"
|
||||
"$authorizationCookieName=Bearer$token; Max-Age=${jwtDuration / 1000}; HttpOnly; SameSite=strict"
|
||||
if (!debugMode) bearerCookie += "; Secure;"
|
||||
response.addHeader(
|
||||
"Set-Cookie",
|
||||
bearerCookie
|
||||
"Set-Cookie",
|
||||
bearerCookie
|
||||
)
|
||||
response.addHeader(authorizationCookieName, "Bearer $token")
|
||||
response.addHeader("X-Authentication-Expiration", "$expirationMs")
|
||||
@ -219,9 +220,9 @@ class JwtAuthenticationFilter(
|
||||
}
|
||||
|
||||
class JwtAuthorizationFilter(
|
||||
private val userDetailsService: EmployeeUserDetailsServiceImpl,
|
||||
private val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
authenticationManager: AuthenticationManager
|
||||
private val userDetailsService: EmployeeUserDetailsService,
|
||||
private val securityConfigurationProperties: SecurityConfigurationProperties,
|
||||
authenticationManager: AuthenticationManager
|
||||
) : BasicAuthenticationFilter(authenticationManager) {
|
||||
override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain) {
|
||||
fun tryLoginFromBearer(): Boolean {
|
||||
@ -259,10 +260,10 @@ class JwtAuthorizationFilter(
|
||||
Assert.notNull(jwtSecret, "No JWT secret has been defined.")
|
||||
return try {
|
||||
val employeeId = Jwts.parser()
|
||||
.setSigningKey(jwtSecret!!.toByteArray())
|
||||
.parseClaimsJws(token.replace("Bearer", ""))
|
||||
.body
|
||||
.subject
|
||||
.setSigningKey(jwtSecret!!.toByteArray())
|
||||
.parseClaimsJws(token.replace("Bearer", ""))
|
||||
.body
|
||||
.subject
|
||||
if (employeeId != null) getAuthenticationToken(employeeId) else null
|
||||
} catch (_: ExpiredJwtException) {
|
||||
null
|
||||
@ -272,7 +273,7 @@ class JwtAuthorizationFilter(
|
||||
private fun getAuthenticationToken(employeeId: String): UsernamePasswordAuthenticationToken? = try {
|
||||
val employeeDetails = userDetailsService.loadUserByEmployeeId(employeeId.toLong(), false)
|
||||
UsernamePasswordAuthenticationToken(employeeDetails.username, null, employeeDetails.authorities)
|
||||
} catch (_: EntityNotFoundException) {
|
||||
} catch (_: NotFoundException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ class MaterialTypeProperties {
|
||||
var baseName: String = ""
|
||||
|
||||
data class MaterialTypeProperty(
|
||||
var name: String = "",
|
||||
var prefix: String = "",
|
||||
var usePercentages: Boolean = false
|
||||
var name: String = "",
|
||||
var prefix: String = "",
|
||||
var usePercentages: Boolean = false
|
||||
) {
|
||||
fun toMaterialType(): MaterialType {
|
||||
Assert.hasText(name, "A system material type has an empty name")
|
||||
|
@ -1,9 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.exception
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto
|
||||
import org.springframework.context.annotation.Profile
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
@ -11,91 +7,91 @@ import org.springframework.validation.FieldError
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.context.request.ServletWebRequest
|
||||
import org.springframework.web.context.request.WebRequest
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
|
||||
|
||||
abstract class RestException(val exceptionMessage: String, val httpStatus: HttpStatus) :
|
||||
RuntimeException(exceptionMessage) {
|
||||
abstract fun buildBody(): RestExceptionBody
|
||||
abstract class RestException(
|
||||
val errorCode: String,
|
||||
val title: String,
|
||||
val status: HttpStatus,
|
||||
val details: String,
|
||||
val extensions: Map<String, Any> = mapOf()
|
||||
) : RuntimeException(details) {
|
||||
fun buildExceptionBody() = mapOf(
|
||||
"type" to errorCode,
|
||||
"title" to title,
|
||||
"status" to status.value(),
|
||||
"detail" to details,
|
||||
|
||||
@Suppress("unused")
|
||||
open inner class RestExceptionBody(
|
||||
val status: Int = httpStatus.value(),
|
||||
@JsonProperty("message") val message: String = exceptionMessage
|
||||
*extensions.map { it.key to it.value }.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
class EntityAlreadyExistsException(val value: Any) :
|
||||
RestException("An entity with the given identifier already exists", HttpStatus.CONFLICT) {
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
}
|
||||
class NotFoundException(
|
||||
errorCode: String,
|
||||
title: String,
|
||||
details: String,
|
||||
identifierValue: Any,
|
||||
identifierName: String = "id"
|
||||
) : RestException(
|
||||
errorCode = "notfound-$errorCode-$identifierName",
|
||||
title = title,
|
||||
status = HttpStatus.NOT_FOUND,
|
||||
details = details,
|
||||
extensions = mapOf(
|
||||
identifierName to identifierValue
|
||||
)
|
||||
)
|
||||
|
||||
class EntityNotFoundException(val value: Any) :
|
||||
RestException("An entity could not be found with the given identifier", HttpStatus.NOT_FOUND) {
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
}
|
||||
class AlreadyExistsException(
|
||||
errorCode: String,
|
||||
title: String,
|
||||
details: String,
|
||||
identifierValue: Any,
|
||||
identifierName: String = "id"
|
||||
) : RestException(
|
||||
errorCode = "exists-$errorCode-$identifierName",
|
||||
title = title,
|
||||
status = HttpStatus.CONFLICT,
|
||||
details = details,
|
||||
extensions = mapOf(
|
||||
identifierName to identifierValue
|
||||
)
|
||||
)
|
||||
|
||||
class CannotDeleteEntityException(val value: Long) :
|
||||
RestException(
|
||||
"The entity with the given identifier could not be deleted because it is required by other entities",
|
||||
HttpStatus.CONFLICT
|
||||
) {
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val id = value
|
||||
}
|
||||
}
|
||||
|
||||
class SimdutWriteException(val material: Material) :
|
||||
RestException(
|
||||
"Could not write the SIMDUT file to disk",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR
|
||||
) {
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = RestExceptionBody()
|
||||
}
|
||||
|
||||
class LowQuantityException(val materialQuantity: MaterialQuantityDto) :
|
||||
RestException(
|
||||
"There is not enough of the given material in the inventory",
|
||||
HttpStatus.CONFLICT
|
||||
) {
|
||||
@Suppress("unused")
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val material = materialQuantity.material
|
||||
val quantity = materialQuantity.quantity
|
||||
}
|
||||
}
|
||||
|
||||
class LowQuantitiesException(val materialQuantities: Collection<MaterialQuantityDto>) :
|
||||
RestException(
|
||||
"There is not enough of one or more given materials in the inventory",
|
||||
HttpStatus.CONFLICT
|
||||
) {
|
||||
@Suppress
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {
|
||||
val lowQuantities = materialQuantities
|
||||
}
|
||||
}
|
||||
class CannotDeleteException(
|
||||
errorCode: String,
|
||||
title: String,
|
||||
details: String
|
||||
) : RestException(
|
||||
errorCode = "cannotdelete-$errorCode",
|
||||
title = title,
|
||||
status = HttpStatus.CONFLICT,
|
||||
details = details
|
||||
)
|
||||
|
||||
@ControllerAdvice
|
||||
class RestResponseEntityExceptionHandler : ResponseEntityExceptionHandler() {
|
||||
@ExceptionHandler(RestException::class)
|
||||
fun handleRestExceptions(exception: RestException, request: WebRequest): ResponseEntity<Any> {
|
||||
return handleExceptionInternal(exception, exception.buildBody(), HttpHeaders(), exception.httpStatus, request)
|
||||
val finalBody = exception.buildExceptionBody().toMutableMap()
|
||||
finalBody["instance"] = (request as ServletWebRequest).request.requestURI
|
||||
|
||||
return handleExceptionInternal(
|
||||
exception,
|
||||
finalBody,
|
||||
HttpHeaders(),
|
||||
exception.status,
|
||||
request
|
||||
)
|
||||
}
|
||||
|
||||
override fun handleMethodArgumentNotValid(
|
||||
ex: MethodArgumentNotValidException,
|
||||
headers: HttpHeaders,
|
||||
status: HttpStatus,
|
||||
request: WebRequest
|
||||
ex: MethodArgumentNotValidException,
|
||||
headers: HttpHeaders,
|
||||
status: HttpStatus,
|
||||
request: WebRequest
|
||||
): ResponseEntity<Any> {
|
||||
val errors = hashMapOf<String, String>()
|
||||
ex.bindingResult.allErrors.forEach {
|
||||
|
@ -1,331 +0,0 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
import javax.validation.constraints.Size
|
||||
|
||||
|
||||
private const val EMPLOYEE_ID_NULL_MESSAGE = "Un numéro d'employé est requis"
|
||||
private const val EMPLOYEE_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis"
|
||||
private const val EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis"
|
||||
private const val EMPLOYEE_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis"
|
||||
private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères"
|
||||
|
||||
@Entity
|
||||
@Table(name = "employee")
|
||||
data class Employee(
|
||||
@Id
|
||||
override val id: Long,
|
||||
|
||||
@Column(name = "first_name")
|
||||
val firstName: String = "",
|
||||
|
||||
@Column(name = "last_name")
|
||||
val lastName: String = "",
|
||||
|
||||
@JsonIgnore
|
||||
val password: String = "",
|
||||
|
||||
@JsonIgnore
|
||||
@Column(name = "default_group_user")
|
||||
val isDefaultGroupUser: Boolean = false,
|
||||
|
||||
@JsonIgnore
|
||||
@Column(name = "system_user")
|
||||
val isSystemUser: Boolean = false,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
@Fetch(FetchMode.SELECT)
|
||||
var group: EmployeeGroup? = null,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")])
|
||||
@Column(name = "permission")
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
@get:JsonProperty("explicitPermissions")
|
||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
|
||||
@Column(name = "last_login_time")
|
||||
var lastLoginTime: LocalDateTime? = null
|
||||
) : Model {
|
||||
@get:JsonProperty("permissions")
|
||||
val flatPermissions: Set<EmployeePermission>
|
||||
get() = permissions
|
||||
.flatMap { it.flat() }
|
||||
.filter { !it.deprecated }
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
if (group != null) this.addAll(group!!.flatPermissions)
|
||||
}
|
||||
|
||||
@get:JsonIgnore
|
||||
val authorities: Set<GrantedAuthority>
|
||||
get() = flatPermissions.map { it.toAuthority() }.toMutableSet()
|
||||
}
|
||||
|
||||
/** DTO for creating employees. Allows a [password] a [groupId]. */
|
||||
open class EmployeeSaveDto(
|
||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
||||
val firstName: String,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
||||
val lastName: String,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE)
|
||||
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
|
||||
val password: String,
|
||||
|
||||
val groupId: Long?,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
|
||||
) : EntityDto<Employee>
|
||||
|
||||
open class EmployeeUpdateDto(
|
||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
||||
val firstName: String?,
|
||||
|
||||
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
||||
val lastName: String?,
|
||||
|
||||
val groupId: Long?,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
val permissions: Set<EmployeePermission>?
|
||||
) : EntityDto<Employee>
|
||||
|
||||
|
||||
private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis"
|
||||
private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis"
|
||||
private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise"
|
||||
|
||||
@Entity
|
||||
@Table(name = "employee_group")
|
||||
data class EmployeeGroup(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override var id: Long? = null,
|
||||
|
||||
@Column(unique = true)
|
||||
override val name: String = "",
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
|
||||
@Column(name = "permission")
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
@get:JsonProperty("explicitPermissions")
|
||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
) : NamedModel {
|
||||
@get:JsonProperty("permissions")
|
||||
val flatPermissions: Set<EmployeePermission>
|
||||
get() = this.permissions
|
||||
.flatMap { it.flat() }
|
||||
.filter { !it.deprecated }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
open class EmployeeGroupSaveDto(
|
||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
||||
@field:Size(min = 3)
|
||||
val name: String,
|
||||
|
||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
||||
val permissions: MutableSet<EmployeePermission>
|
||||
) : EntityDto<EmployeeGroup> {
|
||||
override fun toEntity(): EmployeeGroup =
|
||||
EmployeeGroup(null, name, permissions)
|
||||
}
|
||||
|
||||
open class EmployeeGroupUpdateDto(
|
||||
@field:NotNull(message = GROUP_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
||||
@field:Size(min = 3)
|
||||
val name: String,
|
||||
|
||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
||||
val permissions: MutableSet<EmployeePermission>
|
||||
) : EntityDto<EmployeeGroup> {
|
||||
override fun toEntity(): EmployeeGroup =
|
||||
EmployeeGroup(id, name, permissions)
|
||||
}
|
||||
|
||||
|
||||
data class EmployeeLoginRequest(val id: Long, val password: String)
|
||||
|
||||
|
||||
enum class EmployeePermission(
|
||||
val impliedPermissions: List<EmployeePermission> = listOf(),
|
||||
val deprecated: Boolean = false
|
||||
) {
|
||||
VIEW_RECIPES,
|
||||
VIEW_CATALOG,
|
||||
VIEW_USERS,
|
||||
|
||||
PRINT_MIXES(listOf(VIEW_RECIPES)),
|
||||
|
||||
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
|
||||
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)),
|
||||
EDIT_MATERIALS(listOf(VIEW_CATALOG)),
|
||||
EDIT_MATERIAL_TYPES(listOf(VIEW_CATALOG)),
|
||||
EDIT_COMPANIES(listOf(VIEW_CATALOG)),
|
||||
EDIT_USERS(listOf(VIEW_USERS)),
|
||||
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
|
||||
|
||||
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
|
||||
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
|
||||
|
||||
REMOVE_RECIPES(listOf(EDIT_RECIPES)),
|
||||
REMOVE_MATERIALS(listOf(EDIT_MATERIALS)),
|
||||
REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)),
|
||||
REMOVE_COMPANIES(listOf(EDIT_COMPANIES)),
|
||||
REMOVE_USERS(listOf(EDIT_USERS)),
|
||||
REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)),
|
||||
|
||||
ADMIN(
|
||||
listOf(
|
||||
EDIT_CATALOG,
|
||||
|
||||
REMOVE_RECIPES,
|
||||
REMOVE_USERS,
|
||||
REMOVE_CATALOG,
|
||||
|
||||
PRINT_MIXES,
|
||||
ADD_TO_INVENTORY,
|
||||
DEDUCT_FROM_INVENTORY
|
||||
)
|
||||
),
|
||||
|
||||
// deprecated permissions
|
||||
VIEW_RECIPE(listOf(VIEW_RECIPES), true),
|
||||
VIEW_MATERIAL(listOf(VIEW_CATALOG), true),
|
||||
VIEW_MATERIAL_TYPE(listOf(VIEW_CATALOG), true),
|
||||
VIEW_COMPANY(listOf(VIEW_CATALOG), true),
|
||||
VIEW(listOf(VIEW_RECIPES, VIEW_CATALOG), true),
|
||||
VIEW_EMPLOYEE(listOf(VIEW_USERS), true),
|
||||
VIEW_EMPLOYEE_GROUP(listOf(VIEW_USERS), true),
|
||||
|
||||
EDIT_RECIPE(listOf(EDIT_RECIPES), true),
|
||||
EDIT_MATERIAL(listOf(EDIT_MATERIALS), true),
|
||||
EDIT_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPES), true),
|
||||
EDIT_COMPANY(listOf(EDIT_COMPANIES), true),
|
||||
EDIT(listOf(EDIT_RECIPES, EDIT_CATALOG), true),
|
||||
EDIT_EMPLOYEE(listOf(EDIT_USERS), true),
|
||||
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
|
||||
EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true),
|
||||
|
||||
REMOVE_RECIPE(listOf(REMOVE_RECIPES), true),
|
||||
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
|
||||
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
|
||||
REMOVE_COMPANY(listOf(REMOVE_COMPANIES), true),
|
||||
REMOVE(listOf(REMOVE_RECIPES, REMOVE_CATALOG), true),
|
||||
REMOVE_EMPLOYEE(listOf(REMOVE_USERS), true),
|
||||
REMOVE_EMPLOYEE_GROUP(listOf(REMOVE_USERS), true),
|
||||
|
||||
SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true),
|
||||
;
|
||||
|
||||
operator fun contains(permission: EmployeePermission): Boolean {
|
||||
return permission == this || impliedPermissions.any { permission in it }
|
||||
}
|
||||
}
|
||||
|
||||
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
|
||||
return mutableSetOf(this).apply {
|
||||
impliedPermissions.forEach {
|
||||
addAll(it.flat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */
|
||||
private fun EmployeePermission.toAuthority(): GrantedAuthority {
|
||||
return SimpleGrantedAuthority(name)
|
||||
}
|
||||
|
||||
|
||||
// ==== DSL ====
|
||||
fun employee(
|
||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
||||
id: Long = 0L,
|
||||
firstName: String = "firstName",
|
||||
lastName: String = "lastName",
|
||||
password: String = passwordEncoder.encode("password"),
|
||||
isDefaultGroupUser: Boolean = false,
|
||||
isSystemUser: Boolean = false,
|
||||
group: EmployeeGroup? = null,
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
lastLoginTime: LocalDateTime? = null,
|
||||
op: Employee.() -> Unit = {}
|
||||
) = Employee(
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
isDefaultGroupUser,
|
||||
isSystemUser,
|
||||
group,
|
||||
permissions,
|
||||
lastLoginTime
|
||||
).apply(op)
|
||||
|
||||
fun employeeSaveDto(
|
||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
||||
id: Long = 0L,
|
||||
firstName: String = "firstName",
|
||||
lastName: String = "lastName",
|
||||
password: String = passwordEncoder.encode("password"),
|
||||
groupId: Long? = null,
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeSaveDto.() -> Unit = {}
|
||||
) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op)
|
||||
|
||||
fun employeeUpdateDto(
|
||||
id: Long = 0L,
|
||||
firstName: String = "firstName",
|
||||
lastName: String = "lastName",
|
||||
groupId: Long? = null,
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeUpdateDto.() -> Unit = {}
|
||||
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
|
||||
|
||||
fun employeeGroup(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeGroup.() -> Unit = {}
|
||||
) = EmployeeGroup(id, name, permissions).apply(op)
|
||||
|
||||
fun employeeGroupSaveDto(
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeGroupSaveDto.() -> Unit = {}
|
||||
) = EmployeeGroupSaveDto(name, permissions).apply(op)
|
||||
|
||||
fun employeeGroupUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeGroupUpdateDto.() -> Unit = {}
|
||||
) = EmployeeGroupUpdateDto(id, name, permissions).apply(op)
|
@ -1,5 +1,8 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotBlank
|
||||
@ -11,47 +14,94 @@ private const val COMPANY_NAME_NULL_MESSAGE = "Un nom est requis"
|
||||
@Entity
|
||||
@Table(name = "company")
|
||||
data class Company(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
@Column(unique = true)
|
||||
override val name: String
|
||||
@Column(unique = true)
|
||||
override val name: String
|
||||
) : NamedModel
|
||||
|
||||
|
||||
open class CompanySaveDto(
|
||||
@field:NotBlank(message = COMPANY_NAME_NULL_MESSAGE)
|
||||
val name: String
|
||||
@field:NotBlank(message = COMPANY_NAME_NULL_MESSAGE)
|
||||
val name: String
|
||||
) : EntityDto<Company> {
|
||||
override fun toEntity(): Company = Company(null, name)
|
||||
}
|
||||
|
||||
|
||||
open class CompanyUpdateDto(
|
||||
@field:NotNull(message = COMPANY_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = COMPANY_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = COMPANY_NAME_NULL_MESSAGE)
|
||||
val name: String?
|
||||
@field:NullOrNotBlank(message = COMPANY_NAME_NULL_MESSAGE)
|
||||
val name: String?
|
||||
) : EntityDto<Company> {
|
||||
override fun toEntity(): Company = Company(id, name ?: "")
|
||||
}
|
||||
|
||||
// ==== DSL ====
|
||||
fun company(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
op: Company.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
op: Company.() -> Unit = {}
|
||||
) = Company(id, name).apply(op)
|
||||
|
||||
fun companySaveDto(
|
||||
name: String = "name",
|
||||
op: CompanySaveDto.() -> Unit = {}
|
||||
name: String = "name",
|
||||
op: CompanySaveDto.() -> Unit = {}
|
||||
) = CompanySaveDto(name).apply(op)
|
||||
|
||||
fun companyUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
op: CompanyUpdateDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
op: CompanyUpdateDto.() -> Unit = {}
|
||||
) = CompanyUpdateDto(id, name).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val COMPANY_NOT_FOUND_EXCEPTION_TITLE = "Company not found"
|
||||
private const val COMPANY_ALREADY_EXISTS_EXCEPTION_TITLE = "Company already exists"
|
||||
private const val COMPANY_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete company"
|
||||
private const val COMPANY_EXCEPTION_ERROR_CODE = "company"
|
||||
|
||||
fun companyIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
COMPANY_EXCEPTION_ERROR_CODE,
|
||||
COMPANY_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A company with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun companyNameNotFoundException(name: String) =
|
||||
NotFoundException(
|
||||
COMPANY_EXCEPTION_ERROR_CODE,
|
||||
COMPANY_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A company with the name $name could not be found",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun companyIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
COMPANY_EXCEPTION_ERROR_CODE,
|
||||
COMPANY_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A company with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun companyNameAlreadyExistsException(name: String) =
|
||||
AlreadyExistsException(
|
||||
COMPANY_EXCEPTION_ERROR_CODE,
|
||||
COMPANY_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A company with the name $name already exists",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun cannotDeleteCompany(company: Company) =
|
||||
CannotDeleteException(
|
||||
COMPANY_EXCEPTION_ERROR_CODE,
|
||||
COMPANY_CANNOT_DELETE_EXCEPTION_TITLE,
|
||||
"Cannot delete the company ${company.name} because one or more recipes depends on it"
|
||||
)
|
||||
|
191
src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt
Normal file
191
src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Employee.kt
Normal file
@ -0,0 +1,191 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import java.time.LocalDateTime
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
import javax.validation.constraints.Size
|
||||
|
||||
private const val EMPLOYEE_ID_NULL_MESSAGE = "Un numéro d'employé est requis"
|
||||
private const val EMPLOYEE_LAST_NAME_EMPTY_MESSAGE = "Un nom est requis"
|
||||
private const val EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE = "Un prénom est requis"
|
||||
private const val EMPLOYEE_PASSWORD_EMPTY_MESSAGE = "Un mot de passe est requis"
|
||||
private const val EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE = "Le mot de passe doit contenir au moins 8 caractères"
|
||||
|
||||
@Entity
|
||||
@Table(name = "employee")
|
||||
data class Employee(
|
||||
@Id
|
||||
override val id: Long,
|
||||
|
||||
@Column(name = "first_name")
|
||||
val firstName: String = "",
|
||||
|
||||
@Column(name = "last_name")
|
||||
val lastName: String = "",
|
||||
|
||||
@JsonIgnore
|
||||
val password: String = "",
|
||||
|
||||
@JsonIgnore
|
||||
@Column(name = "default_group_user")
|
||||
val isDefaultGroupUser: Boolean = false,
|
||||
|
||||
@JsonIgnore
|
||||
@Column(name = "system_user")
|
||||
val isSystemUser: Boolean = false,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
@Fetch(FetchMode.SELECT)
|
||||
var group: EmployeeGroup? = null,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "employee_permission", joinColumns = [JoinColumn(name = "employee_id")])
|
||||
@Column(name = "permission")
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
@get:JsonProperty("explicitPermissions")
|
||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
|
||||
@Column(name = "last_login_time")
|
||||
var lastLoginTime: LocalDateTime? = null
|
||||
) : Model {
|
||||
@get:JsonProperty("permissions")
|
||||
val flatPermissions: Set<EmployeePermission>
|
||||
get() = permissions
|
||||
.flatMap { it.flat() }
|
||||
.filter { !it.deprecated }
|
||||
.toMutableSet()
|
||||
.apply {
|
||||
if (group != null) this.addAll(group!!.flatPermissions)
|
||||
}
|
||||
|
||||
@get:JsonIgnore
|
||||
val authorities: Set<GrantedAuthority>
|
||||
get() = flatPermissions.map { it.toAuthority() }.toMutableSet()
|
||||
}
|
||||
|
||||
/** DTO for creating employees. Allows a [password] a [groupId]. */
|
||||
open class EmployeeSaveDto(
|
||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
||||
val firstName: String,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
||||
val lastName: String,
|
||||
|
||||
@field:NotBlank(message = EMPLOYEE_PASSWORD_EMPTY_MESSAGE)
|
||||
@field:Size(min = 8, message = EMPLOYEE_PASSWORD_TOO_SHORT_MESSAGE)
|
||||
val password: String,
|
||||
|
||||
val groupId: Long?,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf()
|
||||
) : EntityDto<Employee>
|
||||
|
||||
open class EmployeeUpdateDto(
|
||||
@field:NotNull(message = EMPLOYEE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = EMPLOYEE_FIRST_NAME_EMPTY_MESSAGE)
|
||||
val firstName: String?,
|
||||
|
||||
@field:NullOrNotBlank(message = EMPLOYEE_LAST_NAME_EMPTY_MESSAGE)
|
||||
val lastName: String?,
|
||||
|
||||
val groupId: Long?,
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
val permissions: Set<EmployeePermission>?
|
||||
) : EntityDto<Employee>
|
||||
|
||||
data class EmployeeLoginRequest(val id: Long, val password: String)
|
||||
|
||||
// ==== DSL ====
|
||||
fun employee(
|
||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
||||
id: Long = 0L,
|
||||
firstName: String = "firstName",
|
||||
lastName: String = "lastName",
|
||||
password: String = passwordEncoder.encode("password"),
|
||||
isDefaultGroupUser: Boolean = false,
|
||||
isSystemUser: Boolean = false,
|
||||
group: EmployeeGroup? = null,
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
lastLoginTime: LocalDateTime? = null,
|
||||
op: Employee.() -> Unit = {}
|
||||
) = Employee(
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
isDefaultGroupUser,
|
||||
isSystemUser,
|
||||
group,
|
||||
permissions,
|
||||
lastLoginTime
|
||||
).apply(op)
|
||||
|
||||
fun employeeSaveDto(
|
||||
passwordEncoder: PasswordEncoder = BCryptPasswordEncoder(),
|
||||
id: Long = 0L,
|
||||
firstName: String = "firstName",
|
||||
lastName: String = "lastName",
|
||||
password: String = passwordEncoder.encode("password"),
|
||||
groupId: Long? = null,
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeSaveDto.() -> Unit = {}
|
||||
) = EmployeeSaveDto(id, firstName, lastName, password, groupId, permissions).apply(op)
|
||||
|
||||
fun employeeUpdateDto(
|
||||
id: Long = 0L,
|
||||
firstName: String = "firstName",
|
||||
lastName: String = "lastName",
|
||||
groupId: Long? = null,
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeUpdateDto.() -> Unit = {}
|
||||
) = EmployeeUpdateDto(id, firstName, lastName, groupId, permissions).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee not found"
|
||||
private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee already exists"
|
||||
private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employee"
|
||||
|
||||
fun employeeIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"An employee with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun employeeIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"An employee with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun employeeFullNameAlreadyExistsException(firstName: String, lastName: String) =
|
||||
AlreadyExistsException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"An employee with the name '$firstName $lastName' already exists",
|
||||
"$firstName $lastName",
|
||||
"fullName"
|
||||
)
|
@ -0,0 +1,136 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import org.hibernate.annotations.Fetch
|
||||
import org.hibernate.annotations.FetchMode
|
||||
import org.springframework.http.HttpStatus
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.NotBlank
|
||||
import javax.validation.constraints.NotNull
|
||||
import javax.validation.constraints.Size
|
||||
|
||||
private const val GROUP_ID_NULL_MESSAGE = "Un identifiant est requis"
|
||||
private const val GROUP_NAME_NULL_MESSAGE = "Un nom est requis"
|
||||
private const val GROUP_PERMISSIONS_EMPTY_MESSAGE = "Au moins une permission est requise"
|
||||
|
||||
@Entity
|
||||
@Table(name = "employee_group")
|
||||
data class EmployeeGroup(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override var id: Long? = null,
|
||||
|
||||
@Column(unique = true)
|
||||
override val name: String = "",
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@ElementCollection(fetch = FetchType.EAGER)
|
||||
@CollectionTable(name = "group_permission", joinColumns = [JoinColumn(name = "group_id")])
|
||||
@Column(name = "permission")
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
@get:JsonProperty("explicitPermissions")
|
||||
val permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
) : NamedModel {
|
||||
@get:JsonProperty("permissions")
|
||||
val flatPermissions: Set<EmployeePermission>
|
||||
get() = this.permissions
|
||||
.flatMap { it.flat() }
|
||||
.filter { !it.deprecated }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
open class EmployeeGroupSaveDto(
|
||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
||||
@field:Size(min = 3)
|
||||
val name: String,
|
||||
|
||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
||||
val permissions: MutableSet<EmployeePermission>
|
||||
) : EntityDto<EmployeeGroup> {
|
||||
override fun toEntity(): EmployeeGroup =
|
||||
EmployeeGroup(null, name, permissions)
|
||||
}
|
||||
|
||||
open class EmployeeGroupUpdateDto(
|
||||
@field:NotNull(message = GROUP_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NotBlank(message = GROUP_NAME_NULL_MESSAGE)
|
||||
@field:Size(min = 3)
|
||||
val name: String,
|
||||
|
||||
@field:Size(min = 1, message = GROUP_PERMISSIONS_EMPTY_MESSAGE)
|
||||
val permissions: MutableSet<EmployeePermission>
|
||||
) : EntityDto<EmployeeGroup> {
|
||||
override fun toEntity(): EmployeeGroup =
|
||||
EmployeeGroup(id, name, permissions)
|
||||
}
|
||||
|
||||
fun employeeGroup(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeGroup.() -> Unit = {}
|
||||
) = EmployeeGroup(id, name, permissions).apply(op)
|
||||
|
||||
fun employeeGroupSaveDto(
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeGroupSaveDto.() -> Unit = {}
|
||||
) = EmployeeGroupSaveDto(name, permissions).apply(op)
|
||||
|
||||
fun employeeGroupUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String = "name",
|
||||
permissions: MutableSet<EmployeePermission> = mutableSetOf(),
|
||||
op: EmployeeGroupUpdateDto.() -> Unit = {}
|
||||
) = EmployeeGroupUpdateDto(id, name, permissions).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE = "Employee group not found"
|
||||
private const val EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE = "Employee group already exists"
|
||||
private const val EMPLOYEE_EXCEPTION_ERROR_CODE = "employeegroup"
|
||||
|
||||
class NoDefaultGroupException : RestException(
|
||||
"nodefaultgroup",
|
||||
"No default group",
|
||||
HttpStatus.NOT_FOUND,
|
||||
"No default group cookie is defined in the current request"
|
||||
)
|
||||
|
||||
fun employeeGroupIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"An employee group with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun employeeGroupNameNotFoundException(name: String) =
|
||||
NotFoundException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"An employee group with the name $name could not be found",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun employeeGroupIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"An employee group with the id $id already exists",
|
||||
id,
|
||||
)
|
||||
|
||||
fun employeeGroupNameAlreadyExistsException(name: String) =
|
||||
AlreadyExistsException(
|
||||
EMPLOYEE_EXCEPTION_ERROR_CODE,
|
||||
EMPLOYEE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"An employee group with the name $name already exists",
|
||||
name,
|
||||
"name"
|
||||
)
|
@ -0,0 +1,94 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import java.util.*
|
||||
|
||||
enum class EmployeePermission(
|
||||
val impliedPermissions: List<EmployeePermission> = listOf(),
|
||||
val deprecated: Boolean = false
|
||||
) {
|
||||
VIEW_RECIPES,
|
||||
VIEW_CATALOG,
|
||||
VIEW_USERS,
|
||||
|
||||
PRINT_MIXES(listOf(VIEW_RECIPES)),
|
||||
|
||||
EDIT_RECIPES_PUBLIC_DATA(listOf(VIEW_RECIPES)),
|
||||
EDIT_RECIPES(listOf(EDIT_RECIPES_PUBLIC_DATA)),
|
||||
EDIT_MATERIALS(listOf(VIEW_CATALOG)),
|
||||
EDIT_MATERIAL_TYPES(listOf(VIEW_CATALOG)),
|
||||
EDIT_COMPANIES(listOf(VIEW_CATALOG)),
|
||||
EDIT_USERS(listOf(VIEW_USERS)),
|
||||
EDIT_CATALOG(listOf(EDIT_MATERIALS, EDIT_MATERIAL_TYPES, EDIT_COMPANIES)),
|
||||
|
||||
ADD_TO_INVENTORY(listOf(VIEW_CATALOG)),
|
||||
DEDUCT_FROM_INVENTORY(listOf(VIEW_RECIPES)),
|
||||
|
||||
REMOVE_RECIPES(listOf(EDIT_RECIPES)),
|
||||
REMOVE_MATERIALS(listOf(EDIT_MATERIALS)),
|
||||
REMOVE_MATERIAL_TYPES(listOf(EDIT_MATERIAL_TYPES)),
|
||||
REMOVE_COMPANIES(listOf(EDIT_COMPANIES)),
|
||||
REMOVE_USERS(listOf(EDIT_USERS)),
|
||||
REMOVE_CATALOG(listOf(REMOVE_MATERIALS, REMOVE_MATERIAL_TYPES, REMOVE_COMPANIES)),
|
||||
|
||||
ADMIN(
|
||||
listOf(
|
||||
EDIT_CATALOG,
|
||||
|
||||
REMOVE_RECIPES,
|
||||
REMOVE_USERS,
|
||||
REMOVE_CATALOG,
|
||||
|
||||
PRINT_MIXES,
|
||||
ADD_TO_INVENTORY,
|
||||
DEDUCT_FROM_INVENTORY
|
||||
)
|
||||
),
|
||||
|
||||
// deprecated permissions
|
||||
VIEW_RECIPE(listOf(VIEW_RECIPES), true),
|
||||
VIEW_MATERIAL(listOf(VIEW_CATALOG), true),
|
||||
VIEW_MATERIAL_TYPE(listOf(VIEW_CATALOG), true),
|
||||
VIEW_COMPANY(listOf(VIEW_CATALOG), true),
|
||||
VIEW(listOf(VIEW_RECIPES, VIEW_CATALOG), true),
|
||||
VIEW_EMPLOYEE(listOf(VIEW_USERS), true),
|
||||
VIEW_EMPLOYEE_GROUP(listOf(VIEW_USERS), true),
|
||||
|
||||
EDIT_RECIPE(listOf(EDIT_RECIPES), true),
|
||||
EDIT_MATERIAL(listOf(EDIT_MATERIALS), true),
|
||||
EDIT_MATERIAL_TYPE(listOf(EDIT_MATERIAL_TYPES), true),
|
||||
EDIT_COMPANY(listOf(EDIT_COMPANIES), true),
|
||||
EDIT(listOf(EDIT_RECIPES, EDIT_CATALOG), true),
|
||||
EDIT_EMPLOYEE(listOf(EDIT_USERS), true),
|
||||
EDIT_EMPLOYEE_PASSWORD(listOf(EDIT_USERS), true),
|
||||
EDIT_EMPLOYEE_GROUP(listOf(EDIT_USERS), true),
|
||||
|
||||
REMOVE_RECIPE(listOf(REMOVE_RECIPES), true),
|
||||
REMOVE_MATERIAL(listOf(REMOVE_MATERIALS), true),
|
||||
REMOVE_MATERIAL_TYPE(listOf(REMOVE_MATERIAL_TYPES), true),
|
||||
REMOVE_COMPANY(listOf(REMOVE_COMPANIES), true),
|
||||
REMOVE(listOf(REMOVE_RECIPES, REMOVE_CATALOG), true),
|
||||
REMOVE_EMPLOYEE(listOf(REMOVE_USERS), true),
|
||||
REMOVE_EMPLOYEE_GROUP(listOf(REMOVE_USERS), true),
|
||||
|
||||
SET_BROWSER_DEFAULT_GROUP(listOf(VIEW_USERS), true),
|
||||
;
|
||||
|
||||
operator fun contains(permission: EmployeePermission): Boolean {
|
||||
return permission == this || impliedPermissions.any { permission in it }
|
||||
}
|
||||
}
|
||||
|
||||
fun EmployeePermission.flat(): Iterable<EmployeePermission> {
|
||||
return mutableSetOf(this).apply {
|
||||
impliedPermissions.forEach {
|
||||
addAll(it.flat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts the given [EmployeePermission] to a [GrantedAuthority]. */
|
||||
fun EmployeePermission.toAuthority(): GrantedAuthority {
|
||||
return SimpleGrantedAuthority(name)
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
@ -21,101 +24,148 @@ private const val MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE = "La quantité do
|
||||
@Entity
|
||||
@Table(name = "material")
|
||||
data class Material(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
@Column(unique = true)
|
||||
override var name: String,
|
||||
@Column(unique = true)
|
||||
override var name: String,
|
||||
|
||||
@Column(name = "inventory_quantity")
|
||||
var inventoryQuantity: Float,
|
||||
@Column(name = "inventory_quantity")
|
||||
var inventoryQuantity: Float,
|
||||
|
||||
@Column(name = "mix_type")
|
||||
val isMixType: Boolean,
|
||||
@Column(name = "mix_type")
|
||||
val isMixType: Boolean,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "material_type_id")
|
||||
var materialType: MaterialType?
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "material_type_id")
|
||||
var materialType: MaterialType?
|
||||
) : NamedModel
|
||||
|
||||
open class MaterialSaveDto(
|
||||
@field:NotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
@field:NotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
|
||||
@field:NotNull(message = MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val inventoryQuantity: Float,
|
||||
@field:NotNull(message = MATERIAL_INVENTORY_QUANTITY_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val inventoryQuantity: Float,
|
||||
|
||||
@field:NotNull(message = MATERIAL_TYPE_NULL_MESSAGE)
|
||||
val materialTypeId: Long,
|
||||
@field:NotNull(message = MATERIAL_TYPE_NULL_MESSAGE)
|
||||
val materialTypeId: Long,
|
||||
|
||||
val simdutFile: MultipartFile? = null
|
||||
val simdutFile: MultipartFile? = null
|
||||
) : EntityDto<Material>
|
||||
|
||||
open class MaterialUpdateDto(
|
||||
@field:NotNull(message = MATERIAL_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = MATERIAL_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
@field:NullOrNotBlank(message = MATERIAL_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
|
||||
@field:NullOrSize(min = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val inventoryQuantity: Float?,
|
||||
@field:NullOrSize(min = 0, message = MATERIAL_INVENTORY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val inventoryQuantity: Float?,
|
||||
|
||||
val materialTypeId: Long?,
|
||||
val materialTypeId: Long?,
|
||||
|
||||
val simdutFile: MultipartFile? = null
|
||||
val simdutFile: MultipartFile? = null
|
||||
) : EntityDto<Material>
|
||||
|
||||
data class MaterialQuantityDto(
|
||||
@field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE)
|
||||
val material: Long,
|
||||
@field:NotNull(message = MATERIAL_QUANTITY_MATERIAL_NULL_MESSAGE)
|
||||
val material: Long,
|
||||
|
||||
@field:NotNull(message = MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val quantity: Float
|
||||
@field:NotNull(message = MATERIAL_QUANTITY_QUANTITY_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MATERIAL_QUANTITY_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val quantity: Float
|
||||
)
|
||||
|
||||
// === DSL ===
|
||||
|
||||
fun material(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
inventoryQuantity: Float = 0f,
|
||||
isMixType: Boolean = false,
|
||||
materialType: MaterialType? = materialType(),
|
||||
op: Material.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
inventoryQuantity: Float = 0f,
|
||||
isMixType: Boolean = false,
|
||||
materialType: MaterialType? = materialType(),
|
||||
op: Material.() -> Unit = {}
|
||||
) = Material(id, name, inventoryQuantity, isMixType, materialType).apply(op)
|
||||
|
||||
fun material(
|
||||
material: Material,
|
||||
id: Long? = null,
|
||||
name: String? = null,
|
||||
material: Material,
|
||||
id: Long? = null,
|
||||
name: String? = null,
|
||||
) = Material(
|
||||
id ?: material.id, name
|
||||
?: material.name, material.inventoryQuantity, material.isMixType, material.materialType
|
||||
id ?: material.id, name
|
||||
?: material.name, material.inventoryQuantity, material.isMixType, material.materialType
|
||||
)
|
||||
|
||||
fun materialSaveDto(
|
||||
name: String = "name",
|
||||
inventoryQuantity: Float = 0f,
|
||||
materialTypeId: Long = 0L,
|
||||
simdutFile: MultipartFile? = null,
|
||||
op: MaterialSaveDto.() -> Unit = {}
|
||||
name: String = "name",
|
||||
inventoryQuantity: Float = 0f,
|
||||
materialTypeId: Long = 0L,
|
||||
simdutFile: MultipartFile? = null,
|
||||
op: MaterialSaveDto.() -> Unit = {}
|
||||
) = MaterialSaveDto(name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
|
||||
|
||||
fun materialUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
inventoryQuantity: Float? = 0f,
|
||||
materialTypeId: Long? = 0L,
|
||||
simdutFile: MultipartFile? = null,
|
||||
op: MaterialUpdateDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
inventoryQuantity: Float? = 0f,
|
||||
materialTypeId: Long? = 0L,
|
||||
simdutFile: MultipartFile? = null,
|
||||
op: MaterialUpdateDto.() -> Unit = {}
|
||||
) = MaterialUpdateDto(id, name, inventoryQuantity, materialTypeId, simdutFile).apply(op)
|
||||
|
||||
fun materialQuantityDto(
|
||||
materialId: Long,
|
||||
quantity: Float,
|
||||
op: MaterialQuantityDto.() -> Unit = {}
|
||||
materialId: Long,
|
||||
quantity: Float,
|
||||
op: MaterialQuantityDto.() -> Unit = {}
|
||||
) = MaterialQuantityDto(materialId, quantity).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Material not found"
|
||||
private const val MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Material already exists"
|
||||
private const val MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material"
|
||||
private const val MATERIAL_EXCEPTION_ERROR_CODE = "material"
|
||||
|
||||
fun materialIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A material with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun materialNameNotFoundException(name: String) =
|
||||
NotFoundException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A material with the name $name could not be found",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun materialIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun materialNameAlreadyExistsException(name: String) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material with the name $name already exists",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun cannotDeleteMaterialException(material: Material) =
|
||||
CannotDeleteException(
|
||||
MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_CANNOT_DELETE_EXCEPTION_TITLE,
|
||||
"Cannot delete the material ${material.name} because one or more recipes depends on it"
|
||||
)
|
||||
|
@ -1,5 +1,8 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize
|
||||
import org.hibernate.annotations.ColumnDefault
|
||||
@ -16,88 +19,144 @@ private const val MATERIAL_TYPE_PREFIX_SIZE_MESSAGE = "Le préfixe doit faire ex
|
||||
@Entity
|
||||
@Table(name = "material_type")
|
||||
data class MaterialType(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long? = null,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long? = null,
|
||||
|
||||
@Column(unique = true)
|
||||
override val name: String = "",
|
||||
@Column(unique = true)
|
||||
override val name: String = "",
|
||||
|
||||
@Column(unique = true)
|
||||
val prefix: String = "",
|
||||
@Column(unique = true)
|
||||
val prefix: String = "",
|
||||
|
||||
@Column(name = "use_percentages")
|
||||
@ColumnDefault("false")
|
||||
val usePercentages: Boolean = false,
|
||||
@Column(name = "use_percentages")
|
||||
@ColumnDefault("false")
|
||||
val usePercentages: Boolean = false,
|
||||
|
||||
@Column(name = "system_type")
|
||||
@ColumnDefault("false")
|
||||
val systemType: Boolean = false
|
||||
@Column(name = "system_type")
|
||||
@ColumnDefault("false")
|
||||
val systemType: Boolean = false
|
||||
) : NamedModel
|
||||
|
||||
open class MaterialTypeSaveDto(
|
||||
@field:NotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
@field:NotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
|
||||
@field:NotBlank(message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE)
|
||||
@field:Size(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_SIZE_MESSAGE)
|
||||
val prefix: String,
|
||||
@field:NotBlank(message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE)
|
||||
@field:Size(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_SIZE_MESSAGE)
|
||||
val prefix: String,
|
||||
|
||||
val usePercentages: Boolean = false
|
||||
val usePercentages: Boolean = false
|
||||
) : EntityDto<MaterialType> {
|
||||
override fun toEntity(): MaterialType =
|
||||
MaterialType(null, name, prefix, usePercentages)
|
||||
MaterialType(null, name, prefix, usePercentages)
|
||||
}
|
||||
|
||||
open class MaterialTypeUpdateDto(
|
||||
@field:NotNull(message = MATERIAL_TYPE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = MATERIAL_TYPE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
@field:NullOrNotBlank(message = MATERIAL_TYPE_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
|
||||
@field:NullOrSize(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE)
|
||||
val prefix: String?
|
||||
@field:NullOrSize(min = 3, max = 3, message = MATERIAL_TYPE_PREFIX_NULL_MESSAGE)
|
||||
val prefix: String?
|
||||
) : EntityDto<MaterialType> {
|
||||
override fun toEntity(): MaterialType =
|
||||
MaterialType(id, name ?: "", prefix ?: "")
|
||||
MaterialType(id, name ?: "", prefix ?: "")
|
||||
}
|
||||
|
||||
// ==== DSL ====
|
||||
fun materialType(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
prefix: String = "PRE",
|
||||
usePercentages: Boolean = false,
|
||||
systemType: Boolean = false,
|
||||
op: MaterialType.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
prefix: String = "PRE",
|
||||
usePercentages: Boolean = false,
|
||||
systemType: Boolean = false,
|
||||
op: MaterialType.() -> Unit = {}
|
||||
) = MaterialType(id, name, prefix, usePercentages, systemType).apply(op)
|
||||
|
||||
fun materialType(
|
||||
materialType: MaterialType,
|
||||
newId: Long? = null,
|
||||
newName: String? = null,
|
||||
newSystemType: Boolean? = null
|
||||
materialType: MaterialType,
|
||||
newId: Long? = null,
|
||||
newName: String? = null,
|
||||
newSystemType: Boolean? = null
|
||||
) = with(materialType) {
|
||||
MaterialType(
|
||||
newId ?: id,
|
||||
newName ?: name,
|
||||
prefix,
|
||||
usePercentages,
|
||||
newSystemType ?: systemType
|
||||
newId ?: id,
|
||||
newName ?: name,
|
||||
prefix,
|
||||
usePercentages,
|
||||
newSystemType ?: systemType
|
||||
)
|
||||
}
|
||||
|
||||
fun materialTypeSaveDto(
|
||||
name: String = "name",
|
||||
prefix: String = "PRE",
|
||||
usePercentages: Boolean = false,
|
||||
op: MaterialTypeSaveDto.() -> Unit = {}
|
||||
name: String = "name",
|
||||
prefix: String = "PRE",
|
||||
usePercentages: Boolean = false,
|
||||
op: MaterialTypeSaveDto.() -> Unit = {}
|
||||
) = MaterialTypeSaveDto(name, prefix, usePercentages).apply(op)
|
||||
|
||||
fun materialTypeUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = null,
|
||||
prefix: String? = null,
|
||||
op: MaterialTypeUpdateDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
name: String? = null,
|
||||
prefix: String? = null,
|
||||
op: MaterialTypeUpdateDto.() -> Unit = {}
|
||||
) = MaterialTypeUpdateDto(id, name, prefix).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val MATERIAL_TYPE_NOT_FOUND_EXCEPTION_TITLE = "Material type not found"
|
||||
private const val MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Material type already exists"
|
||||
private const val MATERIAL_TYPE_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete material type"
|
||||
private const val MATERIAL_TYPE_EXCEPTION_ERROR_CODE = "materialtype"
|
||||
|
||||
fun materialTypeIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_TYPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A material type with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun materialTypeNameNotFoundException(name: String) =
|
||||
NotFoundException(
|
||||
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_TYPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A material type with the name $name could not be found",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun materialTypeIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material type with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun materialTypeNameAlreadyExistsException(name: String) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material type with the name $name already exists",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun materialTypePrefixAlreadyExistsException(prefix: String) =
|
||||
AlreadyExistsException(
|
||||
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A material type with the prefix $prefix already exists",
|
||||
prefix,
|
||||
"prefix"
|
||||
)
|
||||
|
||||
fun cannotDeleteMaterialTypeException(materialType: MaterialType) =
|
||||
CannotDeleteException(
|
||||
MATERIAL_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MATERIAL_TYPE_CANNOT_DELETE_EXCEPTION_TITLE,
|
||||
"Cannot delete material type ${materialType.name} because one or more materials depends on it"
|
||||
)
|
||||
|
@ -1,6 +1,9 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.Min
|
||||
@ -19,105 +22,134 @@ private const val MIX_DEDUCT_RATION_NEGATIVE_MESSAGE = "Le ratio doit être éga
|
||||
@Entity
|
||||
@Table(name = "mix")
|
||||
data class Mix(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
var location: String?,
|
||||
var location: String?,
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val recipe: Recipe,
|
||||
@JsonIgnore
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val recipe: Recipe,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "mix_type_id")
|
||||
var mixType: MixType,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "mix_type_id")
|
||||
var mixType: MixType,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "mix_id")
|
||||
var mixMaterials: MutableSet<MixMaterial>,
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "mix_id")
|
||||
var mixMaterials: MutableSet<MixMaterial>,
|
||||
) : Model
|
||||
|
||||
open class MixSaveDto(
|
||||
@field:NotBlank(message = MIX_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
@field:NotBlank(message = MIX_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
|
||||
@field:NotNull(message = MIX_RECIPE_NULL_MESSAGE)
|
||||
val recipeId: Long,
|
||||
@field:NotNull(message = MIX_RECIPE_NULL_MESSAGE)
|
||||
val recipeId: Long,
|
||||
|
||||
@field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE)
|
||||
val materialTypeId: Long,
|
||||
@field:NotNull(message = MIX_MATERIAL_TYPE_NULL_MESSAGE)
|
||||
val materialTypeId: Long,
|
||||
|
||||
val mixMaterials: Set<MixMaterialDto>?
|
||||
val mixMaterials: Set<MixMaterialDto>?
|
||||
) : EntityDto<Mix> {
|
||||
override fun toEntity(): Mix = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
open class MixUpdateDto(
|
||||
@field:NotNull(message = MIX_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = MIX_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
@field:NullOrNotBlank(message = MIX_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
|
||||
val materialTypeId: Long?,
|
||||
val materialTypeId: Long?,
|
||||
|
||||
var mixMaterials: Set<MixMaterialDto>?
|
||||
var mixMaterials: Set<MixMaterialDto>?
|
||||
) : EntityDto<Mix> {
|
||||
override fun toEntity(): Mix = throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
data class MixDeductDto(
|
||||
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE)
|
||||
val ratio: Float
|
||||
@field:NotNull(message = MIX_DEDUCT_RATIO_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MIX_DEDUCT_RATION_NEGATIVE_MESSAGE)
|
||||
val ratio: Float
|
||||
)
|
||||
|
||||
data class MixLocationDto(
|
||||
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
|
||||
val mixId: Long,
|
||||
@field:NotNull(message = MIX_DEDUCT_MIX_ID_NULL_MESSAGE)
|
||||
val mixId: Long,
|
||||
|
||||
val location: String?
|
||||
val location: String?
|
||||
)
|
||||
|
||||
// ==== DSL ====
|
||||
fun mix(
|
||||
id: Long? = null,
|
||||
location: String? = "location",
|
||||
recipe: Recipe = recipe(),
|
||||
mixType: MixType = mixType(),
|
||||
mixMaterials: MutableSet<MixMaterial> = mutableSetOf(),
|
||||
op: Mix.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
location: String? = "location",
|
||||
recipe: Recipe = recipe(),
|
||||
mixType: MixType = mixType(),
|
||||
mixMaterials: MutableSet<MixMaterial> = mutableSetOf(),
|
||||
op: Mix.() -> Unit = {}
|
||||
) = Mix(id, location, recipe, mixType, mixMaterials).apply(op)
|
||||
|
||||
fun mixSaveDto(
|
||||
name: String = "name",
|
||||
recipeId: Long = 0L,
|
||||
materialTypeId: Long = 0L,
|
||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
||||
op: MixSaveDto.() -> Unit = {}
|
||||
name: String = "name",
|
||||
recipeId: Long = 0L,
|
||||
materialTypeId: Long = 0L,
|
||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
||||
op: MixSaveDto.() -> Unit = {}
|
||||
) = MixSaveDto(name, recipeId, materialTypeId, mixMaterials).apply(op)
|
||||
|
||||
fun mixUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
materialTypeId: Long? = 0L,
|
||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
||||
op: MixUpdateDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
materialTypeId: Long? = 0L,
|
||||
mixMaterials: Set<MixMaterialDto>? = setOf(),
|
||||
op: MixUpdateDto.() -> Unit = {}
|
||||
) = MixUpdateDto(id, name, materialTypeId, mixMaterials).apply(op)
|
||||
|
||||
fun mixRatio(
|
||||
id: Long = 0L,
|
||||
ratio: Float = 1f,
|
||||
op: MixDeductDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
ratio: Float = 1f,
|
||||
op: MixDeductDto.() -> Unit = {}
|
||||
) = MixDeductDto(id, ratio).apply(op)
|
||||
|
||||
fun mixLocationDto(
|
||||
mixId: Long = 0L,
|
||||
location: String? = "location",
|
||||
op: MixLocationDto.() -> Unit = {}
|
||||
mixId: Long = 0L,
|
||||
location: String? = "location",
|
||||
op: MixLocationDto.() -> Unit = {}
|
||||
) = MixLocationDto(mixId, location).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val MIX_NOT_FOUND_EXCEPTION_TITLE = "Mix not found"
|
||||
private const val MIX_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix already exists"
|
||||
private const val MIX_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete mix"
|
||||
private const val MIX_EXCEPTION_ERROR_CODE = "mix"
|
||||
|
||||
fun mixIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MIX_EXCEPTION_ERROR_CODE,
|
||||
MIX_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A mix with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun mixIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MIX_EXCEPTION_ERROR_CODE,
|
||||
MIX_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A mix with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun cannotDeleteMixException(mix: Mix) =
|
||||
CannotDeleteException(
|
||||
MIX_EXCEPTION_ERROR_CODE,
|
||||
MIX_CANNOT_DELETE_EXCEPTION_TITLE,
|
||||
"Cannot delete the mix ${mix.mixType.name} because one or more mixes depends on it"
|
||||
)
|
||||
|
@ -1,5 +1,7 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.Min
|
||||
import javax.validation.constraints.NotNull
|
||||
@ -11,42 +13,63 @@ private const val MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE = "La quantité ne
|
||||
@Entity
|
||||
@Table(name = "mix_material")
|
||||
data class MixMaterial(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "material_id")
|
||||
val material: Material,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "material_id")
|
||||
val material: Material,
|
||||
|
||||
var quantity: Float,
|
||||
var quantity: Float,
|
||||
|
||||
var position: Int
|
||||
var position: Int
|
||||
) : Model
|
||||
|
||||
data class MixMaterialDto(
|
||||
@field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE)
|
||||
val materialId: Long,
|
||||
@field:NotNull(message = MIX_MATERIAL_DTO_MATERIAL_ID_NULL_MESSAGE)
|
||||
val materialId: Long,
|
||||
|
||||
@field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val quantity: Float,
|
||||
@field:NotNull(message = MIX_MATERIAL_DTO_QUANTITY_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = MIX_MATERIAL_DTO_QUANTITY_NEGATIVE_MESSAGE)
|
||||
val quantity: Float,
|
||||
|
||||
val position: Int
|
||||
val position: Int
|
||||
)
|
||||
|
||||
// ==== DSL ====
|
||||
fun mixMaterial(
|
||||
id: Long? = null,
|
||||
material: Material = material(),
|
||||
quantity: Float = 0f,
|
||||
position: Int = 0,
|
||||
op: MixMaterial.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
material: Material = material(),
|
||||
quantity: Float = 0f,
|
||||
position: Int = 0,
|
||||
op: MixMaterial.() -> Unit = {}
|
||||
) = MixMaterial(id, material, quantity, position).apply(op)
|
||||
|
||||
fun mixMaterialDto(
|
||||
materialId: Long = 0L,
|
||||
quantity: Float = 0f,
|
||||
position: Int = 0,
|
||||
op: MixMaterialDto.() -> Unit = {}
|
||||
materialId: Long = 0L,
|
||||
quantity: Float = 0f,
|
||||
position: Int = 0,
|
||||
op: MixMaterialDto.() -> Unit = {}
|
||||
) = MixMaterialDto(materialId, quantity, position).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE = "Mix material not found"
|
||||
private const val MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix material already exists"
|
||||
private const val MIX_MATERIAL_EXCEPTION_ERROR_CODE = "mixmaterial"
|
||||
|
||||
fun mixMaterialIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MIX_MATERIAL_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A mix material with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun mixMaterialIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MIX_MATERIAL_EXCEPTION_ERROR_CODE,
|
||||
MIX_MATERIAL_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A mix material with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
@ -1,38 +1,100 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import org.springframework.http.HttpStatus
|
||||
import javax.persistence.*
|
||||
|
||||
const val IDENTIFIER_MATERIAL_NAME = "material"
|
||||
|
||||
@Entity
|
||||
@Table(name = "mix_type")
|
||||
data class MixType(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
@Column(unique = true)
|
||||
override var name: String,
|
||||
@Column(unique = true)
|
||||
override var name: String,
|
||||
|
||||
@OneToOne(cascade = [CascadeType.ALL])
|
||||
@JoinColumn(name = "material_id")
|
||||
var material: Material
|
||||
@OneToOne(cascade = [CascadeType.ALL])
|
||||
@JoinColumn(name = "material_id")
|
||||
var material: Material
|
||||
) : NamedModel
|
||||
|
||||
// ==== DSL ====
|
||||
fun mixType(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
material: Material = material(),
|
||||
op: MixType.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
material: Material = material(),
|
||||
op: MixType.() -> Unit = {}
|
||||
) = MixType(id, name, material).apply(op)
|
||||
|
||||
fun mixType(
|
||||
name: String = "name",
|
||||
materialType: MaterialType = materialType(),
|
||||
op: MixType.() -> Unit = {}
|
||||
name: String = "name",
|
||||
materialType: MaterialType = materialType(),
|
||||
op: MixType.() -> Unit = {}
|
||||
) = mixType(
|
||||
id = null,
|
||||
name,
|
||||
material = material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType)
|
||||
id = null,
|
||||
name,
|
||||
material = material(name = name, inventoryQuantity = 0f, isMixType = true, materialType = materialType)
|
||||
).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE = "Mix type not found"
|
||||
private const val MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Mix type already exists"
|
||||
private const val MIX_TYPE_CANNOT_DELETE_EXCEPTION_TITLE = "Cannot delete mix type"
|
||||
private const val MIX_TYPE_EXCEPTION_ERROR_CODE = "mixtype"
|
||||
|
||||
class MixTypeNameAndMaterialTypeNotFoundException(name: String, materialType: MaterialType) :
|
||||
RestException(
|
||||
"notfound-mixtype-namematerialtype",
|
||||
MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
HttpStatus.NOT_FOUND,
|
||||
"A mix type with the name $name and material type ${materialType.name} could not be found",
|
||||
mapOf(
|
||||
"name" to name,
|
||||
"materialType" to materialType.name
|
||||
)
|
||||
)
|
||||
|
||||
fun mixTypeIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
MIX_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A mix type with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun mixTypeIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
MIX_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A mix type with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
||||
fun mixTypeNameNotFoundException(name: String) =
|
||||
NotFoundException(
|
||||
MIX_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MIX_TYPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A mix type with the name $name could not be found",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun mixTypeNameAlreadyExistsException(name: String) =
|
||||
AlreadyExistsException(
|
||||
MIX_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MIX_TYPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A mix type with the name $name already exists",
|
||||
name,
|
||||
"name"
|
||||
)
|
||||
|
||||
fun cannotDeleteMixTypeException(mixType: MixType) =
|
||||
CannotDeleteException(
|
||||
MIX_TYPE_EXCEPTION_ERROR_CODE,
|
||||
MIX_TYPE_CANNOT_DELETE_EXCEPTION_TITLE,
|
||||
"Cannot delete the mix type ${mixType.name} because one or more mixes depends on it"
|
||||
)
|
||||
|
@ -1,8 +1,12 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrNotBlank
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.NullOrSize
|
||||
import org.springframework.http.HttpStatus
|
||||
import java.time.LocalDate
|
||||
import javax.persistence.*
|
||||
import javax.validation.constraints.*
|
||||
@ -24,39 +28,39 @@ private const val NOTE_GROUP_ID_NULL_MESSAGE = "Un identifiant de groupe est req
|
||||
@Entity
|
||||
@Table(name = "recipe")
|
||||
data class Recipe(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
|
||||
val name: String,
|
||||
/** The name of the recipe. It is not unique in the entire system, but is unique in the scope of a [Company]. */
|
||||
val name: String,
|
||||
|
||||
val description: String,
|
||||
val description: String,
|
||||
|
||||
/** The color produced by the recipe. The string should be formatted as a hexadecimal color without the sharp (#). */
|
||||
val color: String,
|
||||
/** The color produced by the recipe. The string should be formatted as a hexadecimal color without the sharp (#). */
|
||||
val color: String,
|
||||
|
||||
/** The gloss of the color in percents. (0-100) */
|
||||
val gloss: Byte,
|
||||
/** The gloss of the color in percents. (0-100) */
|
||||
val gloss: Byte,
|
||||
|
||||
val sample: Int?,
|
||||
val sample: Int?,
|
||||
|
||||
@Column(name = "approbation_date")
|
||||
val approbationDate: LocalDate?,
|
||||
@Column(name = "approbation_date")
|
||||
val approbationDate: LocalDate?,
|
||||
|
||||
/** A remark given by the creator of the recipe. */
|
||||
val remark: String,
|
||||
/** A remark given by the creator of the recipe. */
|
||||
val remark: String,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "company_id")
|
||||
val company: Company,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "company_id")
|
||||
val company: Company,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
|
||||
val mixes: MutableList<Mix>,
|
||||
@OneToMany(cascade = [CascadeType.ALL], mappedBy = "recipe")
|
||||
val mixes: MutableList<Mix>,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val groupsInformation: Set<RecipeGroupInformation>
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_id")
|
||||
val groupsInformation: Set<RecipeGroupInformation>
|
||||
) : Model {
|
||||
/** The mix types contained in this recipe. */
|
||||
val mixTypes: Collection<MixType>
|
||||
@ -64,185 +68,218 @@ data class Recipe(
|
||||
get() = mixes.map { it.mixType }
|
||||
|
||||
fun groupInformationForGroup(groupId: Long) =
|
||||
groupsInformation.firstOrNull { it.group.id == groupId }
|
||||
groupsInformation.firstOrNull { it.group.id == groupId }
|
||||
}
|
||||
|
||||
open class RecipeSaveDto(
|
||||
@field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
@field:NotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String,
|
||||
|
||||
@field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String,
|
||||
@field:NotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String,
|
||||
|
||||
@field:NotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String,
|
||||
@field:NotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String,
|
||||
|
||||
@field:NotNull(message = RECIPE_GLOSS_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte,
|
||||
@field:NotNull(message = RECIPE_GLOSS_NULL_MESSAGE)
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte,
|
||||
|
||||
@field:Min(value = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
@field:Min(value = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
|
||||
val approbationDate: LocalDate?,
|
||||
val approbationDate: LocalDate?,
|
||||
|
||||
val remark: String?,
|
||||
val remark: String?,
|
||||
|
||||
@field:Min(value = 0, message = RECIPE_COMPANY_NULL_MESSAGE)
|
||||
val companyId: Long = -1L,
|
||||
@field:Min(value = 0, message = RECIPE_COMPANY_NULL_MESSAGE)
|
||||
val companyId: Long = -1L,
|
||||
) : EntityDto<Recipe> {
|
||||
override fun toEntity(): Recipe = recipe(
|
||||
name = name,
|
||||
description = description,
|
||||
sample = sample,
|
||||
approbationDate = approbationDate,
|
||||
remark = remark ?: "",
|
||||
company = company(id = companyId)
|
||||
name = name,
|
||||
description = description,
|
||||
sample = sample,
|
||||
approbationDate = approbationDate,
|
||||
remark = remark ?: "",
|
||||
company = company(id = companyId)
|
||||
)
|
||||
}
|
||||
|
||||
open class RecipeUpdateDto(
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val id: Long,
|
||||
|
||||
@field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
@field:NullOrNotBlank(message = RECIPE_NAME_NULL_MESSAGE)
|
||||
val name: String?,
|
||||
|
||||
@field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String?,
|
||||
@field:NullOrNotBlank(message = RECIPE_DESCRIPTION_NULL_MESSAGE)
|
||||
val description: String?,
|
||||
|
||||
@field:NullOrNotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String?,
|
||||
@field:NullOrNotBlank(message = RECIPE_COLOR_NULL_MESSAGE)
|
||||
@field:Pattern(regexp = "^#([0-9a-f]{6})$")
|
||||
val color: String?,
|
||||
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte?,
|
||||
@field:Min(value = 0, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
@field:Max(value = 100, message = RECIPE_GLOSS_OUTSIDE_RANGE_MESSAGE)
|
||||
val gloss: Byte?,
|
||||
|
||||
@field:NullOrSize(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
@field:NullOrSize(min = 0, message = RECIPE_SAMPLE_TOO_SMALL_MESSAGE)
|
||||
val sample: Int?,
|
||||
|
||||
val approbationDate: LocalDate?,
|
||||
val approbationDate: LocalDate?,
|
||||
|
||||
val remark: String?,
|
||||
val remark: String?,
|
||||
|
||||
val steps: Set<RecipeStepsDto>?
|
||||
val steps: Set<RecipeStepsDto>?
|
||||
) : EntityDto<Recipe>
|
||||
|
||||
@Entity
|
||||
@Table(name = "recipe_group_information")
|
||||
data class RecipeGroupInformation(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
val id: Long?,
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
val group: EmployeeGroup,
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "group_id")
|
||||
val group: EmployeeGroup,
|
||||
|
||||
var note: String?,
|
||||
var note: String?,
|
||||
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_group_information_id")
|
||||
var steps: MutableSet<RecipeStep>?
|
||||
@OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(name = "recipe_group_information_id")
|
||||
var steps: MutableSet<RecipeStep>?
|
||||
)
|
||||
|
||||
data class RecipeStepsDto(
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE)
|
||||
val steps: Set<RecipeStep>
|
||||
@field:NotNull(message = RECIPE_STEPS_DTO_MESSAGES_NULL_MESSAGE)
|
||||
val steps: Set<RecipeStep>
|
||||
)
|
||||
|
||||
data class RecipePublicDataDto(
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val recipeId: Long,
|
||||
@field:NotNull(message = RECIPE_ID_NULL_MESSAGE)
|
||||
val recipeId: Long,
|
||||
|
||||
val notes: Set<NoteDto>?,
|
||||
val notes: Set<NoteDto>?,
|
||||
|
||||
val mixesLocation: Set<MixLocationDto>?
|
||||
val mixesLocation: Set<MixLocationDto>?
|
||||
)
|
||||
|
||||
data class NoteDto(
|
||||
@field:NotNull(message = NOTE_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
@field:NotNull(message = NOTE_GROUP_ID_NULL_MESSAGE)
|
||||
val groupId: Long,
|
||||
|
||||
val content: String?
|
||||
val content: String?
|
||||
)
|
||||
|
||||
// ==== DSL ====
|
||||
fun recipe(
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
company: Company = company(),
|
||||
mixes: MutableList<Mix> = mutableListOf(),
|
||||
groupsInformation: Set<RecipeGroupInformation> = setOf(),
|
||||
op: Recipe.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
company: Company = company(),
|
||||
mixes: MutableList<Mix> = mutableListOf(),
|
||||
groupsInformation: Set<RecipeGroupInformation> = setOf(),
|
||||
op: Recipe.() -> Unit = {}
|
||||
) = Recipe(
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
color,
|
||||
gloss,
|
||||
sample,
|
||||
approbationDate,
|
||||
remark,
|
||||
company,
|
||||
mixes,
|
||||
groupsInformation
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
color,
|
||||
gloss,
|
||||
sample,
|
||||
approbationDate,
|
||||
remark,
|
||||
company,
|
||||
mixes,
|
||||
groupsInformation
|
||||
).apply(op)
|
||||
|
||||
fun recipeSaveDto(
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
companyId: Long = 0L,
|
||||
op: RecipeSaveDto.() -> Unit = {}
|
||||
name: String = "name",
|
||||
description: String = "description",
|
||||
color: String = "ffffff",
|
||||
gloss: Byte = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String = "remark",
|
||||
companyId: Long = 0L,
|
||||
op: RecipeSaveDto.() -> Unit = {}
|
||||
) = RecipeSaveDto(name, description, color, gloss, sample, approbationDate, remark, companyId).apply(op)
|
||||
|
||||
fun recipeUpdateDto(
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
description: String? = "description",
|
||||
color: String? = "ffffff",
|
||||
gloss: Byte? = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String? = "remark",
|
||||
steps: Set<RecipeStepsDto>? = setOf(),
|
||||
op: RecipeUpdateDto.() -> Unit = {}
|
||||
id: Long = 0L,
|
||||
name: String? = "name",
|
||||
description: String? = "description",
|
||||
color: String? = "ffffff",
|
||||
gloss: Byte? = 0,
|
||||
sample: Int? = -1,
|
||||
approbationDate: LocalDate? = LocalDate.MIN,
|
||||
remark: String? = "remark",
|
||||
steps: Set<RecipeStepsDto>? = setOf(),
|
||||
op: RecipeUpdateDto.() -> Unit = {}
|
||||
) = RecipeUpdateDto(id, name, description, color, gloss, sample, approbationDate, remark, steps).apply(op)
|
||||
|
||||
fun recipeGroupInformation(
|
||||
id: Long? = null,
|
||||
group: EmployeeGroup = employeeGroup(),
|
||||
note: String? = null,
|
||||
steps: MutableSet<RecipeStep>? = mutableSetOf(),
|
||||
op: RecipeGroupInformation.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
group: EmployeeGroup = employeeGroup(),
|
||||
note: String? = null,
|
||||
steps: MutableSet<RecipeStep>? = mutableSetOf(),
|
||||
op: RecipeGroupInformation.() -> Unit = {}
|
||||
) = RecipeGroupInformation(id, group, note, steps).apply(op)
|
||||
|
||||
fun recipePublicDataDto(
|
||||
recipeId: Long = 0L,
|
||||
notes: Set<NoteDto>? = null,
|
||||
mixesLocation: Set<MixLocationDto>? = null,
|
||||
op: RecipePublicDataDto.() -> Unit = {}
|
||||
recipeId: Long = 0L,
|
||||
notes: Set<NoteDto>? = null,
|
||||
mixesLocation: Set<MixLocationDto>? = null,
|
||||
op: RecipePublicDataDto.() -> Unit = {}
|
||||
) = RecipePublicDataDto(recipeId, notes, mixesLocation).apply(op)
|
||||
|
||||
fun noteDto(
|
||||
groupId: Long = 0L,
|
||||
content: String? = "note",
|
||||
op: NoteDto.() -> Unit = {}
|
||||
groupId: Long = 0L,
|
||||
content: String? = "note",
|
||||
op: NoteDto.() -> Unit = {}
|
||||
) = NoteDto(groupId, content).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val RECIPE_NOT_FOUND_EXCEPTION_TITLE = "Recipe not found"
|
||||
private const val RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE = "Recipe already exists"
|
||||
private const val RECIPE_EXCEPTION_ERROR_CODE = "recipe"
|
||||
|
||||
class RecipeImageNotFoundException(id: Long, recipe: Recipe) :
|
||||
RestException(
|
||||
"notfound-recipeimage-id",
|
||||
"Recipe image not found",
|
||||
HttpStatus.NOT_FOUND,
|
||||
"A recipe image with the id $id could no be found for the recipe ${recipe.name}",
|
||||
mapOf(
|
||||
"id" to id,
|
||||
"recipe" to recipe.name
|
||||
)
|
||||
)
|
||||
|
||||
fun recipeIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
RECIPE_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A recipe with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun recipeIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
RECIPE_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A recipe with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
@ -1,23 +1,46 @@
|
||||
package dev.fyloz.colorrecipesexplorer.model
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import javax.persistence.*
|
||||
|
||||
@Entity
|
||||
@Table(name = "recipe_step")
|
||||
data class RecipeStep(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
override val id: Long?,
|
||||
|
||||
val position: Int,
|
||||
val position: Int,
|
||||
|
||||
val message: String
|
||||
val message: String
|
||||
) : Model
|
||||
|
||||
// ==== DSL ====
|
||||
fun recipeStep(
|
||||
id: Long? = null,
|
||||
position: Int = 0,
|
||||
message: String = "message",
|
||||
op: RecipeStep.() -> Unit = {}
|
||||
id: Long? = null,
|
||||
position: Int = 0,
|
||||
message: String = "message",
|
||||
op: RecipeStep.() -> Unit = {}
|
||||
) = RecipeStep(id, position, message).apply(op)
|
||||
|
||||
// ==== Exceptions ====
|
||||
private const val RECIPE_STEP_NOT_FOUND_EXCEPTION_TITLE = "Recipe step not found"
|
||||
private const val RECIPE_STEP_ALREADY_EXISTS_EXCEPTION_TITLE = "Recipe step already exists"
|
||||
private const val RECIPE_STEP_EXCEPTION_ERROR_CODE = "recipestep"
|
||||
|
||||
fun recipeStepIdNotFoundException(id: Long) =
|
||||
NotFoundException(
|
||||
RECIPE_STEP_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_STEP_NOT_FOUND_EXCEPTION_TITLE,
|
||||
"A recipe step with the id $id could not be found",
|
||||
id
|
||||
)
|
||||
|
||||
fun recipeStepIdAlreadyExistsException(id: Long) =
|
||||
AlreadyExistsException(
|
||||
RECIPE_STEP_EXCEPTION_ERROR_CODE,
|
||||
RECIPE_STEP_ALREADY_EXISTS_EXCEPTION_TITLE,
|
||||
"A recipe step with the id $id already exists",
|
||||
id
|
||||
)
|
||||
|
@ -14,9 +14,9 @@ private const val MESSAGE = "must be null or not blank"
|
||||
@MustBeDocumented
|
||||
@Constraint(validatedBy = [NullOrNotBlankValidator::class])
|
||||
annotation class NullOrNotBlank(
|
||||
val message: String = MESSAGE,
|
||||
val groups: Array<KClass<*>> = [],
|
||||
@Suppress("unused") val payload: Array<KClass<out Payload>> = []
|
||||
val message: String = MESSAGE,
|
||||
val groups: Array<KClass<*>> = [],
|
||||
@Suppress("unused") val payload: Array<KClass<out Payload>> = []
|
||||
)
|
||||
|
||||
class NullOrNotBlankValidator : ConstraintValidator<NullOrNotBlank, String> {
|
||||
|
@ -14,11 +14,11 @@ private const val MESSAGE = "must be null or have a correct length"
|
||||
@MustBeDocumented
|
||||
@Constraint(validatedBy = [NullOrSizeValidator::class])
|
||||
annotation class NullOrSize(
|
||||
val min: Long = MIN_SIZE,
|
||||
val max: Long = MAX_SIZE,
|
||||
val message: String = MESSAGE,
|
||||
val groups: Array<KClass<*>> = [],
|
||||
@Suppress("unused") val payload: Array<KClass<out Payload>> = []
|
||||
val min: Long = MIN_SIZE,
|
||||
val max: Long = MAX_SIZE,
|
||||
val message: String = MESSAGE,
|
||||
val groups: Array<KClass<*>> = [],
|
||||
@Suppress("unused") val payload: Array<KClass<out Payload>> = []
|
||||
)
|
||||
|
||||
class NullOrSizeValidator : ConstraintValidator<NullOrSize, Any> {
|
||||
|
@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository
|
||||
@Repository
|
||||
interface CompanyRepository : NamedJpaRepository<Company> {
|
||||
@Query(
|
||||
"""
|
||||
"""
|
||||
select case when(count(r.id) > 0) then false else true end
|
||||
from Company c
|
||||
left join Recipe r on c.id = r.company.id
|
||||
|
@ -17,7 +17,7 @@ interface MaterialRepository : NamedJpaRepository<Material> {
|
||||
fun updateInventoryQuantityById(id: Long, inventoryQuantity: Float)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
"""
|
||||
select case when(count(mm.id) + count(mt.id) > 0) then false else true end
|
||||
from Material m
|
||||
left join MixMaterial mm on m.id = mm.material.id
|
||||
|
@ -16,7 +16,7 @@ interface MaterialTypeRepository : NamedJpaRepository<MaterialType> {
|
||||
fun findByPrefix(prefix: String): MaterialType?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
"""
|
||||
select case when(count(m.id) > 0) then false else true end
|
||||
from MaterialType t
|
||||
left join Material m on t.id = m.materialType.id
|
||||
|
@ -16,7 +16,7 @@ interface MixRepository : JpaRepository<Mix, Long> {
|
||||
fun updateLocationById(id: Long, location: String?)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
"""
|
||||
select case when(count(mm.id) > 0) then false else true end
|
||||
from Mix m
|
||||
left join MixMaterial mm on m.mixType.material.id = mm.material.id
|
||||
|
@ -19,7 +19,7 @@ interface MixTypeRepository : NamedJpaRepository<MixType> {
|
||||
fun findByNameAndMaterialType(name: String, materialType: MaterialType): MixType?
|
||||
|
||||
@Query(
|
||||
"""
|
||||
"""
|
||||
select case when(count(m.id) > 0) then false else true end
|
||||
from MixType t
|
||||
left join Mix m on t.id = m.mixType.id
|
||||
|
@ -23,52 +23,52 @@ class EmployeeController(private val employeeService: EmployeeService) {
|
||||
@GetMapping
|
||||
@PreAuthorizeViewUsers
|
||||
fun getAll() =
|
||||
ok(employeeService.getAll())
|
||||
ok(employeeService.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(employeeService.getById(id))
|
||||
ok(employeeService.getById(id))
|
||||
|
||||
@GetMapping("current")
|
||||
fun getCurrent(loggedInEmployee: Principal?) =
|
||||
if (loggedInEmployee != null)
|
||||
ok(
|
||||
employeeService.getById(
|
||||
loggedInEmployee.name.toLong(),
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = false
|
||||
)
|
||||
if (loggedInEmployee != null)
|
||||
ok(
|
||||
employeeService.getById(
|
||||
loggedInEmployee.name.toLong(),
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = false
|
||||
)
|
||||
else
|
||||
forbidden()
|
||||
)
|
||||
else
|
||||
forbidden()
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun save(@Valid @RequestBody employee: EmployeeSaveDto) =
|
||||
created<Employee>(EMPLOYEE_CONTROLLER_PATH) {
|
||||
employeeService.save(employee)
|
||||
}
|
||||
created<Employee>(EMPLOYEE_CONTROLLER_PATH) {
|
||||
employeeService.save(employee)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun update(@Valid @RequestBody employee: EmployeeUpdateDto) =
|
||||
noContent {
|
||||
employeeService.update(employee)
|
||||
}
|
||||
noContent {
|
||||
employeeService.update(employee)
|
||||
}
|
||||
|
||||
@PutMapping("{id}/password", consumes = [MediaType.TEXT_PLAIN_VALUE])
|
||||
@PreAuthorizeEditUsers
|
||||
fun updatePassword(@PathVariable id: Long, @RequestBody password: String) =
|
||||
noContent {
|
||||
employeeService.updatePassword(id, password)
|
||||
}
|
||||
noContent {
|
||||
employeeService.updatePassword(id, password)
|
||||
}
|
||||
|
||||
@PutMapping("{employeeId}/permissions/{permission}")
|
||||
@PreAuthorizeEditUsers
|
||||
fun addPermission(
|
||||
@PathVariable employeeId: Long,
|
||||
@PathVariable permission: EmployeePermission
|
||||
@PathVariable employeeId: Long,
|
||||
@PathVariable permission: EmployeePermission
|
||||
) = noContent {
|
||||
employeeService.addPermission(employeeId, permission)
|
||||
}
|
||||
@ -76,8 +76,8 @@ class EmployeeController(private val employeeService: EmployeeService) {
|
||||
@DeleteMapping("{employeeId}/permissions/{permission}")
|
||||
@PreAuthorizeEditUsers
|
||||
fun removePermission(
|
||||
@PathVariable employeeId: Long,
|
||||
@PathVariable permission: EmployeePermission
|
||||
@PathVariable employeeId: Long,
|
||||
@PathVariable permission: EmployeePermission
|
||||
) = noContent {
|
||||
employeeService.removePermission(employeeId, permission)
|
||||
}
|
||||
@ -85,7 +85,7 @@ class EmployeeController(private val employeeService: EmployeeService) {
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorizeRemoveUsers
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
employeeService.deleteById(id)
|
||||
employeeService.deleteById(id)
|
||||
}
|
||||
|
||||
@RestController
|
||||
@ -94,50 +94,50 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) {
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyAuthority('VIEW_RECIPES', 'VIEW_USERS')")
|
||||
fun getAll() =
|
||||
ok(groupService.getAll())
|
||||
ok(groupService.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(groupService.getById(id))
|
||||
ok(groupService.getById(id))
|
||||
|
||||
@GetMapping("{id}/employees")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getEmployeesForGroup(@PathVariable id: Long) =
|
||||
ok(groupService.getEmployeesForGroup(id))
|
||||
ok(groupService.getEmployeesForGroup(id))
|
||||
|
||||
@PostMapping("default/{groupId}")
|
||||
@PreAuthorizeViewUsers
|
||||
fun setDefaultGroup(@PathVariable groupId: Long, response: HttpServletResponse) =
|
||||
noContent {
|
||||
groupService.setResponseDefaultGroup(groupId, response)
|
||||
}
|
||||
noContent {
|
||||
groupService.setResponseDefaultGroup(groupId, response)
|
||||
}
|
||||
|
||||
@GetMapping("default")
|
||||
@PreAuthorizeViewUsers
|
||||
fun getRequestDefaultGroup(request: HttpServletRequest) =
|
||||
ok(groupService.getRequestDefaultGroup(request))
|
||||
ok(groupService.getRequestDefaultGroup(request))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun save(@Valid @RequestBody group: EmployeeGroupSaveDto) =
|
||||
created<EmployeeGroup>(EMPLOYEE_GROUP_CONTROLLER_PATH) {
|
||||
groupService.save(group)
|
||||
}
|
||||
created<EmployeeGroup>(EMPLOYEE_GROUP_CONTROLLER_PATH) {
|
||||
groupService.save(group)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditUsers
|
||||
fun update(@Valid @RequestBody group: EmployeeGroupUpdateDto) =
|
||||
noContent {
|
||||
groupService.update(group)
|
||||
}
|
||||
noContent {
|
||||
groupService.update(group)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorizeRemoveUsers
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
groupService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
groupService.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@ -145,7 +145,7 @@ class GroupsController(private val groupService: EmployeeGroupServiceImpl) {
|
||||
class LogoutController(private val employeeService: EmployeeService) {
|
||||
@GetMapping("logout")
|
||||
fun logout(request: HttpServletRequest) =
|
||||
ok<Void> {
|
||||
employeeService.logout(request)
|
||||
}
|
||||
ok<Void> {
|
||||
employeeService.logout(request)
|
||||
}
|
||||
}
|
||||
|
@ -17,30 +17,30 @@ private const val COMPANY_CONTROLLER_PATH = "api/company"
|
||||
class CompanyController(private val companyService: CompanyService) {
|
||||
@GetMapping
|
||||
fun getAll() =
|
||||
ok(companyService.getAll())
|
||||
ok(companyService.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(companyService.getById(id))
|
||||
ok(companyService.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('EDIT_COMPANIES')")
|
||||
fun save(@Valid @RequestBody company: CompanySaveDto) =
|
||||
created<Company>(COMPANY_CONTROLLER_PATH) {
|
||||
companyService.save(company)
|
||||
}
|
||||
created<Company>(COMPANY_CONTROLLER_PATH) {
|
||||
companyService.save(company)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorize("hasAuthority('EDIT_COMPANIES')")
|
||||
fun update(@Valid @RequestBody company: CompanyUpdateDto) =
|
||||
noContent {
|
||||
companyService.update(company)
|
||||
}
|
||||
noContent {
|
||||
companyService.update(company)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorize("hasAuthority('REMOVE_COMPANIES')")
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
companyService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
companyService.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ private const val INVENTORY_CONTROLLER_PATH = "api/inventory"
|
||||
@RestController
|
||||
@RequestMapping(INVENTORY_CONTROLLER_PATH)
|
||||
class InventoryController(
|
||||
private val inventoryService: InventoryService
|
||||
private val inventoryService: InventoryService
|
||||
) {
|
||||
@PutMapping("add")
|
||||
@PreAuthorize("hasAuthority('ADD_TO_INVENTORY')")
|
||||
@ -26,10 +26,10 @@ class InventoryController(
|
||||
@PutMapping("deduct")
|
||||
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
|
||||
fun deduct(@RequestBody quantities: Collection<MaterialQuantityDto>) =
|
||||
ok(inventoryService.deduct(quantities))
|
||||
ok(inventoryService.deduct(quantities))
|
||||
|
||||
@PutMapping("deduct/mix")
|
||||
@PreAuthorize("hasAuthority('DEDUCT_FROM_INVENTORY')")
|
||||
fun deduct(@RequestBody mixRatio: MixDeductDto) =
|
||||
ok(inventoryService.deductMix(mixRatio))
|
||||
ok(inventoryService.deductMix(mixRatio))
|
||||
}
|
||||
|
@ -18,55 +18,55 @@ private const val MATERIAL_CONTROLLER_PATH = "api/material"
|
||||
class MaterialController(private val materialService: MaterialService) {
|
||||
@GetMapping
|
||||
fun getAll() =
|
||||
ok(materialService.getAll())
|
||||
ok(materialService.getAll())
|
||||
|
||||
@GetMapping("notmixtype")
|
||||
fun getAllNotMixType() =
|
||||
ok(materialService.getAllNotMixType())
|
||||
ok(materialService.getAllNotMixType())
|
||||
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(materialService.getById(id))
|
||||
ok(materialService.getById(id))
|
||||
|
||||
@PostMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
||||
fun save(@Valid material: MaterialSaveDto, simdutFile: MultipartFile?) =
|
||||
created<Material>(MATERIAL_CONTROLLER_PATH) {
|
||||
materialService.save(
|
||||
materialSaveDto(
|
||||
name = material.name,
|
||||
inventoryQuantity = material.inventoryQuantity,
|
||||
materialTypeId = material.materialTypeId,
|
||||
simdutFile = simdutFile
|
||||
)
|
||||
created<Material>(MATERIAL_CONTROLLER_PATH) {
|
||||
materialService.save(
|
||||
materialSaveDto(
|
||||
name = material.name,
|
||||
inventoryQuantity = material.inventoryQuantity,
|
||||
materialTypeId = material.materialTypeId,
|
||||
simdutFile = simdutFile
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@PutMapping(consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorize("hasAuthority('EDIT_MATERIALS')")
|
||||
fun update(@Valid material: MaterialUpdateDto, simdutFile: MultipartFile?) =
|
||||
noContent {
|
||||
materialService.update(
|
||||
materialUpdateDto(
|
||||
id = material.id,
|
||||
name = material.name,
|
||||
inventoryQuantity = material.inventoryQuantity,
|
||||
materialTypeId = material.materialTypeId,
|
||||
simdutFile = simdutFile
|
||||
)
|
||||
noContent {
|
||||
materialService.update(
|
||||
materialUpdateDto(
|
||||
id = material.id,
|
||||
name = material.name,
|
||||
inventoryQuantity = material.inventoryQuantity,
|
||||
materialTypeId = material.materialTypeId,
|
||||
simdutFile = simdutFile
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorize("hasAuthority('REMOVE_MATERIALS')")
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
materialService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
materialService.deleteById(id)
|
||||
}
|
||||
|
||||
@GetMapping("{id}/simdut/exists")
|
||||
fun hasSimdut(@PathVariable id: Long) =
|
||||
ok(materialService.hasSimdut(id))
|
||||
ok(materialService.hasSimdut(id))
|
||||
|
||||
@GetMapping("{id}/simdut", produces = [MediaType.APPLICATION_PDF_VALUE])
|
||||
fun getSimdut(@PathVariable id: Long): ResponseEntity<ByteArray> = with(materialService.getSimdut(id)) {
|
||||
@ -79,14 +79,14 @@ class MaterialController(private val materialService: MaterialService) {
|
||||
|
||||
@GetMapping("/simdut")
|
||||
fun getAllIdsWithSimdut() =
|
||||
ok(materialService.getAllIdsWithSimdut())
|
||||
ok(materialService.getAllIdsWithSimdut())
|
||||
|
||||
@GetMapping("mix/create/{recipeId}")
|
||||
fun getAllForMixCreation(@PathVariable recipeId: Long) =
|
||||
ok(materialService.getAllForMixCreation(recipeId))
|
||||
ok(materialService.getAllForMixCreation(recipeId))
|
||||
|
||||
@GetMapping("mix/update/{mixId}")
|
||||
fun getAllForMixUpdate(@PathVariable mixId: Long) =
|
||||
ok(materialService.getAllForMixUpdate(mixId))
|
||||
ok(materialService.getAllForMixUpdate(mixId))
|
||||
}
|
||||
|
||||
|
@ -17,31 +17,31 @@ private const val MATERIAL_TYPE_CONTROLLER_PATH = "api/materialtype"
|
||||
class MaterialTypeController(private val materialTypeService: MaterialTypeService) {
|
||||
@GetMapping
|
||||
fun getAll() =
|
||||
ok(materialTypeService.getAll())
|
||||
ok(materialTypeService.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(materialTypeService.getById(id))
|
||||
ok(materialTypeService.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')")
|
||||
fun save(@Valid @RequestBody materialType: MaterialTypeSaveDto) =
|
||||
created<MaterialType>(MATERIAL_TYPE_CONTROLLER_PATH) {
|
||||
materialTypeService.save(materialType)
|
||||
}
|
||||
created<MaterialType>(MATERIAL_TYPE_CONTROLLER_PATH) {
|
||||
materialTypeService.save(materialType)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorize("hasAuthority('EDIT_MATERIAL_TYPES')")
|
||||
fun update(@Valid @RequestBody materialType: MaterialTypeUpdateDto) =
|
||||
noContent {
|
||||
materialTypeService.update(materialType)
|
||||
}
|
||||
noContent {
|
||||
materialTypeService.update(materialType)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorize("hasAuthority('REMOVE_MATERIAL_TYPES')")
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
materialTypeService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
materialTypeService.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,39 +25,39 @@ private const val MIX_CONTROLLER_PATH = "api/recipe/mix"
|
||||
class RecipeController(private val recipeService: RecipeService) {
|
||||
@GetMapping
|
||||
fun getAll() =
|
||||
ok(recipeService.getAll())
|
||||
ok(recipeService.getAll())
|
||||
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(recipeService.getById(id))
|
||||
ok(recipeService.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun save(@Valid @RequestBody recipe: RecipeSaveDto) =
|
||||
created<Recipe>(RECIPE_CONTROLLER_PATH) {
|
||||
recipeService.save(recipe)
|
||||
}
|
||||
created<Recipe>(RECIPE_CONTROLLER_PATH) {
|
||||
recipeService.save(recipe)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun update(@Valid @RequestBody recipe: RecipeUpdateDto) =
|
||||
noContent {
|
||||
recipeService.update(recipe)
|
||||
}
|
||||
noContent {
|
||||
recipeService.update(recipe)
|
||||
}
|
||||
|
||||
@PutMapping("public")
|
||||
@PreAuthorize("hasAuthority('EDIT_RECIPES_PUBLIC_DATA')")
|
||||
fun updatePublicData(@Valid @RequestBody publicDataDto: RecipePublicDataDto) =
|
||||
noContent {
|
||||
recipeService.updatePublicData(publicDataDto)
|
||||
}
|
||||
noContent {
|
||||
recipeService.updatePublicData(publicDataDto)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorizeRemoveRecipes
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
recipeService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
recipeService.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@ -66,11 +66,11 @@ class RecipeController(private val recipeService: RecipeService) {
|
||||
class RecipeImageController(val recipeImageService: RecipeImageService) {
|
||||
@GetMapping("{recipeId}/image")
|
||||
fun getAllIdsForRecipe(@PathVariable recipeId: Long) =
|
||||
ok(recipeImageService.getAllIdsForRecipe(recipeId))
|
||||
ok(recipeImageService.getAllIdsForRecipe(recipeId))
|
||||
|
||||
@GetMapping("{recipeId}/image/{id}", produces = [MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE])
|
||||
fun getById(@PathVariable recipeId: Long, @PathVariable id: Long) =
|
||||
ok(recipeImageService.getByIdForRecipe(id, recipeId))
|
||||
ok(recipeImageService.getByIdForRecipe(id, recipeId))
|
||||
|
||||
@PostMapping("{recipeId}/image", consumes = [MediaType.MULTIPART_FORM_DATA_VALUE])
|
||||
@PreAuthorizeEditRecipes
|
||||
@ -82,9 +82,9 @@ class RecipeImageController(val recipeImageService: RecipeImageService) {
|
||||
@DeleteMapping("{recipeId}/image/{id}")
|
||||
@PreAuthorizeRemoveRecipes
|
||||
fun delete(@PathVariable recipeId: Long, @PathVariable id: Long) =
|
||||
noContent {
|
||||
recipeImageService.delete(id, recipeId)
|
||||
}
|
||||
noContent {
|
||||
recipeImageService.delete(id, recipeId)
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
@ -93,26 +93,26 @@ class RecipeImageController(val recipeImageService: RecipeImageService) {
|
||||
class MixController(private val mixService: MixService) {
|
||||
@GetMapping("{id}")
|
||||
fun getById(@PathVariable id: Long) =
|
||||
ok(mixService.getById(id))
|
||||
ok(mixService.getById(id))
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun save(@Valid @RequestBody mix: MixSaveDto) =
|
||||
created<Mix>(MIX_CONTROLLER_PATH) {
|
||||
mixService.save(mix)
|
||||
}
|
||||
created<Mix>(MIX_CONTROLLER_PATH) {
|
||||
mixService.save(mix)
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
@PreAuthorizeEditRecipes
|
||||
fun update(@Valid @RequestBody mix: MixUpdateDto) =
|
||||
noContent {
|
||||
mixService.update(mix)
|
||||
}
|
||||
noContent {
|
||||
mixService.update(mix)
|
||||
}
|
||||
|
||||
@DeleteMapping("{id}")
|
||||
@PreAuthorizeRemoveRecipes
|
||||
fun deleteById(@PathVariable id: Long) =
|
||||
noContent {
|
||||
mixService.deleteById(id)
|
||||
}
|
||||
noContent {
|
||||
mixService.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
@ -9,11 +9,11 @@ import java.net.URI
|
||||
|
||||
/** Creates a HTTP OK [ResponseEntity] from the given [body]. */
|
||||
fun <T> ok(body: T): ResponseEntity<T> =
|
||||
ResponseEntity.ok(body)
|
||||
ResponseEntity.ok(body)
|
||||
|
||||
/** Creates a HTTP OK [ResponseEntity] from the given [body] and [headers]. */
|
||||
fun <T> ok(body: T, headers: HttpHeaders): ResponseEntity<T> =
|
||||
ResponseEntity(body, headers, HttpStatus.OK)
|
||||
ResponseEntity(body, headers, HttpStatus.OK)
|
||||
|
||||
/** Executes the given [action] then returns an HTTP OK [ResponseEntity] form the given [body]. */
|
||||
fun <T> ok(action: () -> Unit): ResponseEntity<T> {
|
||||
@ -23,19 +23,19 @@ fun <T> ok(action: () -> Unit): ResponseEntity<T> {
|
||||
|
||||
/** Creates a HTTP CREATED [ResponseEntity] from the given [body] with the location set to [controllerPath]/id. */
|
||||
fun <T : Model> created(controllerPath: String, body: T): ResponseEntity<T> =
|
||||
ResponseEntity.created(URI.create("$controllerPath/${body.id}")).body(body)
|
||||
ResponseEntity.created(URI.create("$controllerPath/${body.id}")).body(body)
|
||||
|
||||
/** Creates a HTTP CREATED [ResponseEntity] with the result of the given [producer] as its body. */
|
||||
fun <T : Model> created(controllerPath: String, producer: () -> T): ResponseEntity<T> =
|
||||
created(controllerPath, producer())
|
||||
created(controllerPath, producer())
|
||||
|
||||
/** Creates a HTTP NOT FOUND [ResponseEntity]. */
|
||||
fun <T> notFound(): ResponseEntity<T> =
|
||||
ResponseEntity.notFound().build()
|
||||
ResponseEntity.notFound().build()
|
||||
|
||||
/** Creates a HTTP NO CONTENT [ResponseEntity]. */
|
||||
fun noContent(): ResponseEntity<Void> =
|
||||
ResponseEntity.noContent().build()
|
||||
ResponseEntity.noContent().build()
|
||||
|
||||
/** Executes the given [action] then returns an HTTP NO CONTENT [ResponseEntity]. */
|
||||
fun noContent(action: () -> Unit): ResponseEntity<Void> {
|
||||
@ -45,12 +45,12 @@ fun noContent(action: () -> Unit): ResponseEntity<Void> {
|
||||
|
||||
/** Creates a HTTP FORBIDDEN [ResponseEntity]. */
|
||||
fun <T> forbidden(): ResponseEntity<T> =
|
||||
ResponseEntity.status(HttpStatus.FORBIDDEN).build()
|
||||
ResponseEntity.status(HttpStatus.FORBIDDEN).build()
|
||||
|
||||
/** Creates an [HttpHeaders] instance from the given options. */
|
||||
fun httpHeaders(
|
||||
contentType: MediaType = MediaType.APPLICATION_JSON,
|
||||
op: HttpHeaders.() -> Unit = {}
|
||||
contentType: MediaType = MediaType.APPLICATION_JSON,
|
||||
op: HttpHeaders.() -> Unit = {}
|
||||
) = HttpHeaders().apply {
|
||||
this.contentType = contentType
|
||||
|
||||
|
@ -2,13 +2,11 @@ package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.blacklistedJwtTokens
|
||||
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.or
|
||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository
|
||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UserDetails
|
||||
@ -58,7 +56,7 @@ interface EmployeeService : ExternalModelService<Employee, EmployeeSaveDto, Empl
|
||||
}
|
||||
|
||||
interface EmployeeGroupService :
|
||||
ExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository> {
|
||||
ExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository> {
|
||||
/** Gets all the employees of the group with the given [id]. */
|
||||
fun getEmployeesForGroup(id: Long): Collection<Employee>
|
||||
|
||||
@ -76,67 +74,69 @@ interface EmployeeUserDetailsService : UserDetailsService {
|
||||
|
||||
@Service
|
||||
class EmployeeServiceImpl(
|
||||
employeeRepository: EmployeeRepository,
|
||||
@Lazy val passwordEncoder: PasswordEncoder
|
||||
employeeRepository: EmployeeRepository,
|
||||
@Lazy val groupService: EmployeeGroupService,
|
||||
@Lazy val passwordEncoder: PasswordEncoder,
|
||||
) : AbstractExternalModelService<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeRepository>(employeeRepository),
|
||||
EmployeeService {
|
||||
@Autowired
|
||||
lateinit var groupService: EmployeeGroupServiceImpl
|
||||
EmployeeService {
|
||||
override fun idNotFoundException(id: Long) = employeeIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = employeeIdAlreadyExistsException(id)
|
||||
|
||||
override fun existsByFirstNameAndLastName(firstName: String, lastName: String): Boolean =
|
||||
repository.existsByFirstNameAndLastName(firstName, lastName)
|
||||
repository.existsByFirstNameAndLastName(firstName, lastName)
|
||||
|
||||
override fun getAll(): Collection<Employee> =
|
||||
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
|
||||
super.getAll().filter { !it.isSystemUser && !it.isDefaultGroupUser }
|
||||
|
||||
override fun getById(id: Long): Employee =
|
||||
getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
|
||||
override fun getById(id: Long, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee =
|
||||
super.getById(id).apply {
|
||||
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser) throw EntityNotFoundException(
|
||||
id
|
||||
)
|
||||
}
|
||||
super.getById(id).apply {
|
||||
if (ignoreSystemUsers && isSystemUser || ignoreDefaultGroupUsers && isDefaultGroupUser)
|
||||
throw idNotFoundException(id)
|
||||
}
|
||||
|
||||
override fun getByGroup(group: EmployeeGroup): Collection<Employee> =
|
||||
repository.findAllByGroup(group).filter {
|
||||
!it.isSystemUser && !it.isDefaultGroupUser
|
||||
}
|
||||
repository.findAllByGroup(group).filter {
|
||||
!it.isSystemUser && !it.isDefaultGroupUser
|
||||
}
|
||||
|
||||
override fun getDefaultGroupEmployee(group: EmployeeGroup): Employee =
|
||||
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
|
||||
repository.findByIsDefaultGroupUserIsTrueAndGroupIs(group)
|
||||
|
||||
override fun save(entity: EmployeeSaveDto): Employee =
|
||||
save(with(entity) {
|
||||
Employee(
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
passwordEncoder.encode(password),
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = if (groupId != null) groupService.getById(groupId) else null,
|
||||
permissions = permissions
|
||||
)
|
||||
})
|
||||
save(with(entity) {
|
||||
Employee(
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
passwordEncoder.encode(password),
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = if (groupId != null) groupService.getById(groupId) else null,
|
||||
permissions = permissions
|
||||
)
|
||||
})
|
||||
|
||||
override fun save(entity: Employee): Employee {
|
||||
if (existsById(entity.id))
|
||||
throw employeeIdAlreadyExistsException(entity.id)
|
||||
if (existsByFirstNameAndLastName(entity.firstName, entity.lastName))
|
||||
throw EntityAlreadyExistsException("${entity.firstName} ${entity.lastName}")
|
||||
throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
||||
return super<AbstractExternalModelService>.save(entity)
|
||||
}
|
||||
|
||||
override fun saveDefaultGroupEmployee(group: EmployeeGroup) {
|
||||
save(
|
||||
Employee(
|
||||
id = 1000000L + group.id!!,
|
||||
firstName = group.name,
|
||||
lastName = "EmployeeModel",
|
||||
password = passwordEncoder.encode(group.name),
|
||||
group = group,
|
||||
isDefaultGroupUser = true
|
||||
)
|
||||
employee(
|
||||
id = 1000000L + group.id!!,
|
||||
firstName = group.name,
|
||||
lastName = "EmployeeModel",
|
||||
password = passwordEncoder.encode(group.name),
|
||||
group = group,
|
||||
isDefaultGroupUser = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -144,9 +144,9 @@ class EmployeeServiceImpl(
|
||||
val employee = getById(employeeId, ignoreDefaultGroupUsers = true, ignoreSystemUsers = false)
|
||||
employee.lastLoginTime = time
|
||||
return update(
|
||||
employee,
|
||||
ignoreDefaultGroupUsers = true,
|
||||
ignoreSystemUsers = false
|
||||
employee,
|
||||
ignoreDefaultGroupUsers = true,
|
||||
ignoreSystemUsers = false
|
||||
)
|
||||
}
|
||||
|
||||
@ -154,26 +154,26 @@ class EmployeeServiceImpl(
|
||||
val persistedEmployee by lazy { getById(entity.id) }
|
||||
return update(with(entity) {
|
||||
Employee(
|
||||
id = id,
|
||||
firstName = firstName or persistedEmployee.firstName,
|
||||
lastName = lastName or persistedEmployee.lastName,
|
||||
password = persistedEmployee.password,
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group,
|
||||
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
|
||||
lastLoginTime = persistedEmployee.lastLoginTime
|
||||
id = id,
|
||||
firstName = firstName or persistedEmployee.firstName,
|
||||
lastName = lastName or persistedEmployee.lastName,
|
||||
password = persistedEmployee.password,
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = if (entity.groupId != null) groupService.getById(entity.groupId) else persistedEmployee.group,
|
||||
permissions = permissions?.toMutableSet() ?: persistedEmployee.permissions,
|
||||
lastLoginTime = persistedEmployee.lastLoginTime
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun update(entity: Employee): Employee =
|
||||
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
update(entity, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
|
||||
override fun update(entity: Employee, ignoreDefaultGroupUsers: Boolean, ignoreSystemUsers: Boolean): Employee {
|
||||
with(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)) {
|
||||
if (this != null && id != entity.id)
|
||||
throw EntityAlreadyExistsException("${entity.firstName} ${entity.lastName}")
|
||||
throw employeeFullNameAlreadyExistsException(entity.firstName, entity.lastName)
|
||||
}
|
||||
|
||||
return super<AbstractExternalModelService>.update(entity)
|
||||
@ -183,24 +183,24 @@ class EmployeeServiceImpl(
|
||||
val persistedEmployee = getById(id, ignoreDefaultGroupUsers = true, ignoreSystemUsers = true)
|
||||
return super<AbstractExternalModelService>.update(with(persistedEmployee) {
|
||||
Employee(
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
passwordEncoder.encode(password),
|
||||
isDefaultGroupUser,
|
||||
isSystemUser,
|
||||
group,
|
||||
permissions,
|
||||
lastLoginTime
|
||||
id,
|
||||
firstName,
|
||||
lastName,
|
||||
passwordEncoder.encode(password),
|
||||
isDefaultGroupUser,
|
||||
isSystemUser,
|
||||
group,
|
||||
permissions,
|
||||
lastLoginTime
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun addPermission(employeeId: Long, permission: EmployeePermission): Employee =
|
||||
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions += permission })
|
||||
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions += permission })
|
||||
|
||||
override fun removePermission(employeeId: Long, permission: EmployeePermission): Employee =
|
||||
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions -= permission })
|
||||
super<AbstractExternalModelService>.update(getById(employeeId).apply { permissions -= permission })
|
||||
|
||||
override fun logout(request: HttpServletRequest) {
|
||||
val authorizationCookie = WebUtils.getCookie(request, "Authorization")
|
||||
@ -217,16 +217,20 @@ const val defaultGroupCookieMaxAge = 10 * 365 * 24 * 60 * 60 // 10 ans
|
||||
|
||||
@Service
|
||||
class EmployeeGroupServiceImpl(
|
||||
val employeeService: EmployeeService,
|
||||
employeeGroupRepository: EmployeeGroupRepository
|
||||
) :
|
||||
AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository>(
|
||||
employeeGroupRepository
|
||||
),
|
||||
EmployeeGroupService {
|
||||
private val employeeService: EmployeeService,
|
||||
employeeGroupRepository: EmployeeGroupRepository
|
||||
) : AbstractExternalNamedModelService<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupRepository>(
|
||||
employeeGroupRepository
|
||||
),
|
||||
EmployeeGroupService {
|
||||
override fun idNotFoundException(id: Long) = employeeGroupIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = employeeGroupIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = employeeGroupNameNotFoundException(name)
|
||||
override fun nameAlreadyExistsException(name: String) = employeeGroupNameAlreadyExistsException(name)
|
||||
|
||||
override fun existsByName(name: String): Boolean = repository.existsByName(name)
|
||||
override fun getEmployeesForGroup(id: Long): Collection<Employee> =
|
||||
employeeService.getByGroup(getById(id))
|
||||
employeeService.getByGroup(getById(id))
|
||||
|
||||
@Transactional
|
||||
override fun save(entity: EmployeeGroup): EmployeeGroup {
|
||||
@ -239,9 +243,9 @@ class EmployeeGroupServiceImpl(
|
||||
val persistedGroup by lazy { getById(entity.id) }
|
||||
return update(with(entity) {
|
||||
EmployeeGroup(
|
||||
entity.id,
|
||||
if (name.isNotBlank()) entity.name else persistedGroup.name,
|
||||
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
|
||||
entity.id,
|
||||
if (name.isNotBlank()) entity.name else persistedGroup.name,
|
||||
if (permissions.isNotEmpty()) entity.permissions else persistedGroup.permissions
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -254,11 +258,11 @@ class EmployeeGroupServiceImpl(
|
||||
|
||||
override fun getRequestDefaultGroup(request: HttpServletRequest): EmployeeGroup {
|
||||
val defaultGroupCookie = WebUtils.getCookie(request, defaultGroupCookieName)
|
||||
?: throw EntityNotFoundException("defaultGroup")
|
||||
?: throw NoDefaultGroupException()
|
||||
val defaultGroupUser = employeeService.getById(
|
||||
defaultGroupCookie.value.toLong(),
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = true
|
||||
defaultGroupCookie.value.toLong(),
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = true
|
||||
)
|
||||
return defaultGroupUser.group!!
|
||||
}
|
||||
@ -267,32 +271,32 @@ class EmployeeGroupServiceImpl(
|
||||
val group = getById(groupId)
|
||||
val defaultGroupUser = employeeService.getDefaultGroupEmployee(group)
|
||||
response.addHeader(
|
||||
"Set-Cookie",
|
||||
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict"
|
||||
"Set-Cookie",
|
||||
"$defaultGroupCookieName=${defaultGroupUser.id}; Max-Age=${defaultGroupCookieMaxAge}; Path=/api; HttpOnly; Secure; SameSite=strict"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
class EmployeeUserDetailsServiceImpl(
|
||||
val employeeService: EmployeeService
|
||||
private val employeeService: EmployeeService
|
||||
) :
|
||||
EmployeeUserDetailsService {
|
||||
EmployeeUserDetailsService {
|
||||
override fun loadUserByUsername(username: String): UserDetails {
|
||||
try {
|
||||
return loadUserByEmployeeId(username.toLong(), true)
|
||||
} catch (ex: EntityNotFoundException) {
|
||||
} catch (ex: NotFoundException) {
|
||||
throw UsernameNotFoundException(username)
|
||||
} catch (ex: EntityNotFoundException) {
|
||||
} catch (ex: NotFoundException) {
|
||||
throw UsernameNotFoundException(username)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadUserByEmployeeId(employeeId: Long, ignoreDefaultGroupUsers: Boolean): UserDetails {
|
||||
val employee = employeeService.getById(
|
||||
employeeId,
|
||||
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
|
||||
ignoreSystemUsers = false
|
||||
employeeId,
|
||||
ignoreDefaultGroupUsers = ignoreDefaultGroupUsers,
|
||||
ignoreSystemUsers = false
|
||||
)
|
||||
return User(employee.id.toString(), employee.password, employee.authorities)
|
||||
}
|
||||
|
@ -1,10 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
|
||||
import dev.fyloz.colorrecipesexplorer.model.Company
|
||||
import dev.fyloz.colorrecipesexplorer.model.CompanySaveDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.CompanyUpdateDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.company
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.CompanyRepository
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.stereotype.Service
|
||||
@ -16,11 +12,16 @@ interface CompanyService : ExternalNamedModelService<Company, CompanySaveDto, Co
|
||||
|
||||
@Service
|
||||
class CompanyServiceImpl(
|
||||
companyRepository: CompanyRepository,
|
||||
@Lazy val recipeService: RecipeService
|
||||
companyRepository: CompanyRepository,
|
||||
@Lazy val recipeService: RecipeService
|
||||
) :
|
||||
AbstractExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository>(companyRepository),
|
||||
CompanyService {
|
||||
AbstractExternalNamedModelService<Company, CompanySaveDto, CompanyUpdateDto, CompanyRepository>(companyRepository),
|
||||
CompanyService {
|
||||
override fun idNotFoundException(id: Long) = companyIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = companyIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = companyNameNotFoundException(name)
|
||||
override fun nameAlreadyExistsException(name: String) = companyNameAlreadyExistsException(name)
|
||||
|
||||
override fun isLinkedToRecipes(company: Company): Boolean = recipeService.existsByCompany(company)
|
||||
|
||||
override fun update(entity: CompanyUpdateDto): Company {
|
||||
@ -29,14 +30,14 @@ class CompanyServiceImpl(
|
||||
|
||||
return update(with(entity) {
|
||||
company(
|
||||
id = id,
|
||||
name = if (name != null && name.isNotBlank()) name else persistedCompany.name
|
||||
id = id,
|
||||
name = if (name != null && name.isNotBlank()) name else persistedCompany.name
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
|
||||
super.deleteById(id)
|
||||
override fun delete(entity: Company) {
|
||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteCompany(entity)
|
||||
super.delete(entity)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.LowQuantitiesException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.LowQuantityException
|
||||
import dev.fyloz.colorrecipesexplorer.model.MaterialQuantityDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixDeductDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.model.materialQuantityDto
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.service.utils.mapMayThrow
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import javax.transaction.Transactional
|
||||
|
||||
@ -29,20 +26,20 @@ interface InventoryService {
|
||||
|
||||
@Service
|
||||
class InventoryServiceImpl(
|
||||
private val materialService: MaterialService,
|
||||
private val mixService: MixService
|
||||
private val materialService: MaterialService,
|
||||
private val mixService: MixService
|
||||
) : InventoryService {
|
||||
@Transactional
|
||||
override fun add(materialQuantities: Collection<MaterialQuantityDto>) =
|
||||
materialQuantities.map {
|
||||
materialQuantityDto(materialId = it.material, quantity = add(it))
|
||||
}
|
||||
materialQuantities.map {
|
||||
materialQuantityDto(materialId = it.material, quantity = add(it))
|
||||
}
|
||||
|
||||
override fun add(materialQuantity: MaterialQuantityDto) =
|
||||
materialService.updateQuantity(
|
||||
materialService.getById(materialQuantity.material),
|
||||
materialQuantity.quantity
|
||||
)
|
||||
materialService.updateQuantity(
|
||||
materialService.getById(materialQuantity.material),
|
||||
materialQuantity.quantity
|
||||
)
|
||||
|
||||
@Transactional
|
||||
override fun deductMix(mixRatio: MixDeductDto): Collection<MaterialQuantityDto> {
|
||||
@ -51,41 +48,66 @@ class InventoryServiceImpl(
|
||||
val adjustedFirstMaterialQuantity = firstMixMaterial.quantity * mixRatio.ratio
|
||||
|
||||
fun adjustQuantity(mixMaterial: MixMaterial): Float =
|
||||
if (!mixMaterial.material.materialType!!.usePercentages)
|
||||
mixMaterial.quantity * mixRatio.ratio
|
||||
else
|
||||
(mixMaterial.quantity * adjustedFirstMaterialQuantity) / 100f
|
||||
if (!mixMaterial.material.materialType!!.usePercentages)
|
||||
mixMaterial.quantity * mixRatio.ratio
|
||||
else
|
||||
(mixMaterial.quantity * adjustedFirstMaterialQuantity) / 100f
|
||||
|
||||
return deduct(mix.mixMaterials.map {
|
||||
materialQuantityDto(
|
||||
materialId = it.material.id!!,
|
||||
quantity = adjustQuantity(it)
|
||||
materialId = it.material.id!!,
|
||||
quantity = adjustQuantity(it)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@Transactional
|
||||
override fun deduct(materialQuantities: Collection<MaterialQuantityDto>): Collection<MaterialQuantityDto> {
|
||||
val thrown = mutableListOf<MaterialQuantityDto>()
|
||||
val thrown = mutableListOf<NotEnoughInventoryException>()
|
||||
val updatedQuantities =
|
||||
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, LowQuantityException>(
|
||||
{ thrown.add(it.materialQuantity) }
|
||||
) {
|
||||
materialQuantityDto(materialId = it.material, quantity = deduct(it))
|
||||
}
|
||||
materialQuantities.mapMayThrow<MaterialQuantityDto, MaterialQuantityDto, NotEnoughInventoryException>(
|
||||
{ thrown.add(it) }
|
||||
) {
|
||||
materialQuantityDto(materialId = it.material, quantity = deduct(it))
|
||||
}
|
||||
|
||||
if (thrown.isNotEmpty()) {
|
||||
throw LowQuantitiesException(thrown)
|
||||
throw MultiplesNotEnoughInventoryException(thrown)
|
||||
}
|
||||
return updatedQuantities
|
||||
}
|
||||
|
||||
override fun deduct(materialQuantity: MaterialQuantityDto): Float =
|
||||
with(materialService.getById(materialQuantity.material)) {
|
||||
if (this.inventoryQuantity >= materialQuantity.quantity) {
|
||||
materialService.updateQuantity(this, -materialQuantity.quantity)
|
||||
} else {
|
||||
throw LowQuantityException(materialQuantity)
|
||||
}
|
||||
}
|
||||
with(materialService.getById(materialQuantity.material)) {
|
||||
if (this.inventoryQuantity >= materialQuantity.quantity) {
|
||||
materialService.updateQuantity(this, -materialQuantity.quantity)
|
||||
} else {
|
||||
throw NotEnoughInventoryException(materialQuantity.quantity, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotEnoughInventoryException(quantity: Float, material: Material) :
|
||||
RestException(
|
||||
"notenoughinventory",
|
||||
"Not enough inventory",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot deduct ${quantity}mL of ${material.name} because there is only ${material.inventoryQuantity}mL in inventory",
|
||||
mapOf(
|
||||
"material" to material.name,
|
||||
"materialId" to material.id.toString(),
|
||||
"requestQuantity" to quantity,
|
||||
"availableQuantity" to material.inventoryQuantity
|
||||
)
|
||||
)
|
||||
|
||||
class MultiplesNotEnoughInventoryException(exceptions: List<NotEnoughInventoryException>) :
|
||||
RestException(
|
||||
"notenoughinventory-multiple",
|
||||
"Not enough inventory",
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"Cannot deduct requested quantities because there is no enough of them in inventory",
|
||||
mapOf(
|
||||
"lowQuantities" to exceptions.map { it.extensions }
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.SimdutService
|
||||
@ -9,7 +9,7 @@ import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
interface MaterialService :
|
||||
ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository> {
|
||||
ExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository> {
|
||||
/** Checks if a material with the given [materialType] exists. */
|
||||
fun existsByMaterialType(materialType: MaterialType): Boolean
|
||||
|
||||
@ -37,39 +37,44 @@ interface MaterialService :
|
||||
|
||||
@Service
|
||||
class MaterialServiceImpl(
|
||||
materialRepository: MaterialRepository,
|
||||
val simdutService: SimdutService,
|
||||
val recipeService: RecipeService,
|
||||
val mixService: MixService,
|
||||
@Lazy val materialTypeService: MaterialTypeService
|
||||
materialRepository: MaterialRepository,
|
||||
val simdutService: SimdutService,
|
||||
val recipeService: RecipeService,
|
||||
val mixService: MixService,
|
||||
@Lazy val materialTypeService: MaterialTypeService
|
||||
) :
|
||||
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository>(
|
||||
materialRepository
|
||||
),
|
||||
MaterialService {
|
||||
AbstractExternalNamedModelService<Material, MaterialSaveDto, MaterialUpdateDto, MaterialRepository>(
|
||||
materialRepository
|
||||
),
|
||||
MaterialService {
|
||||
override fun idNotFoundException(id: Long) = materialIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = materialIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = materialNameNotFoundException(name)
|
||||
override fun nameAlreadyExistsException(name: String) = materialNameAlreadyExistsException(name)
|
||||
|
||||
override fun existsByMaterialType(materialType: MaterialType): Boolean =
|
||||
repository.existsByMaterialType(materialType)
|
||||
repository.existsByMaterialType(materialType)
|
||||
|
||||
override fun hasSimdut(id: Long): Boolean = simdutService.exists(getById(id))
|
||||
override fun getSimdut(id: Long): ByteArray = simdutService.read(getById(id))
|
||||
override fun getAllNotMixType(): Collection<Material> = getAll().filter { !it.isMixType }
|
||||
|
||||
override fun getAllIdsWithSimdut(): Collection<Long> =
|
||||
getAllNotMixType()
|
||||
.filter { simdutService.exists(it) }
|
||||
.map { it.id!! }
|
||||
getAllNotMixType()
|
||||
.filter { simdutService.exists(it) }
|
||||
.map { it.id!! }
|
||||
|
||||
override fun save(entity: MaterialSaveDto): Material =
|
||||
save(with(entity) {
|
||||
material(
|
||||
name = entity.name,
|
||||
inventoryQuantity = entity.inventoryQuantity,
|
||||
materialType = materialTypeService.getById(materialTypeId),
|
||||
isMixType = false
|
||||
)
|
||||
}).apply {
|
||||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.write(this, entity.simdutFile)
|
||||
}
|
||||
save(with(entity) {
|
||||
material(
|
||||
name = entity.name,
|
||||
inventoryQuantity = entity.inventoryQuantity,
|
||||
materialType = materialTypeService.getById(materialTypeId),
|
||||
isMixType = false
|
||||
)
|
||||
}).apply {
|
||||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.write(this, entity.simdutFile)
|
||||
}
|
||||
|
||||
override fun update(entity: MaterialUpdateDto): Material {
|
||||
val persistedMaterial by lazy {
|
||||
@ -78,11 +83,11 @@ class MaterialServiceImpl(
|
||||
|
||||
return update(with(entity) {
|
||||
material(
|
||||
id = id,
|
||||
name = if (name != null && name.isNotBlank()) name else persistedMaterial.name,
|
||||
inventoryQuantity = if (inventoryQuantity != null && inventoryQuantity != Float.MIN_VALUE) inventoryQuantity else persistedMaterial.inventoryQuantity,
|
||||
isMixType = persistedMaterial.isMixType,
|
||||
materialType = if (materialTypeId != null) materialTypeService.getById(materialTypeId) else persistedMaterial.materialType
|
||||
id = id,
|
||||
name = if (name != null && name.isNotBlank()) name else persistedMaterial.name,
|
||||
inventoryQuantity = if (inventoryQuantity != null && inventoryQuantity != Float.MIN_VALUE) inventoryQuantity else persistedMaterial.inventoryQuantity,
|
||||
isMixType = persistedMaterial.isMixType,
|
||||
materialType = if (materialTypeId != null) materialTypeService.getById(materialTypeId) else persistedMaterial.materialType
|
||||
)
|
||||
}).apply {
|
||||
if (entity.simdutFile != null && !entity.simdutFile.isEmpty) simdutService.update(entity.simdutFile, this)
|
||||
@ -98,23 +103,23 @@ class MaterialServiceImpl(
|
||||
override fun getAllForMixCreation(recipeId: Long): Collection<Material> {
|
||||
val recipesMixTypes = recipeService.getById(recipeId).mixTypes
|
||||
return getAll()
|
||||
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
||||
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
||||
}
|
||||
|
||||
override fun getAllForMixUpdate(mixId: Long): Collection<Material> {
|
||||
val mix = mixService.getById(mixId)
|
||||
val recipesMixTypes = mix.recipe.mixTypes
|
||||
return getAll()
|
||||
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
||||
.filter { it.id != mix.mixType.material.id }
|
||||
.filter { !it.isMixType || recipesMixTypes.any { mixType -> mixType.material.id == it.id } }
|
||||
.filter { it.id != mix.mixType.material.id }
|
||||
}
|
||||
|
||||
private fun assertPersistedMaterial(material: Material) {
|
||||
Assert.notNull(material.name, "The persisted material with the id ${material.id} has a null name")
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
|
||||
super.deleteById(id)
|
||||
override fun delete(entity: Material) {
|
||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialException(entity)
|
||||
super.delete(entity)
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,14 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.config.properties.MaterialTypeProperties
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.MaterialType
|
||||
import dev.fyloz.colorrecipesexplorer.model.MaterialTypeSaveDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.MaterialTypeUpdateDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.materialType
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.isNotNullAndNotBlank
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
|
||||
interface MaterialTypeService :
|
||||
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository> {
|
||||
ExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository> {
|
||||
/** Checks if a material type with the given [prefix] exists. */
|
||||
fun existsByPrefix(prefix: String): Boolean
|
||||
|
||||
@ -35,32 +27,36 @@ interface MaterialTypeService :
|
||||
|
||||
@Service
|
||||
class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val materialService: MaterialService) :
|
||||
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository>(
|
||||
repository
|
||||
), MaterialTypeService {
|
||||
AbstractExternalNamedModelService<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeRepository>(
|
||||
repository
|
||||
), MaterialTypeService {
|
||||
override fun idNotFoundException(id: Long) = materialTypeIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = materialIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = materialTypeNameNotFoundException(name)
|
||||
override fun nameAlreadyExistsException(name: String) = materialTypeNameAlreadyExistsException(name)
|
||||
|
||||
override fun existsByPrefix(prefix: String): Boolean = repository.existsByPrefix(prefix)
|
||||
override fun isUsedByMaterial(materialType: MaterialType): Boolean =
|
||||
materialService.existsByMaterialType(materialType)
|
||||
materialService.existsByMaterialType(materialType)
|
||||
|
||||
override fun getAllSystemTypes(): Collection<MaterialType> = repository.findAllBySystemTypeIs(true)
|
||||
override fun getAllNonSystemType(): Collection<MaterialType> = repository.findAllBySystemTypeIs(false)
|
||||
|
||||
override fun save(entity: MaterialType): MaterialType {
|
||||
if (existsByPrefix(entity.prefix))
|
||||
throw EntityAlreadyExistsException(entity.prefix)
|
||||
throw materialTypePrefixAlreadyExistsException(entity.prefix)
|
||||
return super<AbstractExternalNamedModelService>.save(entity)
|
||||
}
|
||||
|
||||
@ExperimentalContracts
|
||||
override fun update(entity: MaterialTypeUpdateDto): MaterialType {
|
||||
val persistedMaterialType by lazy { getById(entity.id) }
|
||||
|
||||
return update(with(entity) {
|
||||
MaterialType(
|
||||
id = id,
|
||||
name = if (isNotNullAndNotBlank(name)) name else persistedMaterialType.name,
|
||||
prefix = if (isNotNullAndNotBlank(prefix)) prefix else persistedMaterialType.prefix,
|
||||
systemType = false
|
||||
id = id,
|
||||
name = if (isNotNullAndNotBlank(name)) name else persistedMaterialType.name,
|
||||
prefix = if (isNotNullAndNotBlank(prefix)) prefix else persistedMaterialType.prefix,
|
||||
systemType = false
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -68,15 +64,15 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
|
||||
override fun update(entity: MaterialType): MaterialType {
|
||||
with(repository.findByPrefix(entity.prefix)) {
|
||||
if (this != null && id != entity.id)
|
||||
throw EntityAlreadyExistsException(entity.prefix)
|
||||
throw materialTypePrefixAlreadyExistsException(entity.prefix)
|
||||
}
|
||||
|
||||
return super<AbstractExternalNamedModelService>.update(entity)
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
|
||||
super.deleteById(id)
|
||||
override fun delete(entity: MaterialType) {
|
||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMaterialTypeException(entity)
|
||||
super.delete(entity)
|
||||
}
|
||||
|
||||
override fun saveSystemTypes(systemTypeProperties: Collection<MaterialTypeProperties.MaterialTypeProperty>) {
|
||||
@ -102,9 +98,3 @@ class MaterialTypeServiceImpl(repository: MaterialTypeRepository, private val ma
|
||||
oldSystemTypes.forEach { update(materialType(it, newSystemType = false)) }
|
||||
}
|
||||
}
|
||||
|
||||
@ResponseStatus(HttpStatus.CONFLICT)
|
||||
class CannotDeleteUsedMaterialTypeRestException :
|
||||
RestException("Cannot delete a used material type", HttpStatus.CONFLICT) {
|
||||
override fun buildBody(): RestExceptionBody = object : RestExceptionBody() {}
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.model.MixMaterialDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.mixMaterial
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixMaterialRepository
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.stereotype.Service
|
||||
@ -24,22 +21,25 @@ interface MixMaterialService : ModelService<MixMaterial, MixMaterialRepository>
|
||||
|
||||
@Service
|
||||
class MixMaterialServiceImpl(
|
||||
mixMaterialRepository: MixMaterialRepository,
|
||||
@Lazy val materialService: MaterialService
|
||||
mixMaterialRepository: MixMaterialRepository,
|
||||
@Lazy val materialService: MaterialService
|
||||
) : AbstractModelService<MixMaterial, MixMaterialRepository>(mixMaterialRepository), MixMaterialService {
|
||||
override fun idNotFoundException(id: Long) = mixMaterialIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = mixMaterialIdAlreadyExistsException(id)
|
||||
|
||||
override fun existsByMaterial(material: Material): Boolean = repository.existsByMaterial(material)
|
||||
override fun create(mixMaterials: Set<MixMaterialDto>): Set<MixMaterial> =
|
||||
mixMaterials.map(::create).toSet()
|
||||
mixMaterials.map(::create).toSet()
|
||||
|
||||
override fun create(mixMaterial: MixMaterialDto): MixMaterial =
|
||||
mixMaterial(
|
||||
material = materialService.getById(mixMaterial.materialId),
|
||||
quantity = mixMaterial.quantity,
|
||||
position = mixMaterial.position
|
||||
)
|
||||
mixMaterial(
|
||||
material = materialService.getById(mixMaterial.materialId),
|
||||
quantity = mixMaterial.quantity,
|
||||
position = mixMaterial.position
|
||||
)
|
||||
|
||||
override fun updateQuantity(mixMaterial: MixMaterial, quantity: Float) =
|
||||
update(mixMaterial.apply {
|
||||
this.quantity = quantity
|
||||
})
|
||||
update(mixMaterial.apply {
|
||||
this.quantity = quantity
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixRepository
|
||||
import org.springframework.context.annotation.Lazy
|
||||
@ -23,13 +23,16 @@ interface MixService : ExternalModelService<Mix, MixSaveDto, MixUpdateDto, MixRe
|
||||
|
||||
@Service
|
||||
class MixServiceImpl(
|
||||
mixRepository: MixRepository,
|
||||
@Lazy val recipeService: RecipeService,
|
||||
@Lazy val materialTypeService: MaterialTypeService,
|
||||
val mixMaterialService: MixMaterialService,
|
||||
val mixTypeService: MixTypeService
|
||||
mixRepository: MixRepository,
|
||||
@Lazy val recipeService: RecipeService,
|
||||
@Lazy val materialTypeService: MaterialTypeService,
|
||||
val mixMaterialService: MixMaterialService,
|
||||
val mixTypeService: MixTypeService
|
||||
) : AbstractModelService<Mix, MixRepository>(mixRepository),
|
||||
MixService {
|
||||
MixService {
|
||||
override fun idNotFoundException(id: Long) = mixIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = mixIdAlreadyExistsException(id)
|
||||
|
||||
override fun getAllByMixType(mixType: MixType): Collection<Mix> = repository.findAllByMixType(mixType)
|
||||
override fun mixTypeIsShared(mixType: MixType): Boolean = getAllByMixType(mixType).count() > 1
|
||||
|
||||
@ -81,12 +84,8 @@ class MixServiceImpl(
|
||||
|
||||
@Transactional
|
||||
override fun delete(entity: Mix) {
|
||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixException(entity)
|
||||
recipeService.removeMix(entity)
|
||||
super.delete(entity)
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
|
||||
super.deleteById(id)
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.CannotDeleteEntityException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
|
||||
import org.springframework.context.annotation.Lazy
|
||||
@ -30,55 +27,60 @@ interface MixTypeService : NamedModelService<MixType, MixTypeRepository> {
|
||||
|
||||
@Service
|
||||
class MixTypeServiceImpl(
|
||||
mixTypeRepository: MixTypeRepository,
|
||||
@Lazy val materialService: MaterialService,
|
||||
@Lazy val mixService: MixService
|
||||
mixTypeRepository: MixTypeRepository,
|
||||
@Lazy val materialService: MaterialService,
|
||||
@Lazy val mixService: MixService
|
||||
) :
|
||||
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeService {
|
||||
AbstractNamedModelService<MixType, MixTypeRepository>(mixTypeRepository), MixTypeService {
|
||||
override fun idNotFoundException(id: Long) = mixTypeIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = mixTypeIdAlreadyExistsException(id)
|
||||
override fun nameNotFoundException(name: String) = mixTypeNameNotFoundException(name)
|
||||
override fun nameAlreadyExistsException(name: String) = mixTypeNameAlreadyExistsException(name)
|
||||
|
||||
override fun existsByNameAndMaterialType(name: String, materialType: MaterialType): Boolean =
|
||||
repository.existsByNameAndMaterialType(name, materialType)
|
||||
repository.existsByNameAndMaterialType(name, materialType)
|
||||
|
||||
override fun getByMaterial(material: Material): MixType =
|
||||
repository.findByMaterial(material) ?: throw EntityNotFoundException(material.name)
|
||||
repository.findByMaterial(material) ?: throw nameNotFoundException(material.name)
|
||||
|
||||
override fun getByNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
||||
repository.findByNameAndMaterialType(name, materialType)
|
||||
?: throw EntityNotFoundException("$name/${materialType.name}")
|
||||
repository.findByNameAndMaterialType(name, materialType)
|
||||
?: throw MixTypeNameAndMaterialTypeNotFoundException(name, materialType)
|
||||
|
||||
override fun getOrCreateForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
||||
if (existsByNameAndMaterialType(name, materialType))
|
||||
getByNameAndMaterialType(name, materialType)
|
||||
else
|
||||
saveForNameAndMaterialType(name, materialType)
|
||||
if (existsByNameAndMaterialType(name, materialType))
|
||||
getByNameAndMaterialType(name, materialType)
|
||||
else
|
||||
saveForNameAndMaterialType(name, materialType)
|
||||
|
||||
override fun save(entity: MixType): MixType {
|
||||
if (materialService.existsByName(entity.name))
|
||||
throw EntityAlreadyExistsException(entity.name)
|
||||
throw materialNameAlreadyExistsException(entity.name)
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
override fun saveForNameAndMaterialType(name: String, materialType: MaterialType): MixType =
|
||||
save(
|
||||
mixType(
|
||||
name = name,
|
||||
material = material(
|
||||
name = name,
|
||||
inventoryQuantity = Float.MIN_VALUE,
|
||||
isMixType = true,
|
||||
materialType = materialType
|
||||
)
|
||||
)
|
||||
save(
|
||||
mixType(
|
||||
name = name,
|
||||
material = material(
|
||||
name = name,
|
||||
inventoryQuantity = Float.MIN_VALUE,
|
||||
isMixType = true,
|
||||
materialType = materialType
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
override fun updateForNameAndMaterialType(mixType: MixType, name: String, materialType: MaterialType): MixType =
|
||||
update(mixType.apply {
|
||||
this.name = name
|
||||
material.name = name
|
||||
material.materialType = materialType
|
||||
})
|
||||
update(mixType.apply {
|
||||
this.name = name
|
||||
material.name = name
|
||||
material.materialType = materialType
|
||||
})
|
||||
|
||||
override fun deleteById(id: Long) {
|
||||
if (!repository.canBeDeleted(id)) throw CannotDeleteEntityException(id)
|
||||
super.deleteById(id)
|
||||
override fun delete(entity: MixType) {
|
||||
if (!repository.canBeDeleted(entity.id!!)) throw cannotDeleteMixTypeException(entity)
|
||||
super.delete(entity)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.model.validation.or
|
||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
||||
@ -31,13 +31,16 @@ interface RecipeService : ExternalModelService<Recipe, RecipeSaveDto, RecipeUpda
|
||||
|
||||
@Service
|
||||
class RecipeServiceImpl(
|
||||
recipeRepository: RecipeRepository,
|
||||
val companyService: CompanyService,
|
||||
val mixService: MixService,
|
||||
@Lazy val groupService: EmployeeGroupService
|
||||
recipeRepository: RecipeRepository,
|
||||
val companyService: CompanyService,
|
||||
val mixService: MixService,
|
||||
@Lazy val groupService: EmployeeGroupService
|
||||
) :
|
||||
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(recipeRepository),
|
||||
RecipeService {
|
||||
AbstractExternalModelService<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeRepository>(recipeRepository),
|
||||
RecipeService {
|
||||
override fun idNotFoundException(id: Long) = recipeIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = recipeIdAlreadyExistsException(id)
|
||||
|
||||
override fun existsByCompany(company: Company): Boolean = repository.existsByCompany(company)
|
||||
override fun getAllByCompany(company: Company): Collection<Recipe> = repository.findAllByCompany(company)
|
||||
|
||||
@ -45,14 +48,14 @@ class RecipeServiceImpl(
|
||||
// TODO checks if name is unique in the scope of the [company]
|
||||
return save(with(entity) {
|
||||
recipe(
|
||||
name = name,
|
||||
description = description,
|
||||
color = color,
|
||||
gloss = gloss,
|
||||
sample = sample,
|
||||
approbationDate = approbationDate,
|
||||
remark = remark ?: "",
|
||||
company = companyService.getById(companyId)
|
||||
name = name,
|
||||
description = description,
|
||||
color = color,
|
||||
gloss = gloss,
|
||||
sample = sample,
|
||||
approbationDate = approbationDate,
|
||||
remark = remark ?: "",
|
||||
company = companyService.getById(companyId)
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -63,17 +66,17 @@ class RecipeServiceImpl(
|
||||
|
||||
return update(with(entity) {
|
||||
recipe(
|
||||
id = id,
|
||||
name = name or persistedRecipe.name,
|
||||
description = description or persistedRecipe.description,
|
||||
color = color or persistedRecipe.color,
|
||||
gloss = gloss ?: persistedRecipe.gloss,
|
||||
sample = sample ?: persistedRecipe.sample,
|
||||
approbationDate = approbationDate ?: persistedRecipe.approbationDate,
|
||||
remark = remark or persistedRecipe.remark,
|
||||
company = persistedRecipe.company,
|
||||
mixes = persistedRecipe.mixes,
|
||||
groupsInformation = updateGroupsInformationSteps(persistedRecipe, entity.steps)
|
||||
id = id,
|
||||
name = name or persistedRecipe.name,
|
||||
description = description or persistedRecipe.description,
|
||||
color = color or persistedRecipe.color,
|
||||
gloss = gloss ?: persistedRecipe.gloss,
|
||||
sample = sample ?: persistedRecipe.sample,
|
||||
approbationDate = approbationDate ?: persistedRecipe.approbationDate,
|
||||
remark = remark or persistedRecipe.remark,
|
||||
company = persistedRecipe.company,
|
||||
mixes = persistedRecipe.mixes,
|
||||
groupsInformation = updateGroupsInformationSteps(persistedRecipe, entity.steps)
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -85,17 +88,17 @@ class RecipeServiceImpl(
|
||||
steps.forEach {
|
||||
with(recipe.groupInformationForGroup(it.groupId)) {
|
||||
updatedGroupsInformation.add(
|
||||
this?.apply {
|
||||
if (this.steps != null) {
|
||||
this.steps!!.clear()
|
||||
this.steps!!.addAll(it.steps)
|
||||
} else {
|
||||
this.steps = it.steps.toMutableSet()
|
||||
}
|
||||
} ?: recipeGroupInformation(
|
||||
group = groupService.getById(it.groupId),
|
||||
steps = it.steps.toMutableSet()
|
||||
)
|
||||
this?.apply {
|
||||
if (this.steps != null) {
|
||||
this.steps!!.clear()
|
||||
this.steps!!.addAll(it.steps)
|
||||
} else {
|
||||
this.steps = it.steps.toMutableSet()
|
||||
}
|
||||
} ?: recipeGroupInformation(
|
||||
group = groupService.getById(it.groupId),
|
||||
steps = it.steps.toMutableSet()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -108,7 +111,7 @@ class RecipeServiceImpl(
|
||||
val recipe = getById(publicDataDto.recipeId)
|
||||
|
||||
fun noteForGroup(group: EmployeeGroup) =
|
||||
publicDataDto.notes.firstOrNull { it.groupId == group.id }?.content
|
||||
publicDataDto.notes.firstOrNull { it.groupId == group.id }?.content
|
||||
|
||||
// Notes
|
||||
recipe.groupsInformation.map {
|
||||
@ -127,10 +130,10 @@ class RecipeServiceImpl(
|
||||
}
|
||||
|
||||
override fun addMix(recipe: Recipe, mix: Mix) =
|
||||
update(recipe.apply { mixes.add(mix) })
|
||||
update(recipe.apply { mixes.add(mix) })
|
||||
|
||||
override fun removeMix(mix: Mix): Recipe =
|
||||
update(mix.recipe.apply { mixes.remove(mix) })
|
||||
update(mix.recipe.apply { mixes.remove(mix) })
|
||||
}
|
||||
|
||||
const val RECIPE_IMAGES_DIRECTORY = "images/recipe"
|
||||
@ -151,11 +154,11 @@ interface RecipeImageService {
|
||||
@Service
|
||||
class RecipeImageServiceImpl(val recipeService: RecipeService, val fileService: FileService) : RecipeImageService {
|
||||
override fun getByIdForRecipe(id: Long, recipeId: Long): ByteArray =
|
||||
try {
|
||||
fileService.readAsBytes(getPath(id, recipeId))
|
||||
} catch (ex: NoSuchFileException) {
|
||||
throw EntityNotFoundException("$recipeId/$id")
|
||||
}
|
||||
try {
|
||||
fileService.readAsBytes(getPath(id, recipeId))
|
||||
} catch (ex: NoSuchFileException) {
|
||||
throw RecipeImageNotFoundException(id, recipeService.getById(recipeId))
|
||||
}
|
||||
|
||||
override fun getAllIdsForRecipe(recipeId: Long): Collection<Long> {
|
||||
val recipe = recipeService.getById(recipeId)
|
||||
@ -164,19 +167,19 @@ class RecipeImageServiceImpl(val recipeService: RecipeService, val fileService:
|
||||
return listOf()
|
||||
}
|
||||
return recipeDirectory.listFiles()!! // Should never be null because we check if recipeDirectory is a directory and exists before
|
||||
.filterNotNull()
|
||||
.map { it.name.toLong() }
|
||||
.filterNotNull()
|
||||
.map { it.name.toLong() }
|
||||
}
|
||||
|
||||
override fun save(image: MultipartFile, recipeId: Long): Long {
|
||||
/** Gets the next id available for a new image for the recipe with the given [recipeId]. */
|
||||
fun getNextAvailableId(): Long =
|
||||
with(getAllIdsForRecipe(recipeId)) {
|
||||
if (isEmpty())
|
||||
0
|
||||
else
|
||||
maxOrNull()!! + 1L // maxOrNull() cannot return null because existingIds cannot be empty at this point
|
||||
}
|
||||
with(getAllIdsForRecipe(recipeId)) {
|
||||
if (isEmpty())
|
||||
0
|
||||
else
|
||||
maxOrNull()!! + 1L // maxOrNull() cannot return null because existingIds cannot be empty at this point
|
||||
}
|
||||
|
||||
val nextAvailableId = getNextAvailableId()
|
||||
fileService.write(image, getPath(nextAvailableId, recipeId))
|
||||
@ -184,7 +187,7 @@ class RecipeImageServiceImpl(val recipeService: RecipeService, val fileService:
|
||||
}
|
||||
|
||||
override fun delete(id: Long, recipeId: Long) =
|
||||
fileService.delete(getPath(id, recipeId))
|
||||
fileService.delete(getPath(id, recipeId))
|
||||
|
||||
/** Gets the images directory of the recipe with the given [recipeId]. */
|
||||
fun getRecipeDirectory(recipeId: Long) = File(fileService.getPath("$RECIPE_IMAGES_DIRECTORY/$recipeId"))
|
||||
|
@ -1,6 +1,8 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.model.RecipeStep
|
||||
import dev.fyloz.colorrecipesexplorer.model.recipeStepIdAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.recipeStepIdNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@ -8,5 +10,8 @@ interface RecipeStepService : ModelService<RecipeStep, RecipeStepRepository>
|
||||
|
||||
@Service
|
||||
class RecipeStepServiceImpl(recipeStepRepository: RecipeStepRepository) :
|
||||
AbstractModelService<RecipeStep, RecipeStepRepository>(recipeStepRepository),
|
||||
RecipeStepService
|
||||
AbstractModelService<RecipeStep, RecipeStepRepository>(recipeStepRepository),
|
||||
RecipeStepService {
|
||||
override fun idNotFoundException(id: Long) = recipeStepIdNotFoundException(id)
|
||||
override fun idAlreadyExistsException(id: Long) = recipeStepIdAlreadyExistsException(id)
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.EntityDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.Model
|
||||
import dev.fyloz.colorrecipesexplorer.model.NamedModel
|
||||
@ -51,9 +51,6 @@ interface NamedModelService<E : NamedModel, R : JpaRepository<E, *>> : ModelServ
|
||||
|
||||
/** Gets the entity with the given [name]. */
|
||||
fun getByName(name: String): E
|
||||
|
||||
/** Deletes the entity with the given [name]. */
|
||||
fun deleteByName(name: String)
|
||||
}
|
||||
|
||||
|
||||
@ -65,25 +62,28 @@ abstract class AbstractService<E, R : JpaRepository<E, *>>(override val reposito
|
||||
}
|
||||
|
||||
abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repository: R) :
|
||||
AbstractService<E, R>(repository), ModelService<E, R> {
|
||||
AbstractService<E, R>(repository), ModelService<E, R> {
|
||||
protected abstract fun idNotFoundException(id: Long): NotFoundException
|
||||
protected abstract fun idAlreadyExistsException(id: Long): AlreadyExistsException
|
||||
|
||||
override fun existsById(id: Long): Boolean = repository.existsById(id)
|
||||
override fun getById(id: Long): E = repository.findByIdOrNull(id) ?: throw EntityNotFoundException(id)
|
||||
override fun getById(id: Long): E = repository.findByIdOrNull(id) ?: throw idNotFoundException(id)
|
||||
|
||||
override fun save(entity: E): E {
|
||||
if (entity.id != null && existsById(entity.id!!))
|
||||
throw EntityAlreadyExistsException(entity.id!!)
|
||||
throw idAlreadyExistsException(entity.id!!)
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
override fun update(entity: E): E {
|
||||
assertId(entity.id)
|
||||
if (!existsById(entity.id!!))
|
||||
throw EntityNotFoundException(entity.id!!)
|
||||
throw idNotFoundException(entity.id!!)
|
||||
return super.update(entity)
|
||||
}
|
||||
|
||||
override fun deleteById(id: Long) =
|
||||
delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing
|
||||
delete(getById(id)) // Use delete(entity) to prevent code duplication and to ease testing
|
||||
|
||||
protected fun assertId(id: Long?) {
|
||||
Assert.notNull(id, "${javaClass.simpleName}.update() was called with a null identifier")
|
||||
@ -91,14 +91,16 @@ abstract class AbstractModelService<E : Model, R : JpaRepository<E, Long>>(repos
|
||||
}
|
||||
|
||||
abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<E>>(repository: R) :
|
||||
AbstractModelService<E, R>(repository), NamedModelService<E, R> {
|
||||
AbstractModelService<E, R>(repository), NamedModelService<E, R> {
|
||||
protected abstract fun nameNotFoundException(name: String): NotFoundException
|
||||
protected abstract fun nameAlreadyExistsException(name: String): AlreadyExistsException
|
||||
|
||||
override fun existsByName(name: String): Boolean = repository.existsByName(name)
|
||||
override fun getByName(name: String): E = repository.findByName(name) ?: throw EntityNotFoundException(name)
|
||||
override fun deleteByName(name: String) = repository.deleteByName(name)
|
||||
override fun getByName(name: String): E = repository.findByName(name) ?: throw nameNotFoundException(name)
|
||||
|
||||
override fun save(entity: E): E {
|
||||
if (existsByName(entity.name))
|
||||
throw EntityAlreadyExistsException(entity.name)
|
||||
throw nameAlreadyExistsException(entity.name)
|
||||
return super.save(entity)
|
||||
}
|
||||
|
||||
@ -107,7 +109,7 @@ abstract class AbstractNamedModelService<E : NamedModel, R : NamedJpaRepository<
|
||||
assertName(entity.name)
|
||||
with(repository.findByName(entity.name)) {
|
||||
if (this != null && id != entity.id)
|
||||
throw EntityAlreadyExistsException(entity.name)
|
||||
throw nameAlreadyExistsException(entity.name)
|
||||
}
|
||||
return super.update(entity)
|
||||
}
|
||||
@ -134,23 +136,23 @@ interface ExternalService<E, S : EntityDto<E>, U : EntityDto<E>, R : JpaReposito
|
||||
|
||||
/** An [ExternalService] for entities implementing the [Model] interface. */
|
||||
interface ExternalModelService<E : Model, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>> :
|
||||
ModelService<E, R>, ExternalService<E, S, U, R>
|
||||
ModelService<E, R>, ExternalService<E, S, U, R>
|
||||
|
||||
/** An [ExternalService] for entities implementing the [NamedModel] interface. */
|
||||
interface ExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>> :
|
||||
NamedModelService<E, R>, ExternalModelService<E, S, U, R>
|
||||
NamedModelService<E, R>, ExternalModelService<E, S, U, R>
|
||||
|
||||
/** An [AbstractService] with the functionalities of a [ExternalService]. */
|
||||
@Suppress("unused")
|
||||
abstract class AbstractExternalService<E, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, *>>(repository: R) :
|
||||
AbstractService<E, R>(repository), ExternalService<E, S, U, R>
|
||||
AbstractService<E, R>(repository), ExternalService<E, S, U, R>
|
||||
|
||||
/** An [AbstractModelService] with the functionalities of a [ExternalService]. */
|
||||
abstract class AbstractExternalModelService<E : Model, S : EntityDto<E>, U : EntityDto<E>, R : JpaRepository<E, Long>>(
|
||||
repository: R
|
||||
repository: R
|
||||
) : AbstractModelService<E, R>(repository), ExternalModelService<E, S, U, R>
|
||||
|
||||
/** An [AbstractNamedModelService] with the functionalities of a [ExternalService]. */
|
||||
abstract class AbstractExternalNamedModelService<E : NamedModel, S : EntityDto<E>, U : EntityDto<E>, R : NamedJpaRepository<E>>(
|
||||
repository: R
|
||||
repository: R
|
||||
) : AbstractNamedModelService<E, R>(repository), ExternalNamedModelService<E, S, U, R>
|
||||
|
@ -10,13 +10,12 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
@Service
|
||||
class FileService(
|
||||
private val resourcesLoader: ResourceLoader,
|
||||
private val creProperties: CreProperties,
|
||||
private val logger: Logger
|
||||
private val resourcesLoader: ResourceLoader,
|
||||
private val creProperties: CreProperties,
|
||||
private val logger: Logger
|
||||
) {
|
||||
/** Reads the resource at the given [path] as a [String]. */
|
||||
fun readResource(path: String): String = try {
|
||||
@ -35,18 +34,18 @@ class FileService(
|
||||
|
||||
/** Reads the file at the given [path] as a [ByteArray]. */
|
||||
fun readAsBytes(path: String) =
|
||||
withFileAt(path) { this.readBytes() }
|
||||
withFileAt(path) { this.readBytes() }
|
||||
|
||||
/** Writes the given [multipartFile] to the file at the given [path]. */
|
||||
fun write(multipartFile: MultipartFile, path: String): Boolean =
|
||||
if (multipartFile.size <= 0) true
|
||||
else try {
|
||||
multipartFile.transferTo(create(path).toPath())
|
||||
true
|
||||
} catch (ex: IOException) {
|
||||
logger.error("Unable to write multipart file", ex)
|
||||
false
|
||||
}
|
||||
if (multipartFile.size <= 0) true
|
||||
else try {
|
||||
multipartFile.transferTo(create(path).toPath())
|
||||
true
|
||||
} catch (ex: IOException) {
|
||||
logger.error("Unable to write multipart file", ex)
|
||||
false
|
||||
}
|
||||
|
||||
/** Creates a new file at the given [path]. If the file already exists, nothing will be done. */
|
||||
fun create(path: String) = withFileAt(path) {
|
||||
@ -77,8 +76,8 @@ class FileService(
|
||||
|
||||
/** Runs the given [block] in the context of a file with the given [path]. */
|
||||
fun <T> withFileAt(path: String, block: File.() -> T) =
|
||||
File(path).block()
|
||||
File(path).block()
|
||||
|
||||
fun getPath(fileName: String): String =
|
||||
"${creProperties.workingDirectory}/$fileName"
|
||||
"${creProperties.workingDirectory}/$fileName"
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import dev.fyloz.colorrecipesexplorer.exception.SimdutWriteException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.colorrecipesexplorer.service.MaterialService
|
||||
import org.slf4j.Logger
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.io.IOException
|
||||
@ -13,12 +12,12 @@ const val SIMDUT_DIRECTORY = "simdut"
|
||||
|
||||
@Service
|
||||
class SimdutService(
|
||||
private val fileService: FileService,
|
||||
private val logger: Logger
|
||||
private val fileService: FileService,
|
||||
private val logger: Logger
|
||||
) {
|
||||
/** Checks if the given [material] has a SIMDUT file. */
|
||||
fun exists(material: Material) =
|
||||
fileService.exists(getPath(material))
|
||||
fileService.exists(getPath(material))
|
||||
|
||||
/** Reads the SIMDUT file of the given [material]. */
|
||||
fun read(material: Material): ByteArray {
|
||||
@ -47,13 +46,21 @@ class SimdutService(
|
||||
|
||||
/** Deletes the SIMDUT file of the given [material]. */
|
||||
fun delete(material: Material) =
|
||||
fileService.delete(getPath(material))
|
||||
fileService.delete(getPath(material))
|
||||
|
||||
/** Gets the path of the SIMDUT file of the given [material]. */
|
||||
fun getPath(material: Material) =
|
||||
fileService.getPath("$SIMDUT_DIRECTORY/${getSimdutFileName(material)}")
|
||||
fileService.getPath("$SIMDUT_DIRECTORY/${getSimdutFileName(material)}")
|
||||
|
||||
/** Gets the name of the SIMDUT file of the given [material]. */
|
||||
fun getSimdutFileName(material: Material) =
|
||||
material.id.toString()
|
||||
material.id.toString()
|
||||
}
|
||||
|
||||
class SimdutWriteException(material: Material) :
|
||||
RestException(
|
||||
"simdut-write",
|
||||
"Could not write SIMDUT file",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"Could not write the SIMDUT file for the material ${material.name} to the disk"
|
||||
)
|
||||
|
@ -2,8 +2,8 @@ package dev.fyloz.colorrecipesexplorer.service.utils
|
||||
|
||||
/** Returns a list containing the result of the given [transform] applied to each item of the [Iterable]. If the given [transform] throws, the [Throwable] will be passed to the given [throwableConsumer]. */
|
||||
inline fun <T, R, reified E : Throwable> Iterable<T>.mapMayThrow(
|
||||
throwableConsumer: (E) -> Unit = {},
|
||||
transform: (T) -> R
|
||||
throwableConsumer: (E) -> Unit = {},
|
||||
transform: (T) -> R
|
||||
): List<R> = this.mapNotNull {
|
||||
try {
|
||||
transform(it)
|
||||
|
@ -7,7 +7,6 @@ cre.security.jwt-duration=18000000
|
||||
# Root user
|
||||
cre.security.root.id=9999
|
||||
cre.security.root.password=password
|
||||
|
||||
# Default material types
|
||||
entities.material-types.systemTypes[0].name=Aucun
|
||||
entities.material-types.systemTypes[0].prefix=
|
||||
@ -16,11 +15,9 @@ entities.material-types.systemTypes[1].name=Base
|
||||
entities.material-types.systemTypes[1].prefix=BAS
|
||||
entities.material-types.systemTypes[1].usepercentages=false
|
||||
entities.material-types.baseName=Base
|
||||
|
||||
# Database manager
|
||||
databaseupdater.username=root
|
||||
databaseupdater.password=pass
|
||||
|
||||
# DEBUG
|
||||
spring.jpa.show-sql=true
|
||||
# Do not modify
|
||||
|
@ -1 +1 @@
|
||||
junit.jupiter.testinstance.lifecycle.default = per_class
|
||||
junit.jupiter.testinstance.lifecycle.default=per_class
|
||||
|
@ -10,8 +10,8 @@ import kotlin.test.assertEquals
|
||||
|
||||
@DataJpaTest(excludeAutoConfiguration = [LiquibaseAutoConfiguration::class])
|
||||
class MaterialRepositoryTest @Autowired constructor(
|
||||
private val materialRepository: MaterialRepository,
|
||||
private val entityManager: TestEntityManager
|
||||
private val materialRepository: MaterialRepository,
|
||||
private val entityManager: TestEntityManager
|
||||
) {
|
||||
// updateInventoryQuantityById()
|
||||
|
||||
|
@ -10,8 +10,8 @@ import kotlin.test.assertEquals
|
||||
|
||||
@DataJpaTest(excludeAutoConfiguration = [LiquibaseAutoConfiguration::class])
|
||||
class MixRepositoryTest @Autowired constructor(
|
||||
private val mixRepository: MixRepository,
|
||||
private val entityManager: TestEntityManager
|
||||
private val mixRepository: MixRepository,
|
||||
private val entityManager: TestEntityManager
|
||||
) {
|
||||
// updateLocationById()
|
||||
|
||||
|
@ -1,8 +1,9 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.RestException
|
||||
import dev.fyloz.colorrecipesexplorer.model.EntityDto
|
||||
import dev.fyloz.colorrecipesexplorer.model.Model
|
||||
import dev.fyloz.colorrecipesexplorer.model.NamedModel
|
||||
@ -26,8 +27,8 @@ abstract class AbstractServiceTest<E, S : Service<E, *>, R : JpaRepository<E, *>
|
||||
|
||||
protected val entityList: List<E>
|
||||
get() = listOf(
|
||||
entity,
|
||||
anotherEntity
|
||||
entity,
|
||||
anotherEntity
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
@ -90,7 +91,7 @@ abstract class AbstractServiceTest<E, S : Service<E, *>, R : JpaRepository<E, *>
|
||||
}
|
||||
|
||||
abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : JpaRepository<E, Long>> :
|
||||
AbstractServiceTest1<E, S, R>() {
|
||||
AbstractServiceTest1<E, S, R>() {
|
||||
|
||||
// existsById()
|
||||
|
||||
@ -124,23 +125,21 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
|
||||
}
|
||||
|
||||
@Test
|
||||
open fun `getById() throws EntityNotFoundException when no entity with the given id exists in the repository`() {
|
||||
open fun `getById() throws NotFoundException when no entity with the given id exists in the repository`() {
|
||||
whenever(repository.findById(entity.id!!)).doReturn(Optional.empty())
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.getById(entity.id!!) }
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entity.id, exception.value as Long)
|
||||
assertThrows<NotFoundException> { service.getById(entity.id!!) }
|
||||
.assertErrorCode()
|
||||
}
|
||||
|
||||
// save()
|
||||
|
||||
@Test
|
||||
open fun `save() throws EntityAlreadyExistsException when an entity with the given id exists in the repository`() {
|
||||
doReturn(true).whenever(repository).existsById(entity.id!!)
|
||||
open fun `save() throws AlreadyExistsException when an entity with the given id exists in the repository`() {
|
||||
doReturn(true).whenever(service).existsById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entity.id, exception.value as Long)
|
||||
assertThrows<AlreadyExistsException> { service.save(entity) }
|
||||
.assertErrorCode()
|
||||
}
|
||||
|
||||
// update()
|
||||
@ -158,12 +157,11 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
|
||||
}
|
||||
|
||||
@Test
|
||||
open fun `update() throws EntityNotFoundException when no entity with the given id exists in the repository`() {
|
||||
open fun `update() throws NotFoundException when no entity with the given id exists in the repository`() {
|
||||
doReturn(false).whenever(service).existsById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.update(entity) }
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entity.id, exception.value as Long)
|
||||
assertThrows<NotFoundException> { service.update(entity) }
|
||||
.assertErrorCode()
|
||||
}
|
||||
|
||||
// deleteById()
|
||||
@ -179,7 +177,7 @@ abstract class AbstractModelServiceTest<E : Model, S : ModelService<E, *>, R : J
|
||||
}
|
||||
|
||||
abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelService<E, *>, R : NamedJpaRepository<E>> :
|
||||
AbstractModelServiceTest<E, S, R>() {
|
||||
AbstractModelServiceTest<E, S, R>() {
|
||||
protected abstract val entityWithEntityName: E
|
||||
|
||||
// existsByName()
|
||||
@ -214,21 +212,21 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
|
||||
}
|
||||
|
||||
@Test
|
||||
open fun `getByName() throws EntityNotFoundException when no entity with the given name exists`() {
|
||||
open fun `getByName() throws NotFoundException when no entity with the given name exists`() {
|
||||
whenever(repository.findByName(entity.name)).doReturn(null)
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.getByName(entity.name) }
|
||||
assertEquals(entity.name, exception.value)
|
||||
assertThrows<NotFoundException> { service.getByName(entity.name) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
// save()
|
||||
|
||||
@Test
|
||||
open fun `save() throws EntityAlreadyExistsException when an entity with the given name exists`() {
|
||||
open fun `save() throws AlreadyExistsException when an entity with the given name exists`() {
|
||||
doReturn(true).whenever(service).existsByName(entity.name)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
|
||||
assertEquals(entity.name, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.save(entity) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
// update()
|
||||
@ -247,32 +245,20 @@ abstract class AbstractNamedModelServiceTest<E : NamedModel, S : NamedModelServi
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun `update() throws EntityNotFoundException when no entity with the given id exists in the repository`() {
|
||||
override fun `update() throws NotFoundException when no entity with the given id exists in the repository`() {
|
||||
whenever(repository.findByName(entity.name)).doReturn(null)
|
||||
doReturn(false).whenever(service).existsById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.update(entity) }
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entity.id, exception.value as Long)
|
||||
|
||||
assertThrows<NotFoundException> { service.update(entity) }
|
||||
}
|
||||
|
||||
@Test
|
||||
open fun `update() throws EntityAlreadyExistsException when an entity with the updated name exists`() {
|
||||
open fun `update() throws AlreadyExistsException when an entity with the updated name exists`() {
|
||||
whenever(repository.findByName(entity.name)).doReturn(entityWithEntityName)
|
||||
doReturn(entity).whenever(service).getById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.update(entity) }
|
||||
assertEquals(entity.name, exception.value)
|
||||
}
|
||||
|
||||
// deleteByName()
|
||||
|
||||
@Test
|
||||
open fun `deleteByName() deletes the entity with the given name in the repository`() {
|
||||
service.deleteByName(entity.name)
|
||||
|
||||
verify(repository).deleteByName(entity.name)
|
||||
assertThrows<AlreadyExistsException> { service.update(entity) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,7 +270,7 @@ interface ExternalModelServiceTest {
|
||||
// ==== IMPLEMENTATIONS FOR EXTERNAL SERVICES ====
|
||||
// Lots of code duplication but I don't have a better solution for now
|
||||
abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U : EntityDto<E>, S : ExternalModelService<E, N, U, *>, R : JpaRepository<E, Long>> :
|
||||
AbstractModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
||||
AbstractModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
||||
protected abstract val entitySaveDto: N
|
||||
protected abstract val entityUpdateDto: U
|
||||
|
||||
@ -296,7 +282,7 @@ abstract class AbstractExternalModelServiceTest<E : Model, N : EntityDto<E>, U :
|
||||
}
|
||||
|
||||
abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityDto<E>, U : EntityDto<E>, S : ExternalNamedModelService<E, N, U, *>, R : NamedJpaRepository<E>> :
|
||||
AbstractNamedModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
||||
AbstractNamedModelServiceTest<E, S, R>(), ExternalModelServiceTest {
|
||||
protected abstract val entitySaveDto: N
|
||||
protected abstract val entityUpdateDto: U
|
||||
|
||||
@ -307,12 +293,25 @@ abstract class AbstractExternalNamedModelServiceTest<E : NamedModel, N : EntityD
|
||||
}
|
||||
}
|
||||
|
||||
fun NotFoundException.assertErrorCode(identifierName: String = "id") =
|
||||
this.assertErrorCode("notfound", identifierName)
|
||||
|
||||
fun AlreadyExistsException.assertErrorCode(identifierName: String = "id") =
|
||||
this.assertErrorCode("exists", identifierName)
|
||||
|
||||
fun RestException.assertErrorCode(type: String, identifierName: String) {
|
||||
assertTrue {
|
||||
this.errorCode.startsWith(type) &&
|
||||
this.errorCode.endsWith(identifierName)
|
||||
}
|
||||
}
|
||||
|
||||
fun <E : Model, N : EntityDto<E>> withBaseSaveDtoTest(
|
||||
entity: E,
|
||||
entitySaveDto: N,
|
||||
service: ExternalService<E, N, *, *>,
|
||||
saveMockMatcher: () -> E = { entity },
|
||||
op: () -> Unit = {}
|
||||
entity: E,
|
||||
entitySaveDto: N,
|
||||
service: ExternalService<E, N, *, *>,
|
||||
saveMockMatcher: () -> E = { entity },
|
||||
op: () -> Unit = {}
|
||||
) {
|
||||
doReturn(entity).whenever(service).save(saveMockMatcher())
|
||||
doReturn(entity).whenever(entitySaveDto).toEntity()
|
||||
@ -326,11 +325,11 @@ fun <E : Model, N : EntityDto<E>> withBaseSaveDtoTest(
|
||||
}
|
||||
|
||||
fun <E : Model, U : EntityDto<E>> withBaseUpdateDtoTest(
|
||||
entity: E,
|
||||
entityUpdateDto: U,
|
||||
service: ExternalModelService<E, *, U, *>,
|
||||
updateMockMatcher: () -> E,
|
||||
op: E.() -> Unit = {}
|
||||
entity: E,
|
||||
entityUpdateDto: U,
|
||||
service: ExternalModelService<E, *, U, *>,
|
||||
updateMockMatcher: () -> E,
|
||||
op: E.() -> Unit = {}
|
||||
) {
|
||||
doAnswer { it.arguments[0] }.whenever(service).update(updateMockMatcher())
|
||||
doReturn(entity).whenever(entityUpdateDto).toEntity()
|
||||
|
@ -2,13 +2,15 @@ package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.config.defaultGroupCookieName
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeGroupRepository
|
||||
import dev.fyloz.colorrecipesexplorer.repository.EmployeeRepository
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.springframework.mock.web.MockHttpServletResponse
|
||||
import org.springframework.security.core.userdetails.User
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
@ -16,10 +18,13 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
|
||||
import java.util.*
|
||||
import javax.servlet.http.Cookie
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import kotlin.test.*
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class EmployeeServiceTest :
|
||||
AbstractExternalModelServiceTest<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeService, EmployeeRepository>() {
|
||||
AbstractExternalModelServiceTest<Employee, EmployeeSaveDto, EmployeeUpdateDto, EmployeeService, EmployeeRepository>() {
|
||||
private val passwordEncoder = BCryptPasswordEncoder()
|
||||
|
||||
override val entity: Employee = employee(passwordEncoder, id = 0L)
|
||||
@ -31,22 +36,23 @@ class EmployeeServiceTest :
|
||||
override val entityUpdateDto: EmployeeUpdateDto = spy(employeeUpdateDto(id = 0L))
|
||||
|
||||
override val repository: EmployeeRepository = mock()
|
||||
override val service: EmployeeService = spy(EmployeeServiceImpl(repository, passwordEncoder))
|
||||
private val employeeGroupService: EmployeeGroupService = mock()
|
||||
override val service: EmployeeService = spy(EmployeeServiceImpl(repository, employeeGroupService, passwordEncoder))
|
||||
|
||||
private val entitySaveDtoEmployee = Employee(
|
||||
entitySaveDto.id,
|
||||
entitySaveDto.firstName,
|
||||
entitySaveDto.lastName,
|
||||
passwordEncoder.encode(entitySaveDto.password),
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = null,
|
||||
permissions = entitySaveDto.permissions
|
||||
entitySaveDto.id,
|
||||
entitySaveDto.firstName,
|
||||
entitySaveDto.lastName,
|
||||
passwordEncoder.encode(entitySaveDto.password),
|
||||
isDefaultGroupUser = false,
|
||||
isSystemUser = false,
|
||||
group = null,
|
||||
permissions = entitySaveDto.permissions
|
||||
)
|
||||
|
||||
@AfterEach
|
||||
override fun afterEach() {
|
||||
reset(entitySaveDto, entityUpdateDto)
|
||||
reset(employeeGroupService)
|
||||
super.afterEach()
|
||||
}
|
||||
|
||||
@ -73,33 +79,29 @@ class EmployeeServiceTest :
|
||||
// getById()
|
||||
|
||||
@Test
|
||||
fun `getById() throws EntityNotFoundException when the corresponding employee is a default group user`() {
|
||||
fun `getById() throws NotFoundException when the corresponding employee is a default group user`() {
|
||||
whenever(repository.findById(entityDefaultGroupUser.id)).doReturn(Optional.of(entityDefaultGroupUser))
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> {
|
||||
assertThrows<NotFoundException> {
|
||||
service.getById(
|
||||
entityDefaultGroupUser.id,
|
||||
ignoreDefaultGroupUsers = true,
|
||||
ignoreSystemUsers = false
|
||||
entityDefaultGroupUser.id,
|
||||
ignoreDefaultGroupUsers = true,
|
||||
ignoreSystemUsers = false
|
||||
)
|
||||
}
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entityDefaultGroupUser.id, exception.value as Long)
|
||||
}.assertErrorCode()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getById() throws EntityNotFoundException when the corresponding employee is a system user`() {
|
||||
fun `getById() throws NotFoundException when the corresponding employee is a system user`() {
|
||||
whenever(repository.findById(entitySystemUser.id)).doReturn(Optional.of(entitySystemUser))
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> {
|
||||
assertThrows<NotFoundException> {
|
||||
service.getById(
|
||||
entitySystemUser.id,
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = true
|
||||
entitySystemUser.id,
|
||||
ignoreDefaultGroupUsers = false,
|
||||
ignoreSystemUsers = true
|
||||
)
|
||||
}
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entitySystemUser.id, exception.value as Long)
|
||||
}.assertErrorCode()
|
||||
}
|
||||
|
||||
// getByGroup()
|
||||
@ -147,18 +149,20 @@ class EmployeeServiceTest :
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `save() throws EntityAlreadyExistsException when firstName and lastName exists`() {
|
||||
fun `save() throws AlreadyExistsException when firstName and lastName exists`() {
|
||||
doReturn(true).whenever(repository).existsByFirstNameAndLastName(entity.firstName, entity.lastName)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
|
||||
assertEquals("${entity.firstName} ${entity.lastName}", exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.save(entity) }
|
||||
.assertErrorCode("fullName")
|
||||
}
|
||||
|
||||
@Test
|
||||
override fun `save(dto) calls and returns save() with the created entity`() {
|
||||
withBaseSaveDtoTest(entity, entitySaveDto, service, {argThat {
|
||||
this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName
|
||||
}})
|
||||
withBaseSaveDtoTest(entity, entitySaveDto, service, {
|
||||
argThat {
|
||||
this.id == entity.id && this.firstName == entity.firstName && this.lastName == entity.lastName
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -175,29 +179,27 @@ class EmployeeServiceTest :
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
|
||||
@Test
|
||||
fun `update() throws EntityAlreadyExistsException when a different employee with the given first name and last name exists`() {
|
||||
fun `update() throws AlreadyExistsException when a different employee with the given first name and last name exists`() {
|
||||
whenever(repository.findByFirstNameAndLastName(entity.firstName, entity.lastName)).doReturn(
|
||||
entityDefaultGroupUser
|
||||
entityDefaultGroupUser
|
||||
)
|
||||
doReturn(entity).whenever(service).getById(eq(entity.id), any(), any())
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> {
|
||||
assertThrows<AlreadyExistsException> {
|
||||
service.update(
|
||||
entity,
|
||||
true,
|
||||
ignoreSystemUsers = true
|
||||
entity,
|
||||
true,
|
||||
ignoreSystemUsers = true
|
||||
)
|
||||
}
|
||||
assertTrue(exception.value is String)
|
||||
assertEquals("${entity.firstName} ${entity.lastName}", exception.value as String)
|
||||
}.assertErrorCode("fullName")
|
||||
}
|
||||
}
|
||||
|
||||
class EmployeeGroupServiceTest :
|
||||
AbstractExternalNamedModelServiceTest<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupService, EmployeeGroupRepository>() {
|
||||
AbstractExternalNamedModelServiceTest<EmployeeGroup, EmployeeGroupSaveDto, EmployeeGroupUpdateDto, EmployeeGroupService, EmployeeGroupRepository>() {
|
||||
private val employeeService: EmployeeService = mock()
|
||||
override val repository: EmployeeGroupRepository = mock()
|
||||
override val service: EmployeeGroupServiceImpl = spy(EmployeeGroupServiceImpl(employeeService, repository))
|
||||
@ -257,13 +259,12 @@ class EmployeeGroupServiceTest :
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getRequestDefaultGroup() throws EntityNotFoundException when the HTTP request does not contains a cookie for the default group`() {
|
||||
fun `getRequestDefaultGroup() throws NoDefaultGroupException when the HTTP request does not contains a cookie for the default group`() {
|
||||
val request: HttpServletRequest = mock()
|
||||
|
||||
whenever(request.cookies).doReturn(arrayOf())
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.getRequestDefaultGroup(request) }
|
||||
assertEquals("defaultGroup", exception.value)
|
||||
assertThrows<NoDefaultGroupException> { service.getRequestDefaultGroup(request) }
|
||||
}
|
||||
|
||||
// setResponseDefaultGroup()
|
||||
@ -297,7 +298,7 @@ class EmployeeGroupServiceTest :
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
}
|
||||
|
||||
class EmployeeUserDetailsServiceTest {
|
||||
@ -317,7 +318,7 @@ class EmployeeUserDetailsServiceTest {
|
||||
fun `loadUserByUsername() calls loadUserByEmployeeId() with the given username as an id`() {
|
||||
whenever(employeeService.getById(eq(employee.id), any(), any())).doReturn(employee)
|
||||
doReturn(User(employee.id.toString(), employee.password, listOf())).whenever(service)
|
||||
.loadUserByEmployeeId(employee.id)
|
||||
.loadUserByEmployeeId(employee.id)
|
||||
|
||||
service.loadUserByUsername(employee.id.toString())
|
||||
|
||||
@ -327,9 +328,7 @@ class EmployeeUserDetailsServiceTest {
|
||||
@Test
|
||||
fun `loadUserByUsername() throws UsernameNotFoundException when no employee with the given id exists`() {
|
||||
whenever(employeeService.getById(eq(employee.id), any(), any())).doThrow(
|
||||
EntityNotFoundException(
|
||||
employee.id
|
||||
)
|
||||
employeeIdNotFoundException(employee.id)
|
||||
)
|
||||
|
||||
assertThrows<UsernameNotFoundException> { service.loadUserByUsername(employee.id.toString()) }
|
||||
|
@ -9,7 +9,7 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CompanyServiceTest :
|
||||
AbstractExternalNamedModelServiceTest<Company, CompanySaveDto, CompanyUpdateDto, CompanyService, CompanyRepository>() {
|
||||
AbstractExternalNamedModelServiceTest<Company, CompanySaveDto, CompanyUpdateDto, CompanyService, CompanyRepository>() {
|
||||
private val recipeService: RecipeService = mock()
|
||||
override val repository: CompanyRepository = mock()
|
||||
override val service: CompanyService = spy(CompanyServiceImpl(repository, recipeService))
|
||||
@ -57,11 +57,27 @@ class CompanyServiceTest :
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
|
||||
// delete()
|
||||
|
||||
override fun `delete() deletes in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`delete() deletes in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
// deleteById()
|
||||
|
||||
override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
whenCanBeDeleted {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.LowQuantitiesException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.LowQuantityException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
@ -25,15 +23,15 @@ class InventoryServiceTest {
|
||||
@Test
|
||||
fun `add(materialQuantities) calls add() for each MaterialQuantityDto`() {
|
||||
val materialQuantities = listOf(
|
||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
||||
)
|
||||
val storedQuantity = 2000f
|
||||
|
||||
doAnswer { storedQuantity + (it.arguments[0] as MaterialQuantityDto).quantity }.whenever(service)
|
||||
.add(any<MaterialQuantityDto>())
|
||||
.add(any<MaterialQuantityDto>())
|
||||
|
||||
val found = service.add(materialQuantities)
|
||||
|
||||
@ -52,8 +50,8 @@ class InventoryServiceTest {
|
||||
val found = service.add(this)
|
||||
|
||||
verify(materialService).updateQuantity(
|
||||
argThat { this.id == this@withGivenQuantities.material },
|
||||
eq(this.quantity)
|
||||
argThat { this.id == this@withGivenQuantities.material },
|
||||
eq(this.quantity)
|
||||
)
|
||||
assertEquals(updatedQuantity, found)
|
||||
}
|
||||
@ -67,15 +65,15 @@ class InventoryServiceTest {
|
||||
val materialPercents = material(id = 1L, materialType = materialType(usePercentages = true))
|
||||
val mixRatio = mixRatio(ratio = 1.5f)
|
||||
val mix = mix(
|
||||
id = mixRatio.id,
|
||||
mixMaterials = mutableSetOf(
|
||||
mixMaterial(id = 0L, material = material, quantity = 1000f, position = 0),
|
||||
mixMaterial(id = 1L, material = materialPercents, quantity = 50f, position = 1)
|
||||
)
|
||||
id = mixRatio.id,
|
||||
mixMaterials = mutableSetOf(
|
||||
mixMaterial(id = 0L, material = material, quantity = 1000f, position = 0),
|
||||
mixMaterial(id = 1L, material = materialPercents, quantity = 50f, position = 1)
|
||||
)
|
||||
)
|
||||
val expectedQuantities = mapOf(
|
||||
0L to 1500f,
|
||||
1L to 750f
|
||||
0L to 1500f,
|
||||
1L to 750f
|
||||
)
|
||||
|
||||
whenever(mixService.getById(mix.id!!)).doReturn(mix)
|
||||
@ -99,15 +97,15 @@ class InventoryServiceTest {
|
||||
@Test
|
||||
fun `deduct(materialQuantities) calls deduct() for each MaterialQuantityDto`() {
|
||||
val materialQuantities = listOf(
|
||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
||||
)
|
||||
val storedQuantity = 5000f
|
||||
|
||||
doAnswer { storedQuantity - (it.arguments[0] as MaterialQuantityDto).quantity }.whenever(service)
|
||||
.deduct(any<MaterialQuantityDto>())
|
||||
.deduct(any<MaterialQuantityDto>())
|
||||
|
||||
val found = service.deduct(materialQuantities)
|
||||
|
||||
@ -118,22 +116,20 @@ class InventoryServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deduct(materialQuantities) throws LowQuantitiesException when there is not enough inventory of a given material`() {
|
||||
fun `deduct(materialQuantities) throws MultiplesNotEnoughInventoryException when there is not enough inventory of a given material`() {
|
||||
val materialQuantities = listOf(
|
||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
||||
materialQuantityDto(materialId = 1, quantity = 1234f),
|
||||
materialQuantityDto(materialId = 2, quantity = 2345f),
|
||||
materialQuantityDto(materialId = 3, quantity = 3456f),
|
||||
materialQuantityDto(materialId = 4, quantity = 4567f)
|
||||
)
|
||||
val inventoryQuantity = 3000f
|
||||
val lowQuantities = materialQuantities.filter { it.quantity > inventoryQuantity }
|
||||
|
||||
materialQuantities.forEach {
|
||||
withGivenQuantities(inventoryQuantity, it)
|
||||
}
|
||||
|
||||
val exception = assertThrows<LowQuantitiesException> { service.deduct(materialQuantities) }
|
||||
assertTrue { exception.materialQuantities.containsAll(lowQuantities) }
|
||||
assertThrows<MultiplesNotEnoughInventoryException> { service.deduct(materialQuantities) }
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -145,26 +141,25 @@ class InventoryServiceTest {
|
||||
val found = service.deduct(this)
|
||||
|
||||
verify(materialService).updateQuantity(
|
||||
argThat { this.id == this@withGivenQuantities.material },
|
||||
eq(-this.quantity)
|
||||
argThat { this.id == this@withGivenQuantities.material },
|
||||
eq(-this.quantity)
|
||||
)
|
||||
assertEquals(updatedQuantity, found)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deduct(materialQuantity) throws LowQuantityException when there is not enough inventory of the given material`() {
|
||||
fun `deduct(materialQuantity) throws NotEnoughInventoryException when there is not enough inventory of the given material`() {
|
||||
withGivenQuantities(0f, 1000f) {
|
||||
val exception = assertThrows<LowQuantityException> { service.deduct(this) }
|
||||
assertEquals(this, exception.materialQuantity)
|
||||
assertThrows<NotEnoughInventoryException> { service.deduct(this) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun withGivenQuantities(
|
||||
stored: Float,
|
||||
quantity: Float,
|
||||
materialId: Long = 0L,
|
||||
test: MaterialQuantityDto.(Float) -> Unit = {}
|
||||
stored: Float,
|
||||
quantity: Float,
|
||||
materialId: Long = 0L,
|
||||
test: MaterialQuantityDto.(Float) -> Unit = {}
|
||||
) {
|
||||
val materialQuantity = materialQuantityDto(materialId = materialId, quantity = quantity)
|
||||
|
||||
@ -172,9 +167,9 @@ class InventoryServiceTest {
|
||||
}
|
||||
|
||||
private fun withGivenQuantities(
|
||||
stored: Float,
|
||||
materialQuantity: MaterialQuantityDto,
|
||||
test: MaterialQuantityDto.(Float) -> Unit = {}
|
||||
stored: Float,
|
||||
materialQuantity: MaterialQuantityDto,
|
||||
test: MaterialQuantityDto.(Float) -> Unit = {}
|
||||
) {
|
||||
val material = material(id = materialQuantity.material, inventoryQuantity = stored)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.SimdutService
|
||||
@ -14,14 +14,14 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MaterialServiceTest :
|
||||
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
|
||||
AbstractExternalNamedModelServiceTest<Material, MaterialSaveDto, MaterialUpdateDto, MaterialService, MaterialRepository>() {
|
||||
override val repository: MaterialRepository = mock()
|
||||
private val simdutService: SimdutService = mock()
|
||||
private val recipeService: RecipeService = mock()
|
||||
private val mixService: MixService = mock()
|
||||
private val materialTypeService: MaterialTypeService = mock()
|
||||
override val service: MaterialService =
|
||||
spy(MaterialServiceImpl(repository, simdutService, recipeService, mixService, materialTypeService))
|
||||
spy(MaterialServiceImpl(repository, simdutService, recipeService, mixService, materialTypeService))
|
||||
|
||||
override val entity: Material = material(id = 0L, name = "material")
|
||||
override val anotherEntity: Material = material(id = 1L, name = "another material")
|
||||
@ -99,19 +99,19 @@ class MaterialServiceTest :
|
||||
@Test
|
||||
fun `getAllIdsWithSimdut() returns a list containing the identifier of every material with a SIMDUT file`() {
|
||||
val materials = listOf(
|
||||
material(id = 0L),
|
||||
material(id = 1L),
|
||||
material(id = 2L),
|
||||
material(id = 3L)
|
||||
material(id = 0L),
|
||||
material(id = 1L),
|
||||
material(id = 2L),
|
||||
material(id = 3L)
|
||||
)
|
||||
val hasSimdut = mapOf(
|
||||
*materials
|
||||
.map { it.id!! to it.evenId }
|
||||
.toTypedArray()
|
||||
*materials
|
||||
.map { it.id!! to it.evenId }
|
||||
.toTypedArray()
|
||||
)
|
||||
val expectedIds = hasSimdut
|
||||
.filter { it.value }
|
||||
.map { it.key }
|
||||
.filter { it.value }
|
||||
.map { it.key }
|
||||
|
||||
whenever(simdutService.exists(any())).doAnswer { hasSimdut[(it.arguments[0] as Material).id] }
|
||||
doReturn(materials).whenever(service).getAllNotMixType()
|
||||
@ -124,11 +124,11 @@ class MaterialServiceTest :
|
||||
// save()
|
||||
|
||||
@Test
|
||||
fun `save() throws EntityAlreadyExistsException when a material with the given name exists in the repository`() {
|
||||
fun `save() throws AlreadyExistsException when a material with the given name exists in the repository`() {
|
||||
doReturn(true).whenever(service).existsByName(entity.name)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
|
||||
assertEquals(entity.name, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.save(entity) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -152,15 +152,15 @@ class MaterialServiceTest :
|
||||
// update()
|
||||
|
||||
@Test
|
||||
fun `update() throws EntityAlreadyExistsException when another material with the updated name exists in the repository`() {
|
||||
fun `update() throws AlreadyExistsException when another material with the updated name exists in the repository`() {
|
||||
val material = material(id = 0L, name = "name")
|
||||
val anotherMaterial = material(id = 1L, name = "name")
|
||||
|
||||
whenever(repository.findByName(material.name)).doReturn(anotherMaterial)
|
||||
doReturn(entity).whenever(service).getById(material.id!!)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.update(material) }
|
||||
assertEquals(material.name, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.update(material) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
// updateQuantity()
|
||||
@ -186,7 +186,7 @@ class MaterialServiceTest :
|
||||
val anotherMixTypeMaterial = material(id = 2L, isMixType = true)
|
||||
val materials = listOf(normalMaterial, mixTypeMaterial, anotherMixTypeMaterial)
|
||||
val recipe =
|
||||
recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial))))
|
||||
recipe(id = 0L, mixes = mutableListOf(mix(mixType = mixType(id = 0L, material = mixTypeMaterial))))
|
||||
|
||||
whenever(recipeService.getById(recipe.id!!)).doReturn(recipe)
|
||||
doReturn(materials).whenever(service).getAll()
|
||||
@ -239,11 +239,27 @@ class MaterialServiceTest :
|
||||
|
||||
// delete()
|
||||
|
||||
override fun `delete() deletes in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`delete() deletes in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
// deleteById()
|
||||
|
||||
override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
whenCanBeDeleted {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
/** Utility property to check if the identifier of the given [Material] is even. */
|
||||
val Material.evenId: Boolean
|
||||
private val Material.evenId: Boolean
|
||||
get() = this.id!! % 2 == 0L
|
||||
|
||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MaterialTypeRepository
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
@ -13,7 +13,7 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class MaterialTypeServiceTest :
|
||||
AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeService, MaterialTypeRepository>() {
|
||||
AbstractExternalNamedModelServiceTest<MaterialType, MaterialTypeSaveDto, MaterialTypeUpdateDto, MaterialTypeService, MaterialTypeRepository>() {
|
||||
override val repository: MaterialTypeRepository = mock()
|
||||
private val materialService: MaterialService = mock()
|
||||
override val service: MaterialTypeService = spy(MaterialTypeServiceImpl(repository, materialService))
|
||||
@ -24,7 +24,7 @@ class MaterialTypeServiceTest :
|
||||
private val anotherSystemType = materialType(id = 4L, name = "another systype", prefix = "ASY", systemType = true)
|
||||
override val entitySaveDto: MaterialTypeSaveDto = spy(materialTypeSaveDto(name = "material type", prefix = "MAT"))
|
||||
override val entityUpdateDto: MaterialTypeUpdateDto =
|
||||
spy(materialTypeUpdateDto(id = 0L, name = "material type", prefix = "MAT"))
|
||||
spy(materialTypeUpdateDto(id = 0L, name = "material type", prefix = "MAT"))
|
||||
|
||||
@AfterEach
|
||||
override fun afterEach() {
|
||||
@ -106,18 +106,18 @@ class MaterialTypeServiceTest :
|
||||
// saveMaterialType()
|
||||
|
||||
@Test
|
||||
fun `saveMaterialType() throws EntityAlreadyExistsException when a material type with the given prefix already exists`() {
|
||||
fun `saveMaterialType() throws AlreadyExistsException when a material type with the given prefix already exists`() {
|
||||
doReturn(true).whenever(service).existsByPrefix(entity.prefix)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
|
||||
assertEquals(entity.prefix, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.save(entity) }
|
||||
.assertErrorCode("prefix")
|
||||
}
|
||||
|
||||
// update()
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
|
||||
override fun `update() saves in the repository and returns the updated value`() {
|
||||
whenever(repository.save(entity)).doReturn(entity)
|
||||
@ -132,49 +132,53 @@ class MaterialTypeServiceTest :
|
||||
assertEquals(entity, found)
|
||||
}
|
||||
|
||||
override fun `update() throws EntityNotFoundException when no entity with the given id exists in the repository`() {
|
||||
override fun `update() throws NotFoundException when no entity with the given id exists in the repository`() {
|
||||
whenever(repository.findByName(entity.name)).doReturn(null)
|
||||
whenever(repository.findByPrefix(entity.prefix)).doReturn(null)
|
||||
doReturn(false).whenever(service).existsById(entity.id!!)
|
||||
doReturn(null).whenever(service).getById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.update(entity) }
|
||||
assertTrue(exception.value is Long)
|
||||
assertEquals(entity.id, exception.value as Long)
|
||||
assertThrows<NotFoundException> { service.update(entity) }
|
||||
.assertErrorCode()
|
||||
}
|
||||
|
||||
override fun `update() throws EntityAlreadyExistsException when an entity with the updated name exists`() {
|
||||
override fun `update() throws AlreadyExistsException when an entity with the updated name exists`() {
|
||||
whenever(repository.findByName(entity.name)).doReturn(entityWithEntityName)
|
||||
whenever(repository.findByPrefix(entity.prefix)).doReturn(null)
|
||||
doReturn(true).whenever(service).existsById(entity.id!!)
|
||||
doReturn(entity).whenever(service).getById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.update(entity) }
|
||||
assertEquals(entity.name, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.update(entity) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `update() throws EntityAlreadyExistsException when an entity with the updated prefix exists`() {
|
||||
fun `update() throws AlreadyExistsException when an entity with the updated prefix exists`() {
|
||||
val anotherMaterialType = materialType(prefix = entity.prefix)
|
||||
whenever(repository.findByPrefix(entity.prefix)).doReturn(anotherMaterialType)
|
||||
doReturn(entity).whenever(service).getById(entity.id!!)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.update(entity) }
|
||||
assertEquals(entity.prefix, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.update(entity) }
|
||||
.assertErrorCode("prefix")
|
||||
}
|
||||
|
||||
// delete()
|
||||
|
||||
@Test
|
||||
fun `delete() calls delete() in the repository`() {
|
||||
doReturn(false).whenever(service).isUsedByMaterial(entity)
|
||||
|
||||
service.delete(entity)
|
||||
|
||||
verify(repository).delete(entity)
|
||||
override fun `delete() deletes in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`delete() deletes in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
whenCanBeDeleted {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
@ -43,18 +43,18 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
|
||||
@Test
|
||||
fun `create(set) calls create() for each MixMaterialDto`() {
|
||||
val mixMaterialDtos = setOf(
|
||||
mixMaterialDto(materialId = 0L, quantity = 1000f, position = 1),
|
||||
mixMaterialDto(materialId = 1L, quantity = 2000f, position = 2),
|
||||
mixMaterialDto(materialId = 2L, quantity = 3000f, position = 3),
|
||||
mixMaterialDto(materialId = 3L, quantity = 4000f, position = 4)
|
||||
mixMaterialDto(materialId = 0L, quantity = 1000f, position = 1),
|
||||
mixMaterialDto(materialId = 1L, quantity = 2000f, position = 2),
|
||||
mixMaterialDto(materialId = 2L, quantity = 3000f, position = 3),
|
||||
mixMaterialDto(materialId = 3L, quantity = 4000f, position = 4)
|
||||
)
|
||||
|
||||
doAnswer {
|
||||
with(it.arguments[0] as MixMaterialDto) {
|
||||
mixMaterial(
|
||||
material = material(id = this.materialId),
|
||||
quantity = this.quantity,
|
||||
position = this.position
|
||||
material = material(id = this.materialId),
|
||||
quantity = this.quantity,
|
||||
position = this.position
|
||||
)
|
||||
}
|
||||
}.whenever(service).create(any<MixMaterialDto>())
|
||||
@ -81,8 +81,8 @@ class MixMaterialServiceTest : AbstractModelServiceTest<MixMaterial, MixMaterial
|
||||
|
||||
assertTrue {
|
||||
found.material.id == mixMaterialDto.materialId &&
|
||||
found.quantity == mixMaterialDto.quantity &&
|
||||
found.position == mixMaterialDto.position
|
||||
found.quantity == mixMaterialDto.quantity &&
|
||||
found.position == mixMaterialDto.position
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,12 +15,12 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
private val mixMaterialService: MixMaterialService = mock()
|
||||
private val mixTypeService: MixTypeService = mock()
|
||||
override val service: MixService =
|
||||
spy(MixServiceImpl(repository, recipeService, materialTypeService, mixMaterialService, mixTypeService))
|
||||
spy(MixServiceImpl(repository, recipeService, materialTypeService, mixMaterialService, mixTypeService))
|
||||
|
||||
override val entity: Mix = mix(id = 0L, location = "location")
|
||||
override val anotherEntity: Mix = mix(id = 1L)
|
||||
override val entitySaveDto: MixSaveDto =
|
||||
spy(mixSaveDto(mixMaterials = setOf(mixMaterialDto(materialId = 1L, quantity = 1000f, position = 0))))
|
||||
spy(mixSaveDto(mixMaterials = setOf(mixMaterialDto(materialId = 1L, quantity = 1000f, position = 0))))
|
||||
override val entityUpdateDto: MixUpdateDto = spy(mixUpdateDto(id = entity.id!!))
|
||||
|
||||
@AfterEach
|
||||
@ -49,10 +49,10 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
val recipe = recipe(id = entitySaveDto.recipeId)
|
||||
val materialType = materialType(id = entitySaveDto.materialTypeId)
|
||||
val material = material(
|
||||
name = entitySaveDto.name,
|
||||
inventoryQuantity = Float.MIN_VALUE,
|
||||
isMixType = true,
|
||||
materialType = materialType
|
||||
name = entitySaveDto.name,
|
||||
inventoryQuantity = Float.MIN_VALUE,
|
||||
isMixType = true,
|
||||
materialType = materialType
|
||||
)
|
||||
val mixType = mixType(name = entitySaveDto.name, material = material)
|
||||
val mix = mix(recipe = recipe, mixType = mixType)
|
||||
@ -63,10 +63,10 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
whenever(materialTypeService.getById(materialType.id!!)).doReturn(materialType)
|
||||
whenever(mixMaterialService.create(entitySaveDto.mixMaterials!!)).doReturn(mixMaterials)
|
||||
whenever(
|
||||
mixTypeService.getOrCreateForNameAndMaterialType(
|
||||
mixType.name,
|
||||
mixType.material.materialType!!
|
||||
)
|
||||
mixTypeService.getOrCreateForNameAndMaterialType(
|
||||
mixType.name,
|
||||
mixType.material.materialType!!
|
||||
)
|
||||
).doReturn(mixType)
|
||||
doReturn(true).whenever(service).existsById(mixWithId.id!!)
|
||||
doReturn(mixWithId).whenever(service).save(any<Mix>())
|
||||
@ -85,9 +85,9 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
// update()
|
||||
|
||||
private fun mixUpdateDtoTest(
|
||||
scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
|
||||
sharedMixType: Boolean = false,
|
||||
op: MixUpdateDtoTestScope.() -> Unit
|
||||
scope: MixUpdateDtoTestScope = MixUpdateDtoTestScope(),
|
||||
sharedMixType: Boolean = false,
|
||||
op: MixUpdateDtoTestScope.() -> Unit
|
||||
) {
|
||||
with(scope) {
|
||||
doReturn(true).whenever(service).existsById(mix.id!!)
|
||||
@ -127,7 +127,7 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
fun `update(dto) calls MixTypeService saveForNameAndMaterialType() when mix type is shared`() {
|
||||
mixUpdateDtoMixTypeTest(sharedMixType = true) {
|
||||
whenever(mixTypeService.saveForNameAndMaterialType(mixUpdateDto.name!!, materialType))
|
||||
.doReturn(newMixType)
|
||||
.doReturn(newMixType)
|
||||
|
||||
val found = service.update(mixUpdateDto)
|
||||
|
||||
@ -141,7 +141,7 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
fun `update(dto) calls MixTypeService updateForNameAndMaterialType() when mix type is not shared`() {
|
||||
mixUpdateDtoMixTypeTest {
|
||||
whenever(mixTypeService.updateForNameAndMaterialType(mixType, mixUpdateDto.name!!, materialType))
|
||||
.doReturn(newMixType)
|
||||
.doReturn(newMixType)
|
||||
|
||||
val found = service.update(mixUpdateDto)
|
||||
|
||||
@ -155,19 +155,19 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
fun `update(dto) update, create and delete mix materials according to the given mix materials map`() {
|
||||
mixUpdateDtoTest {
|
||||
val mixMaterials = setOf(
|
||||
mixMaterialDto(materialId = 0L, quantity = 100f, position = 0),
|
||||
mixMaterialDto(materialId = 1L, quantity = 200f, position = 1),
|
||||
mixMaterialDto(materialId = 2L, quantity = 300f, position = 2),
|
||||
mixMaterialDto(materialId = 3L, quantity = 400f, position = 3),
|
||||
mixMaterialDto(materialId = 0L, quantity = 100f, position = 0),
|
||||
mixMaterialDto(materialId = 1L, quantity = 200f, position = 1),
|
||||
mixMaterialDto(materialId = 2L, quantity = 300f, position = 2),
|
||||
mixMaterialDto(materialId = 3L, quantity = 400f, position = 3),
|
||||
)
|
||||
mixUpdateDto.mixMaterials = mixMaterials
|
||||
|
||||
whenever(mixMaterialService.create(any<Set<MixMaterialDto>>())).doAnswer {
|
||||
(it.arguments[0] as Set<MixMaterialDto>).map { dto ->
|
||||
mixMaterial(
|
||||
material = material(id = dto.materialId),
|
||||
quantity = dto.quantity,
|
||||
position = dto.position
|
||||
material = material(id = dto.materialId),
|
||||
quantity = dto.quantity,
|
||||
position = dto.position
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
@ -189,10 +189,10 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
@Test
|
||||
fun `updateLocations() calls updateLocation() for each given MixLocationDto`() {
|
||||
val locations = setOf(
|
||||
mixLocationDto(mixId = 0, location = "Loc 0"),
|
||||
mixLocationDto(mixId = 1, location = "Loc 1"),
|
||||
mixLocationDto(mixId = 2, location = "Loc 2"),
|
||||
mixLocationDto(mixId = 3, location = "Loc 3")
|
||||
mixLocationDto(mixId = 0, location = "Loc 0"),
|
||||
mixLocationDto(mixId = 1, location = "Loc 1"),
|
||||
mixLocationDto(mixId = 2, location = "Loc 2"),
|
||||
mixLocationDto(mixId = 3, location = "Loc 3")
|
||||
)
|
||||
|
||||
service.updateLocations(locations)
|
||||
@ -215,24 +215,39 @@ class MixServiceTest : AbstractExternalModelServiceTest<Mix, MixSaveDto, MixUpda
|
||||
|
||||
// delete()
|
||||
|
||||
override fun `delete() deletes in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`delete() deletes in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
// deleteById()
|
||||
|
||||
@Test
|
||||
override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
whenever(repository.canBeDeleted(entity.id!!)).doReturn(true)
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
whenCanBeDeleted {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
||||
data class MixUpdateDtoTestScope(
|
||||
val mixType: MixType = mixType(name = "mix type"),
|
||||
val newMixType: MixType = mixType(name = "another mix type"),
|
||||
val materialType: MaterialType = materialType(id = 0L),
|
||||
val mix: Mix = mix(id = 0L, mixType = mixType),
|
||||
val mixUpdateDto: MixUpdateDto = spy(
|
||||
mixUpdateDto(
|
||||
id = 0L,
|
||||
name = null,
|
||||
materialTypeId = null,
|
||||
mixMaterials = setOf()
|
||||
)
|
||||
val mixType: MixType = mixType(name = "mix type"),
|
||||
val newMixType: MixType = mixType(name = "another mix type"),
|
||||
val materialType: MaterialType = materialType(id = 0L),
|
||||
val mix: Mix = mix(id = 0L, mixType = mixType),
|
||||
val mixUpdateDto: MixUpdateDto = spy(
|
||||
mixUpdateDto(
|
||||
id = 0L,
|
||||
name = null,
|
||||
materialTypeId = null,
|
||||
mixMaterials = setOf()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityAlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException
|
||||
import dev.fyloz.colorrecipesexplorer.exception.NotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.MixTypeRepository
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
@ -54,11 +54,11 @@ class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getByMaterial() throws EntityNotFoundException when no mix type with the given material exists`() {
|
||||
fun `getByMaterial() throws NotFoundException when no mix type with the given material exists`() {
|
||||
whenever(repository.findByMaterial(material)).doReturn(null)
|
||||
|
||||
val exception = assertThrows<EntityNotFoundException> { service.getByMaterial(material) }
|
||||
assertEquals(material.name, exception.value)
|
||||
assertThrows<NotFoundException> { service.getByMaterial(material) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
// getByNameAndMaterialType()
|
||||
@ -100,11 +100,11 @@ class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService
|
||||
// save()
|
||||
|
||||
@Test
|
||||
fun `save() throws EntityAlreadyExistsException when a material with the name of the new mix type exists`() {
|
||||
fun `save() throws AlreadyExistsException when a material with the name of the new mix type exists`() {
|
||||
whenever(materialService.existsByName(entity.name)).doReturn(true)
|
||||
|
||||
val exception = assertThrows<EntityAlreadyExistsException> { service.save(entity) }
|
||||
assertEquals(entity.name, exception.value)
|
||||
assertThrows<AlreadyExistsException> { service.save(entity) }
|
||||
.assertErrorCode("name")
|
||||
}
|
||||
|
||||
// saveForNameAndMaterialType()
|
||||
@ -149,8 +149,23 @@ class MixTypeServiceTest : AbstractNamedModelServiceTest<MixType, MixTypeService
|
||||
|
||||
// delete()
|
||||
|
||||
override fun `delete() deletes in the repository`() {
|
||||
whenCanBeDeleted {
|
||||
super.`delete() deletes in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
// deleteById()
|
||||
|
||||
override fun `deleteById() deletes the entity with the given id in the repository`() {
|
||||
whenever(repository.canBeDeleted(any())).doReturn(true)
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
whenCanBeDeleted {
|
||||
super.`deleteById() deletes the entity with the given id in the repository`()
|
||||
}
|
||||
}
|
||||
|
||||
private fun whenCanBeDeleted(id: Long = any(), test: () -> Unit) {
|
||||
whenever(repository.canBeDeleted(id)).doReturn(true)
|
||||
|
||||
test()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.EntityNotFoundException
|
||||
import dev.fyloz.colorrecipesexplorer.model.*
|
||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeRepository
|
||||
import dev.fyloz.colorrecipesexplorer.service.files.FileService
|
||||
@ -16,7 +15,7 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class RecipeServiceTest :
|
||||
AbstractExternalModelServiceTest<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService, RecipeRepository>() {
|
||||
AbstractExternalModelServiceTest<Recipe, RecipeSaveDto, RecipeUpdateDto, RecipeService, RecipeRepository>() {
|
||||
override val repository: RecipeRepository = mock()
|
||||
private val companyService: CompanyService = mock()
|
||||
private val mixService: MixService = mock()
|
||||
@ -79,22 +78,22 @@ class RecipeServiceTest :
|
||||
|
||||
@Test
|
||||
override fun `update(dto) calls and returns update() with the created entity`() =
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
withBaseUpdateDtoTest(entity, entityUpdateDto, service, { any() })
|
||||
|
||||
// updatePublicData()
|
||||
|
||||
@Test
|
||||
fun `updatePublicData() updates the notes of a recipe groups information according to the RecipePublicDataDto`() {
|
||||
val recipe = recipe(
|
||||
id = 0L, groupsInformation = setOf(
|
||||
recipeGroupInformation(id = 0L, group = employeeGroup(id = 1L), note = "Old note"),
|
||||
recipeGroupInformation(id = 1L, group = employeeGroup(id = 2L), note = "Another note"),
|
||||
recipeGroupInformation(id = 2L, group = employeeGroup(id = 3L), note = "Up to date note")
|
||||
)
|
||||
id = 0L, groupsInformation = setOf(
|
||||
recipeGroupInformation(id = 0L, group = employeeGroup(id = 1L), note = "Old note"),
|
||||
recipeGroupInformation(id = 1L, group = employeeGroup(id = 2L), note = "Another note"),
|
||||
recipeGroupInformation(id = 2L, group = employeeGroup(id = 3L), note = "Up to date note")
|
||||
)
|
||||
)
|
||||
val notes = setOf(
|
||||
noteDto(groupId = 1, content = "Note 1"),
|
||||
noteDto(groupId = 2, content = null)
|
||||
noteDto(groupId = 1, content = "Note 1"),
|
||||
noteDto(groupId = 2, content = null)
|
||||
)
|
||||
val publicData = recipePublicDataDto(recipeId = recipe.id!!, notes = notes)
|
||||
|
||||
@ -114,10 +113,12 @@ class RecipeServiceTest :
|
||||
|
||||
@Test
|
||||
fun `updatePublicData() update the location of a recipe mixes in the mix service according to the RecipePublicDataDto`() {
|
||||
val publicData = recipePublicDataDto(mixesLocation = setOf(
|
||||
val publicData = recipePublicDataDto(
|
||||
mixesLocation = setOf(
|
||||
mixLocationDto(mixId = 0L, location = "Loc 1"),
|
||||
mixLocationDto(mixId = 1L, location = "Loc 2")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
service.updatePublicData(publicData)
|
||||
|
||||
@ -193,16 +194,14 @@ class RecipeImageServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getByIdForRecipe() throws EntityNotFoundException when no image with the given recipe and image id exists`() {
|
||||
fun `getByIdForRecipe() throws RecipeImageNotFoundException when no image with the given recipe and image id exists`() {
|
||||
doReturn(imagePath).whenever(service).getPath(imageId, recipeId)
|
||||
whenever(recipeService.getById(recipeId)).doReturn(recipe)
|
||||
whenever(fileService.readAsBytes(imagePath)).doAnswer { throw NoSuchFileException(imagePath) }
|
||||
|
||||
val exception =
|
||||
assertThrows<EntityNotFoundException> { service.getByIdForRecipe(imageId, recipeId) }
|
||||
assertEquals("$recipeId/$imageId", exception.value)
|
||||
assertThrows<RecipeImageNotFoundException> { service.getByIdForRecipe(imageId, recipeId) }
|
||||
}
|
||||
|
||||
|
||||
// getAllIdsForRecipe()
|
||||
|
||||
@Test
|
||||
|
@ -7,7 +7,7 @@ import dev.fyloz.colorrecipesexplorer.model.recipeStep
|
||||
import dev.fyloz.colorrecipesexplorer.repository.RecipeStepRepository
|
||||
|
||||
class RecipeStepServiceTest :
|
||||
AbstractModelServiceTest<RecipeStep, RecipeStepService, RecipeStepRepository>() {
|
||||
AbstractModelServiceTest<RecipeStep, RecipeStepService, RecipeStepRepository>() {
|
||||
override val repository: RecipeStepRepository = mock()
|
||||
override val service: RecipeStepService = spy(RecipeStepServiceImpl(repository))
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package dev.fyloz.colorrecipesexplorer.service.files
|
||||
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import dev.fyloz.colorrecipesexplorer.exception.SimdutWriteException
|
||||
import dev.fyloz.colorrecipesexplorer.model.Material
|
||||
import dev.fyloz.colorrecipesexplorer.model.material
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
@ -26,7 +25,7 @@ class SimdutServiceTest {
|
||||
|
||||
@JvmName("withNullableMaterialPath")
|
||||
private inline fun withMaterialPath(material: Material? = null, exists: Boolean = true, test: (String) -> Unit) =
|
||||
withMaterialPath(material ?: this.material, exists, test)
|
||||
withMaterialPath(material ?: this.material, exists, test)
|
||||
|
||||
private inline fun withMaterialPath(material: Material, exists: Boolean = true, test: (String) -> Unit) {
|
||||
val path = "data/simdut/${material.id}"
|
||||
@ -109,8 +108,7 @@ class SimdutServiceTest {
|
||||
|
||||
whenever(fileService.write(simdutMultipart, path)).doReturn(false)
|
||||
|
||||
val exception = assertThrows<SimdutWriteException> { service.write(material, simdutMultipart) }
|
||||
assertEquals(material, exception.material)
|
||||
assertThrows<SimdutWriteException> { service.write(material, simdutMultipart) }
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user