Start from somewhere
This commit is contained in:
commit
6de543b0a0
140 changed files with 14915 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
ant-lib
|
||||||
79
README.md
Normal file
79
README.md
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
Pasteque core
|
||||||
|
=============
|
||||||
|
|
||||||
|
Common libraries for Pasteque.
|
||||||
|
|
||||||
|
Setup
|
||||||
|
-----
|
||||||
|
|
||||||
|
This projects can use either Maven or Ant and Ivy.
|
||||||
|
|
||||||
|
### Setting up Maven
|
||||||
|
|
||||||
|
This build uses `pom.xml`.
|
||||||
|
|
||||||
|
Install maven with (Debian-based)
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install maven
|
||||||
|
```
|
||||||
|
|
||||||
|
The initialization is done automatically when building the project.
|
||||||
|
|
||||||
|
### Setting up Ant and Ivy
|
||||||
|
|
||||||
|
This build uses `build.xml` and `ivy.xml`.
|
||||||
|
|
||||||
|
Install And and Ivy (Debian-based)
|
||||||
|
|
||||||
|
```
|
||||||
|
apt install ant ivy
|
||||||
|
```
|
||||||
|
|
||||||
|
Debian doesn't link Ivy to Ant by default.
|
||||||
|
|
||||||
|
```
|
||||||
|
ln -s /usr/share/java/ivy.jar /usr/share/ant/lib/ivy.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the required dependencies with Ivy.
|
||||||
|
|
||||||
|
```
|
||||||
|
ant resolve
|
||||||
|
```
|
||||||
|
|
||||||
|
This will download the dependencies from Maven repositories and put them in `ant-lib`.
|
||||||
|
|
||||||
|
Build commands
|
||||||
|
--------------
|
||||||
|
|
||||||
|
### Compile
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn compile
|
||||||
|
or
|
||||||
|
ant compile
|
||||||
|
```
|
||||||
|
|
||||||
|
It will create compiled classes in `target/classes`
|
||||||
|
|
||||||
|
### Generate the documentation
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn javadoc:javadoc
|
||||||
|
or
|
||||||
|
ant javadoc
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate the javadoc and put it it `target/site/apidocs`.
|
||||||
|
|
||||||
|
### Clean the project
|
||||||
|
|
||||||
|
```
|
||||||
|
mvn clean
|
||||||
|
or
|
||||||
|
ant clean
|
||||||
|
ant lib-clean
|
||||||
|
```
|
||||||
|
|
||||||
|
It will delete the `target` directory. Te reset ant dependencies, use `ant lib-clean` to remove `ant-lib`.
|
||||||
82
build.xml
Normal file
82
build.xml
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
<project xmlns:ivy="antlib:org.apache.ivy.ant" name="pasteque-common" basedir="." default="resolve">
|
||||||
|
|
||||||
|
<!-- Project properties that must match those from pom.xml -->
|
||||||
|
<property name="project.build.sourceEncoding" value="utf8"/>
|
||||||
|
<property name="maven.compiler.target" value="17"/>
|
||||||
|
<property name="maven.compiler.source" value="17"/>
|
||||||
|
|
||||||
|
<!-- ant properties to match maven defaults -->
|
||||||
|
<property name="src.main" value="${basedir}/src/main/java"/>
|
||||||
|
<property name="dest" value="${basedir}/target"/>
|
||||||
|
<property name="dest.classes" value="${dest}/classes"/>
|
||||||
|
<property name="dest.javadoc" value="${dest}/site/apidocs"/>
|
||||||
|
|
||||||
|
<!-- ant-specific properties -->
|
||||||
|
<property name="dest.lib" value="${basedir}/ant-lib"/>
|
||||||
|
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
<!-- I N I T -->
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
|
||||||
|
<target name="resolve" description="Retrieve dependencies with Ivy">
|
||||||
|
<ivy:retrieve pattern="${dest.lib}/[conf]/[artifact].[ext]"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="init.classes">
|
||||||
|
<mkdir dir="${dest.classes}"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="init.javadoc">
|
||||||
|
<delete dir="${dest.javadoc}"/>
|
||||||
|
<mkdir dir="${dest.javadoc}"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
<!-- B U I L D -->
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
|
||||||
|
<target name="compile" depends="init.classes">
|
||||||
|
<javac srcdir="${src.main}"
|
||||||
|
destdir="${dest.classes}"
|
||||||
|
source="${maven.compiler.source}"
|
||||||
|
target="${maven.compiler.target}"
|
||||||
|
encoding="${project.build.sourceEncoding}"
|
||||||
|
includeAntRuntime="false">
|
||||||
|
<classpath>
|
||||||
|
<fileset dir="${dest.lib}">
|
||||||
|
<include name="**/*.jar"/>
|
||||||
|
</fileset>
|
||||||
|
</classpath>
|
||||||
|
<compilerarg value="-Xlint:classfile,divzero,empty,overloads,overrides,processing,removal,static,try,varargs"/>
|
||||||
|
</javac>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
<!-- J A V A D O C -->
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
|
||||||
|
<target name="javadoc" depends="init.javadoc" description="Generate javadoc">
|
||||||
|
<javadoc sourcepath="${src.main}"
|
||||||
|
destdir="${dest.javadoc}"
|
||||||
|
packagenames="org.pasteque.*">
|
||||||
|
<classpath>
|
||||||
|
<fileset dir="${dest.lib}/compile">
|
||||||
|
<include name="**/*.jar"/>
|
||||||
|
</fileset>
|
||||||
|
</classpath>
|
||||||
|
<arg line="--add-stylesheet "${basedir}/src/main/javadoc/javadoc-dark.css""/>
|
||||||
|
</javadoc>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
<!-- R E S E T -->
|
||||||
|
<!-- ================================================================= -->
|
||||||
|
|
||||||
|
<target name="clean" description="Clear the target directory">
|
||||||
|
<delete dir="${dest}"/>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
<target name="lib-clean" description="Clear the dependencies downloaded by resolve">
|
||||||
|
<delete dir="${dest.lib}"/>
|
||||||
|
</target>
|
||||||
|
</project>
|
||||||
13
ivy.xml
Normal file
13
ivy.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<ivy-module version="2.0">
|
||||||
|
<info organisation="org.pasteque" module="org.pasteque.common"/>
|
||||||
|
|
||||||
|
<configurations>
|
||||||
|
<conf name="compile" visibility="private" description="Include only dependencies required for compilation" />
|
||||||
|
<conf name="test" visibility="private" description="Include dependencies required to run tests" />
|
||||||
|
</configurations>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency org="org.json" name="json" rev="20240303" conf="compile->default" />
|
||||||
|
<dependency org="org.junit.jupiter" name="junit-jupiter-api" rev="latest.release" conf="test->default" />
|
||||||
|
</dependencies>
|
||||||
|
</ivy-module>
|
||||||
44
pom.xml
Normal file
44
pom.xml
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>org.pasteque</groupId>
|
||||||
|
<artifactId>org.pasteque.common</artifactId>
|
||||||
|
<version>8.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>Pasteque Common</name>
|
||||||
|
<description>Common definition, classes and utilities to share data and behaviour between Pasteque applications.</description>
|
||||||
|
|
||||||
|
<!-- Project properties that must match those from build.xml -->
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.8.0</version>
|
||||||
|
<configuration>
|
||||||
|
<addStylesheets>
|
||||||
|
<addStylesheet>javadoc-dark.css</addStylesheet>
|
||||||
|
</addStylesheets>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20240303</version>
|
||||||
|
<type>jar</type>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer's contact field code to be able to customize them with a
|
||||||
|
* {@link org.pasteque.common.model.option.CustomContactFieldsOption}.
|
||||||
|
*/
|
||||||
|
public enum CustomerContactField
|
||||||
|
{
|
||||||
|
/** Code for first name label. Code: {@code Label.Customer.FirstName}. */
|
||||||
|
FIRST_NAME("Label.Customer.FirstName"),
|
||||||
|
/** Code for last name label. Code: {@code Label.Customer.LastName}. */
|
||||||
|
LAST_NAME("Label.Customer.LastName"),
|
||||||
|
/** Code for email label. Code: {@code Label.Customer.Email}. */
|
||||||
|
EMAIL("Label.Customer.Email"),
|
||||||
|
/** Code for phone1 label. Code: {@code Label.Customer.Phone}. */
|
||||||
|
PHONE1("Label.Customer.Phone"),
|
||||||
|
/** Code for phone2 label. Code: {@code Label.Customer.Phone2}. */
|
||||||
|
PHONE2("Label.Customer.Phone2"),
|
||||||
|
/** Code for fax label. Code: {@code Label.Customer.Fax}. */
|
||||||
|
FAX("Label.Customer.Fax"),
|
||||||
|
/** Code for address label. Code: {@code Label.Customer.Addr}. */
|
||||||
|
ADDR1("Label.Customer.Addr"),
|
||||||
|
/** Code for address2 label. Code: {@code Label.Customer.Addr2}. */
|
||||||
|
ADDR2("Label.Customer.Addr2"),
|
||||||
|
/** Code for zip code label. Code: {@code Label.Customer.ZipCode}. */
|
||||||
|
ZIP_CODE("Label.Customer.ZipCode"),
|
||||||
|
/** Code for city label. Code: {@code Label.Customer.City}. */
|
||||||
|
CITY("Label.Customer.City"),
|
||||||
|
/** Code for region label. Code: {@code Label.Customer.Region}. */
|
||||||
|
REGION("Label.Customer.Region"),
|
||||||
|
/** Code for country label. Code: {@code Label.Customer.Country}. */
|
||||||
|
COUNTRY("Label.Customer.Country");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static CustomerContactField fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (CustomerContactField v : CustomerContactField.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
CustomerContactField(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Interface to get default images. Client must implement one and
|
||||||
|
* Instantiate one to use in
|
||||||
|
* {@link org.pasteque.common.constants.DefaultImages}.</p>
|
||||||
|
*/
|
||||||
|
public class DefaultImageImplementation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the default image for the given models.
|
||||||
|
* @param model The type of model.
|
||||||
|
* @return The image as raw bytes.
|
||||||
|
* @throws IOException When an error occurs while reading the default image.
|
||||||
|
*/
|
||||||
|
public byte[] getDefaultImage(ModelName model) throws IOException {
|
||||||
|
return this.getClass().getClassLoader().getResourceAsStream("broken.png").readAllBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Interface to get default images from a singleton.</p>
|
||||||
|
* <p>Clients must define an implementation by overriding
|
||||||
|
* {@link org.pasteque.common.constants.DefaultImageImplementation} and
|
||||||
|
* setting a singleton in {@link singleton}.</p>
|
||||||
|
* {@see org.pasteque.common.constants.DefaultImagesImplementation}
|
||||||
|
*/
|
||||||
|
public class DefaultImages
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The singleton to use to load default images.
|
||||||
|
*/
|
||||||
|
public static DefaultImageImplementation singleton = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default image for the given models from the singleton.
|
||||||
|
* @param model The type of model.
|
||||||
|
* @return The image as raw bytes.
|
||||||
|
* @throws IOException When an error occurs while reading an image.
|
||||||
|
*/
|
||||||
|
public static byte[] getDefaultImage(ModelName model) throws IOException {
|
||||||
|
if (singleton == null) {
|
||||||
|
// Use a dummy implementation with empty images
|
||||||
|
// not to crash
|
||||||
|
singleton = new DefaultImageImplementation();
|
||||||
|
}
|
||||||
|
return singleton.getDefaultImage(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/main/java/org/pasteque/common/constants/ModelName.java
Normal file
81
src/main/java/org/pasteque/common/constants/ModelName.java
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Model names for loosely coupled records, like images that can be associated
|
||||||
|
* to multiple types of records.</p>
|
||||||
|
* <p>They are mostly used for database storage and access.</p>
|
||||||
|
*/
|
||||||
|
public enum ModelName
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.CategoryDTO}.
|
||||||
|
* Code: {@code category}.
|
||||||
|
*/
|
||||||
|
CATEGORY("category"),
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.CustomerDTO}.
|
||||||
|
* Code: {@code customer}.
|
||||||
|
*/
|
||||||
|
CUSTOMER("customer"),
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.ProductDTO}
|
||||||
|
* and {@link org.pasteque.common.datatransfer.dto.CompositionDTO}.
|
||||||
|
* Code: {@code product}.
|
||||||
|
*/
|
||||||
|
PRODUCT("product"),
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.UserDTO}.
|
||||||
|
* Code: {@code user}.
|
||||||
|
*/
|
||||||
|
USER("user"),
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.PaymentModeDTO}.
|
||||||
|
* Code: {@code paymentmode}.
|
||||||
|
*/
|
||||||
|
PAYMENT_MODE("paymentmode"),
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.PaymentModeDTO.PaymentModeValueDTO}.
|
||||||
|
* Code: {@code paymentmodevalue}.
|
||||||
|
*/
|
||||||
|
PAYMENT_MODE_VALUE("paymentmodevalue"),
|
||||||
|
/**
|
||||||
|
* Name for {@link org.pasteque.common.datatransfer.dto.ImageDTO}.
|
||||||
|
* Code: {@code image}.
|
||||||
|
*/
|
||||||
|
IMAGE("image");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static ModelName fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (ModelName v : ModelName.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
ModelName(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/main/java/org/pasteque/common/constants/OptionName.java
Normal file
69
src/main/java/org/pasteque/common/constants/OptionName.java
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of known option names.
|
||||||
|
*/
|
||||||
|
public enum OptionName
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Option holding accounting preferences and configuration.
|
||||||
|
* Code {@code accounting.config}.
|
||||||
|
*/
|
||||||
|
ACCOUNTING_CONFIG("accounting.config"),
|
||||||
|
/**
|
||||||
|
* Option holding the URL of the preferred back office.
|
||||||
|
* Code {@code backoffice.url}.
|
||||||
|
*/
|
||||||
|
BACKOFFICE_URL("backoffice.url"),
|
||||||
|
/**
|
||||||
|
* Option holding custom contact fields for customers.
|
||||||
|
* Code {@code customer.customFields}.
|
||||||
|
*/
|
||||||
|
CUSTOMER_CONTACT_FIELDS("customer.customFields"),
|
||||||
|
/**
|
||||||
|
* Option holding accounting preferences and configuration.
|
||||||
|
* Code {@code jsadmin.[local|<host>/<pathname>].accountingConfig}.
|
||||||
|
* @deprecated The option was tied to JSadmin and could be
|
||||||
|
* configured differently for each URL. Use
|
||||||
|
* {@link ACCOUNTING_CONFIG} for a single and client-independent
|
||||||
|
* option instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
LEGACY_ACCOUNTING_CONFIG("jsadmin.*.accountingConfig");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values. This may not be an error
|
||||||
|
* when using custom options.
|
||||||
|
*/
|
||||||
|
public static OptionName fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (OptionName v : OptionName.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
OptionName(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
148
src/main/java/org/pasteque/common/constants/ResourceName.java
Normal file
148
src/main/java/org/pasteque/common/constants/ResourceName.java
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>List of known resource names. This list is not exhaustive and should not
|
||||||
|
* be used to check if a resource name is correct.</p>
|
||||||
|
* <p>Some resources may not be in use anymore.</p>
|
||||||
|
* <p>XML documents are sent to the printer on the desktop client.
|
||||||
|
* These documents control both the printer and the customer's display.</p>
|
||||||
|
*/
|
||||||
|
public enum ResourceName
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Name for the logo to print on the tickets for the desktop client.
|
||||||
|
* Code: {@code Printer.Ticket.Logo}.
|
||||||
|
*/
|
||||||
|
PRINTER_TICKET_LOGO("Printer.Ticket.Logo"),
|
||||||
|
/**
|
||||||
|
* Name for the custom text to print between the logo and the content
|
||||||
|
* of the ticket for the desktop client.
|
||||||
|
* Code: {@code Printer.Ticket.Header}.
|
||||||
|
*/
|
||||||
|
PRINTER_TICKET_HEADER("Printer.Ticket.Header"),
|
||||||
|
/**
|
||||||
|
* Name for the custom text to print at the bottom of the ticket.
|
||||||
|
* Code: {@code Printer.Ticket.Footer}.
|
||||||
|
*/
|
||||||
|
PRINTER_TICKET_FOOTER("Printer.Ticket.Footer"),
|
||||||
|
/**
|
||||||
|
* Name for the logo to print on the tickets for the android client
|
||||||
|
* for the desktop client.
|
||||||
|
* Code: {@code MobilePrinter.Ticket.Logo}.
|
||||||
|
*/
|
||||||
|
MOBILEPRINTER_TICKET_LOGO("MobilePrinter.Ticket.Logo"),
|
||||||
|
/**
|
||||||
|
* Name for the custom text to print between the logo and the content
|
||||||
|
* of the ticket for the android client.
|
||||||
|
* Code: {@code MobilePrinter.Ticket.Header}.
|
||||||
|
*/
|
||||||
|
MOBILEPRINTER_TICKET_HEADER("MobilePrinter.Ticket.Header"),
|
||||||
|
/**
|
||||||
|
* Name for the custom text to print at the bottom of the ticket
|
||||||
|
* for the android client.
|
||||||
|
* Code: {@code MobilePrinter.Ticket.Footer}.
|
||||||
|
*/
|
||||||
|
MOBILEPRINTER_TICKET_FOOTER("MobilePrinter.Ticket.Footer"),
|
||||||
|
/**
|
||||||
|
* Name for the xml definition of coins and bills to show when counting
|
||||||
|
* cash when opening or closing the session.
|
||||||
|
* Code: {@code payment.cash}.
|
||||||
|
*/
|
||||||
|
CASH_VALUES("payment.cash"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document to print to solely open the drawer
|
||||||
|
* from the printer. It is not used to open the drawer when printing
|
||||||
|
* a ticket.
|
||||||
|
* Code: {@code Printer.OpenDrawer}.
|
||||||
|
*/
|
||||||
|
PRINTER_OPEN_DRAWER("Printer.OpenDrawer"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document to print when opening the cash session.
|
||||||
|
* @deprecated Not in use since Pasteque Desktop 8.10. The document
|
||||||
|
* is since embedded in code.
|
||||||
|
* Code: {@code Printer.OpenCash}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
PRINTER_OPEN_CASH("Printer.OpenCash"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document to print when previewing the Z ticket.
|
||||||
|
* @deprecated Not in use since Pasteque Desktop 8.10. The document
|
||||||
|
* is since embedded in code.
|
||||||
|
* Code: {@code Printer.PartialCash}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
PRINTER_PARTIAL_CASH("Printer.PartialCash"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document to print when opening the cash session.
|
||||||
|
* @deprecated Not in use since Pasteque Desktop 8.10. The document
|
||||||
|
* is since embedded in code.
|
||||||
|
* Code: {@code Printer.CloseCash}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
PRINTER_CLOSE_CASH("Printer.CloseCash"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document to print as a welcoming message
|
||||||
|
* when opening a new order for the desktop client.
|
||||||
|
* Code: {@code Printer.Start}.
|
||||||
|
*/
|
||||||
|
PRINTER_START("Printer.Start"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document that defines how order lines are shown
|
||||||
|
* for the desktop client.
|
||||||
|
* Code: {@code MajorTicket.Line}.
|
||||||
|
*/
|
||||||
|
TICKET_LINE("MajorTicket.Line"),
|
||||||
|
/**
|
||||||
|
* Name for the xml document that defines the title of the window
|
||||||
|
* for the desktop client.
|
||||||
|
* @deprecated Not in use since Pasteque Desktop 8.9. The title
|
||||||
|
* is since embedded in code.
|
||||||
|
* Code: {@code Window.Title}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
WINDOW_TITLE("Window.Title"),
|
||||||
|
/**
|
||||||
|
* Name for the binary image to use as the application logo to
|
||||||
|
* show along the title in the in-app header.
|
||||||
|
* @deprecated Removed in Pasteque Desktop 8.9, not in use even
|
||||||
|
* before.
|
||||||
|
* Code: {@code Window.Logo}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
WINDOW_LOGO("Window.Logo");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static ResourceName fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (ResourceName v : ResourceName.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
ResourceName(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of known resource types.
|
||||||
|
*/
|
||||||
|
public enum ResourceType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The resource content is plain text.
|
||||||
|
* Code: {@code 0}.
|
||||||
|
*/
|
||||||
|
PLAIN_TEXT(0),
|
||||||
|
/**
|
||||||
|
* The resource content is a binary image stored in base64.
|
||||||
|
* Code: {@code 1}.
|
||||||
|
*/
|
||||||
|
IMAGE(1),
|
||||||
|
/**
|
||||||
|
* The resource content is raw binary stored in base64.
|
||||||
|
* Code: {@code 2}.
|
||||||
|
*/
|
||||||
|
BINARY(2);
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static ResourceType fromCode(int code) throws IllegalArgumentException {
|
||||||
|
for (ResourceType v : ResourceType.values()) {
|
||||||
|
if (v.getCode() == code) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(String.valueOf(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
ResourceType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/main/java/org/pasteque/common/constants/ScaleType.java
Normal file
64
src/main/java/org/pasteque/common/constants/ScaleType.java
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale types enumeration and units.
|
||||||
|
*/
|
||||||
|
public enum ScaleType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sold in piece.
|
||||||
|
* Code: {@code 0}.
|
||||||
|
*/
|
||||||
|
ATOMIC(0),
|
||||||
|
/**
|
||||||
|
* Sold by kilogrammes.
|
||||||
|
* Code: {@code 1}.
|
||||||
|
*/
|
||||||
|
WEIGHT(1),
|
||||||
|
/**
|
||||||
|
* Sold by liters.
|
||||||
|
* Code: {@code 2}.
|
||||||
|
*/
|
||||||
|
VOLUME(2),
|
||||||
|
/**
|
||||||
|
* Sold by hours.
|
||||||
|
* Code: {@code 3}.
|
||||||
|
*/
|
||||||
|
TIME(3);
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static ScaleType fromCode(int code) throws IllegalArgumentException {
|
||||||
|
switch(code) {
|
||||||
|
case 0: return ATOMIC;
|
||||||
|
case 1: return WEIGHT;
|
||||||
|
case 2: return VOLUME;
|
||||||
|
case 3: return TIME;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(Integer.valueOf(code).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
ScaleType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Common constants and enumerations.
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.constants;
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Building a model from DTO that weren't linked together.
|
||||||
|
*/
|
||||||
|
public class AssociationInconsistencyException extends Exception
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -979786104345156783L;
|
||||||
|
|
||||||
|
/** {@see getFromField()} */
|
||||||
|
private final String fromField;
|
||||||
|
/** {@see getFromValue()} */
|
||||||
|
private final String fromValue;
|
||||||
|
/** {@see getToField()} */
|
||||||
|
private final String toField;
|
||||||
|
/** {@see getToValue()} */
|
||||||
|
private final String toValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Automatically check inconsistencies and throw the exception.</p>
|
||||||
|
* <p>It does nothing if everything is fine.</p>
|
||||||
|
* <p>Use null checks to pass fromValues and toValues:</p>
|
||||||
|
* <code>(from == null) ? null : from.getFromValue()</code>
|
||||||
|
* @param fromField The name of the field from the main model that should
|
||||||
|
* link to the target.
|
||||||
|
* @param fromValue The value of the field to check. Use null if the main
|
||||||
|
* model is null.
|
||||||
|
* @param toField The name of the field in the target model that should
|
||||||
|
* hold the same value.
|
||||||
|
* @param toValue The value of the field to check against. Use null if the
|
||||||
|
* target model is null.
|
||||||
|
* @throws AssociationInconsistencyException When fromValue and toValue
|
||||||
|
* don't match.
|
||||||
|
*/
|
||||||
|
public static void autoThrow(String fromField, Object fromValue,
|
||||||
|
String toField, Object toValue)
|
||||||
|
throws AssociationInconsistencyException {
|
||||||
|
// Null checks
|
||||||
|
if (fromValue == null && toValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (fromValue != null) {
|
||||||
|
if (toValue == null) {
|
||||||
|
throw new AssociationInconsistencyException(
|
||||||
|
fromField, fromValue.toString(),
|
||||||
|
toField, "null"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else { // fromValue is null
|
||||||
|
if (toValue != null) {
|
||||||
|
throw new AssociationInconsistencyException(
|
||||||
|
fromField, "null",
|
||||||
|
toField, toValue.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Neither are null, equality check
|
||||||
|
if (!fromValue.equals(toValue)) {
|
||||||
|
throw new AssociationInconsistencyException(
|
||||||
|
fromField, fromValue.toString(),
|
||||||
|
toField, toValue.toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an exception from all fields.
|
||||||
|
* @param fromField See {@link getFromField}.
|
||||||
|
* @param fromValue See {@link getFromValue}.
|
||||||
|
* @param toField See {@link getFromValue}.
|
||||||
|
* @param toValue See {@link getToValue}.
|
||||||
|
*/
|
||||||
|
public AssociationInconsistencyException(
|
||||||
|
String fromField,
|
||||||
|
String fromValue,
|
||||||
|
String toField,
|
||||||
|
String toValue) {
|
||||||
|
super(String.format("Association mismatch: from %s=%s to %s=%s",
|
||||||
|
fromField,
|
||||||
|
(fromValue == null) ? "null" : fromValue,
|
||||||
|
toField,
|
||||||
|
(toValue == null) ? "null" : toValue));
|
||||||
|
this.fromField = fromField;
|
||||||
|
this.fromValue = fromValue;
|
||||||
|
this.toField = toField;
|
||||||
|
this.toValue = toValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the field that holds the association.
|
||||||
|
* @return The name of the field that holds the association.
|
||||||
|
*/
|
||||||
|
public String getFromField() {
|
||||||
|
return fromField;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the associated record.
|
||||||
|
* @return The value of the associated record.
|
||||||
|
*/
|
||||||
|
public String getFromValue() {
|
||||||
|
return fromValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the field of the linked record.
|
||||||
|
* @return The name of the field of the linked record.
|
||||||
|
*/
|
||||||
|
public String getToField() {
|
||||||
|
return toField;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of the field of the linked record that didn't match
|
||||||
|
* the expected value.
|
||||||
|
* @return The value of the field of the linked record.
|
||||||
|
*/
|
||||||
|
public String getToValue() {
|
||||||
|
return toValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.datatransfer.dto.CategoryDTO;
|
||||||
|
import org.pasteque.common.model.Category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Store and load categories.</p>
|
||||||
|
*/
|
||||||
|
public interface CategoryDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Store a category. It will be updated if it already exists.
|
||||||
|
* @param category The category to store.
|
||||||
|
* @throws IOException When an error occurs while storing the category.
|
||||||
|
*/
|
||||||
|
public void storeCategory(CategoryDTO category) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store multiple categories at once. It may be faster than storing them
|
||||||
|
* one by one.
|
||||||
|
* @param categories The categories to store
|
||||||
|
* @param clear Whether to replace the content of the source or update it.
|
||||||
|
* @throws IOException When an error occurs while storing the categories.
|
||||||
|
* The data may already be wiped and some categories may have been stored.
|
||||||
|
*/
|
||||||
|
public void storeCategories(Iterable<CategoryDTO> categories, boolean clear) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of categories that don't have a parent.
|
||||||
|
* @return The list of categories at the top of the category tree.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public List<Category> getTopCategories() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of categories that are children of the given one.
|
||||||
|
* @param parent The category to get children from.
|
||||||
|
* @return The list of categories that have the given parent.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public List<Category> getSubCategories(Category parent) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a category from the source. It may leave the source in
|
||||||
|
* an inconsistent state if a parent category is dropped
|
||||||
|
* without its children.
|
||||||
|
* @param reference The reference of the category.
|
||||||
|
* @return True if the category was found and deleted. False if not found.
|
||||||
|
* @throws IOException When an error occurs while deleting the currency.
|
||||||
|
*/
|
||||||
|
public boolean dropCategory(String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the data source of all its content.
|
||||||
|
* @throws IOException When an error occurs while deleting its content.
|
||||||
|
* Some data may have been deleted.
|
||||||
|
*/
|
||||||
|
public void clearCategories() throws IOException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.datatransfer.dto.CurrencyDTO;
|
||||||
|
import org.pasteque.common.model.Currency;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidRecordException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Store and load currencies.</p>
|
||||||
|
* <p>The behavior of the source is not defined when multiple main currencies
|
||||||
|
* are stored, but it must always bring the same result from the same source.</p>
|
||||||
|
*/
|
||||||
|
public interface CurrencyDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Store a currency. It will be updated if it already exists.
|
||||||
|
* @param currency The currency to store.
|
||||||
|
* @throws IOException When an error occurs while storing the currency.
|
||||||
|
*/
|
||||||
|
public void storeCurrency(CurrencyDTO currency) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store multiple currencies at once. It may be faster than storing them one
|
||||||
|
* by one.
|
||||||
|
* @param currencies The currencies to store
|
||||||
|
* @param clear Whether to replace the content of the source or update it.
|
||||||
|
* @throws IOException When an error occurs while storing the currencies.
|
||||||
|
* The data may already be wiped and some currencies may have been stored.
|
||||||
|
*/
|
||||||
|
public void storeCurrencies(Iterable<CurrencyDTO> currencies, boolean clear) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a currency by its reference.
|
||||||
|
* @param reference The reference of the currency.
|
||||||
|
* @return The currency, null if not found.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public Currency getCurrency(String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all currencies.
|
||||||
|
* @return All currencies.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public List<Currency> getCurrencies() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the main currency.
|
||||||
|
* @return The currency. It is never null.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
* @throws InvalidRecordException When no main currency is set.
|
||||||
|
*/
|
||||||
|
public Currency getMainCurrency() throws IOException, InvalidRecordException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a currency from the source. It may leave the source in
|
||||||
|
* an inconsistent state if the main currency is dropped
|
||||||
|
* without replacement.
|
||||||
|
* @param reference The reference of the currency.
|
||||||
|
* @return True if the currency was found and deleted. False if not found.
|
||||||
|
* @throws IOException When an error occurs while deleting the currency.
|
||||||
|
*/
|
||||||
|
public boolean dropCurrency(String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the data source of all its content.
|
||||||
|
* @throws IOException When an error occurs while deleting its content.
|
||||||
|
* Some data may have been deleted.
|
||||||
|
*/
|
||||||
|
public void clearCurrencies() throws IOException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.datatransfer.dto.CustomerDTO;
|
||||||
|
import org.pasteque.common.model.Customer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Store, load and search customers.</p>
|
||||||
|
*/
|
||||||
|
public interface CustomerDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Store a customer's account. It will be updated if it already exists.
|
||||||
|
* @param customer The account to store.
|
||||||
|
* @throws IOException When an error occurs while storing the account.
|
||||||
|
*/
|
||||||
|
public void storeCustomer(CustomerDTO customer) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store multiple accounts at once. It may be faster than storing them one
|
||||||
|
* by one.
|
||||||
|
* @param customers The accounts to store
|
||||||
|
* @param clear Whether to replace the content of the source or update it.
|
||||||
|
* @throws IOException When an error occurs while storing the accounts.
|
||||||
|
* The data may already be wiped and some accounts may have been stored.
|
||||||
|
*/
|
||||||
|
public void storeCustomers(Iterable<CustomerDTO> customers, boolean clear) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a customer by their id.
|
||||||
|
* @param id The id.
|
||||||
|
* @return The customer, null if not found.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public Customer getCustomerById(String id) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a customer by their card.
|
||||||
|
* @param card The customer's card.
|
||||||
|
* @return The customer, null if not found.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public Customer getCustomerByCard(String card) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Find customers matching a request.</p>
|
||||||
|
* <p>The search should be case-insensitive and based upon multiple fields:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Their display name</li>
|
||||||
|
* <li>Their card</li>
|
||||||
|
* <li>Contact information may be used, when using extended search.</li>
|
||||||
|
* </ul>
|
||||||
|
* @param search The search string.
|
||||||
|
* @param withinContactInfo Whether to use only the basic search keys or
|
||||||
|
* also search within contact informations. This may be much slower.
|
||||||
|
* @return A list of customers matching the request.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public List<Customer> findCustomers(String search, boolean withinContactInfo) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the list of frequent customers.
|
||||||
|
* @param customers The accounts to store
|
||||||
|
* @param clear Whether to replace the content of the source or update it.
|
||||||
|
* @throws IOException When an error occurs while storing the accounts.
|
||||||
|
* The data may already be wiped and some accounts may have been stored.
|
||||||
|
*/
|
||||||
|
public void storeTop10Customers(Iterable<CustomerDTO> customers, boolean clear) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get the short list of most frequent customers.</p>
|
||||||
|
* <p>This special list is built from the API and should not be recomputed
|
||||||
|
* locally.</p>
|
||||||
|
* @return The list of most frequent customers.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public List<Customer> getTop10Customers() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a customer's account from the source.
|
||||||
|
* @param id The id of the account.
|
||||||
|
* @return True if the account was found and deleted. False if not found.
|
||||||
|
* @throws IOException When an error occurs while deleting the account.
|
||||||
|
*/
|
||||||
|
public boolean dropCustomer(String id) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the data source of all its content.
|
||||||
|
* @throws IOException When an error occurs while deleting its content.
|
||||||
|
* Some data may have been deleted.
|
||||||
|
*/
|
||||||
|
public void clearCustomers() throws IOException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.pasteque.common.constants.ModelName;
|
||||||
|
import org.pasteque.common.datatransfer.dto.ImageDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store and load images for other models.
|
||||||
|
*/
|
||||||
|
public interface ImageDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Store an image. It will be updated if it already exists.
|
||||||
|
* @param image The image to store.
|
||||||
|
* @throws IOException When an error occurs while storing the image.
|
||||||
|
*/
|
||||||
|
public void storeImage(ImageDTO image) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an image.
|
||||||
|
* @param modelName The type of model the image is associated to.
|
||||||
|
* @param reference The reference to load.
|
||||||
|
* It may not be the actual reference, see
|
||||||
|
* {@link org.pasteque.common.datatransfer.dto.ImageDTO#getReference()}.
|
||||||
|
* @return The associated image, null if not found.
|
||||||
|
* @throws IOException When an error occurs while reading the image.
|
||||||
|
*/
|
||||||
|
public ImageDTO getImage(ModelName modelName, String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an image from the source.
|
||||||
|
* @param modelName The type of model the image is associated to.
|
||||||
|
* @param reference The reference to delete.
|
||||||
|
* It may not be the actual reference, see
|
||||||
|
* {@link org.pasteque.common.datatransfer.dto.ImageDTO#getReference()}.
|
||||||
|
* @return True if the image was found and deleted. False if not found.
|
||||||
|
* @throws IOException When an error occurs while deleting the image.
|
||||||
|
*/
|
||||||
|
public boolean dropImage(ModelName modelName, String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the data source of all its content.
|
||||||
|
* @throws IOException When an error occurs while deleting its content.
|
||||||
|
* Some data may have been deleted.
|
||||||
|
*/
|
||||||
|
public void clearImages() throws IOException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.datatransfer.dto.PaymentModeDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store and load payment modes.
|
||||||
|
*/
|
||||||
|
public interface PaymentModeDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Store a payment mode. It will be updated if it already exists.
|
||||||
|
* @param paymentMode The payment mode to store.
|
||||||
|
* @throws IOException When an error occurs while storing the payment mode.
|
||||||
|
*/
|
||||||
|
public void storePaymentMode(PaymentModeDTO paymentMode) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store multiple payment modes at once. It may be faster than storing them one
|
||||||
|
* by one.
|
||||||
|
* @param paymentModes The payment modes to store
|
||||||
|
* @param clear Whether to replace the content of the source or update it.
|
||||||
|
* @throws IOException When an error occurs while storing the payment modes.
|
||||||
|
* The data may already be wiped and some payment modes may have been stored.
|
||||||
|
*/
|
||||||
|
public void storePaymentModes(Iterable<PaymentModeDTO> paymentModes, boolean clear) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a payment mode by its reference.
|
||||||
|
* @param reference The reference of the payment mode.
|
||||||
|
* @return The payment mode, null if not found.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public PaymentModeDTO getPaymentMode(String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all payment modes.
|
||||||
|
* @return All payment modes.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public List<PaymentModeDTO> getPaymentModes() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a payment mode from the source.
|
||||||
|
* @param reference The reference of the payment mode.
|
||||||
|
* @return True if the currency was found and deleted. False if not found.
|
||||||
|
* @throws IOException When an error occurs while deleting the currency.
|
||||||
|
*/
|
||||||
|
public boolean dropPaymentMode(String reference) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the data source of all its content.
|
||||||
|
* @throws IOException When an error occurs while deleting its content.
|
||||||
|
* Some data may have been deleted.
|
||||||
|
*/
|
||||||
|
public void clearPaymentModes() throws IOException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.pasteque.common.datasource.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API host URL.
|
||||||
|
*/
|
||||||
|
public class APIHost
|
||||||
|
{
|
||||||
|
/** {@see getHostUrl()} */
|
||||||
|
private final String host;
|
||||||
|
/** {@see getUser()} */
|
||||||
|
private final String user;
|
||||||
|
/** {@see getHostUrl()} */
|
||||||
|
private final boolean https;
|
||||||
|
/** {@see getHardware()} */
|
||||||
|
private final String hardware;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a host without a specific cash register.
|
||||||
|
* @param host The main URL.
|
||||||
|
* @param user The API user.
|
||||||
|
* @param useHttps Whether to use http or https. Ignored if the host
|
||||||
|
* already contains the protocol.
|
||||||
|
*/
|
||||||
|
public APIHost(String host, String user, boolean useHttps) {
|
||||||
|
if (host.startsWith("http://")) {
|
||||||
|
host = host.substring(7);
|
||||||
|
useHttps = false;
|
||||||
|
} else if (host.startsWith("https://")) {
|
||||||
|
host = host.substring(8);
|
||||||
|
useHttps = true;
|
||||||
|
}
|
||||||
|
this.host = host;
|
||||||
|
this.user = user;
|
||||||
|
this.https = useHttps;
|
||||||
|
this.hardware = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a host for a specific cash register.
|
||||||
|
* @param host The main URL.
|
||||||
|
* @param user The API user.
|
||||||
|
* @param hardware The machine name.
|
||||||
|
* @param useHttps Whether to use http or https. Ignored if the host
|
||||||
|
* already contains the protocol.
|
||||||
|
*/
|
||||||
|
public APIHost(String host, String user, String hardware, boolean useHttps) {
|
||||||
|
if (host.startsWith("http://")) {
|
||||||
|
host = host.substring(7);
|
||||||
|
useHttps = false;
|
||||||
|
} else if (host.startsWith("https://")) {
|
||||||
|
host = host.substring(8);
|
||||||
|
useHttps = true;
|
||||||
|
}
|
||||||
|
this.host = host;
|
||||||
|
this.user = user;
|
||||||
|
this.hardware = hardware;
|
||||||
|
this.https = useHttps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the URL of the host.
|
||||||
|
* @return The full URL.
|
||||||
|
*/
|
||||||
|
public String getHostUrl() {
|
||||||
|
if (this.https) {
|
||||||
|
return String.format("https://%s", this.host);
|
||||||
|
} else {
|
||||||
|
return String.format("http://%s", this.host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the login for this host.
|
||||||
|
* @return The user name.
|
||||||
|
*/
|
||||||
|
public String getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the hardware identifier for this host.
|
||||||
|
* @return The hardware identifier. Can be null.
|
||||||
|
*/
|
||||||
|
public String getHardware() {
|
||||||
|
return this.hardware;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.pasteque.common.datasource.api;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic API response. Use subclasses to parse the content.
|
||||||
|
*/
|
||||||
|
public class APIResponse
|
||||||
|
{
|
||||||
|
/** {@see getToken()} */
|
||||||
|
private final APIToken token;
|
||||||
|
/** {@see getStatus()} */
|
||||||
|
protected final APIResponseStatus status;
|
||||||
|
/** Internal reader to parse the response. */
|
||||||
|
protected final Reader reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a parseable response.
|
||||||
|
* @param reader The Reader initialized to the response content.
|
||||||
|
* It must not be shared with any other instance.
|
||||||
|
* @throws ParseException When an errors occurs while parsing the token
|
||||||
|
* or status.
|
||||||
|
*/
|
||||||
|
public APIResponse(Reader reader) throws ParseException {
|
||||||
|
this.reader = reader;
|
||||||
|
this.reader.startObject();
|
||||||
|
this.token = new APIToken(reader.readString("token"));
|
||||||
|
this.status = APIResponseStatus.fromStatus(reader.readString("status"));
|
||||||
|
this.reader.endObject();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the new token associated to this response.
|
||||||
|
* @return The new token that was sent along the response.
|
||||||
|
*/
|
||||||
|
public APIToken getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the response status.
|
||||||
|
* @return The response status.
|
||||||
|
*/
|
||||||
|
public APIResponseStatus getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.pasteque.common.datasource.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response code from the API.
|
||||||
|
*/
|
||||||
|
public enum APIResponseStatus
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The call went well and the response holds the expected content.
|
||||||
|
* Its code is "ok".
|
||||||
|
*/
|
||||||
|
STATUS_OK("ok"),
|
||||||
|
/**
|
||||||
|
* The call was rejected due to incorrect input.
|
||||||
|
* Its code is "rej".
|
||||||
|
*/
|
||||||
|
STATUS_REJECTED("rej"),
|
||||||
|
/**
|
||||||
|
* An error occured on the side of the API.
|
||||||
|
* Its code is "err".
|
||||||
|
*/
|
||||||
|
STATUS_ERROR("err");
|
||||||
|
|
||||||
|
/** {@see getStatus()} */
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a ResponseStatus from its code.
|
||||||
|
* @param status The status to parse.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException If status is null or not an expected status.
|
||||||
|
*/
|
||||||
|
public static APIResponseStatus fromStatus(String status)
|
||||||
|
throws IllegalArgumentException {
|
||||||
|
if (status == null) {
|
||||||
|
throw new IllegalArgumentException("null");
|
||||||
|
}
|
||||||
|
if (STATUS_OK.getStatus().equals(status)) {
|
||||||
|
return STATUS_OK;
|
||||||
|
} else if (STATUS_REJECTED.getStatus().equals(status)) {
|
||||||
|
return STATUS_REJECTED;
|
||||||
|
} else if (STATUS_ERROR.getStatus().equals(status)) {
|
||||||
|
return STATUS_ERROR;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(Integer.valueOf(status).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param status See {@link getStatus()}.
|
||||||
|
*/
|
||||||
|
private APIResponseStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The status code.
|
||||||
|
*/
|
||||||
|
public String getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.pasteque.common.datasource.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT used to identify authenticated users.
|
||||||
|
*/
|
||||||
|
public class APIToken
|
||||||
|
{
|
||||||
|
/** {@see getToken()} */
|
||||||
|
private final String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a token from its string.
|
||||||
|
* @param token The token string.
|
||||||
|
*/
|
||||||
|
public APIToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the raw JWT token.
|
||||||
|
* @return The raw JWT token.
|
||||||
|
*/
|
||||||
|
public String getToken() {
|
||||||
|
return this.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.pasteque.common.datasource.api;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.DTOFactory;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API response that expects a single or an array of DTO.
|
||||||
|
*/
|
||||||
|
// TODO WIP
|
||||||
|
public class SimpleAPIResponse<T extends DTOInterface> extends APIResponse
|
||||||
|
{
|
||||||
|
private final DTOFactory<T> factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
* @param reader {@inheritDoc}
|
||||||
|
* @throws ParseException {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public SimpleAPIResponse(Reader reader) throws ParseException {
|
||||||
|
super(reader);
|
||||||
|
this.factory = null;//new DTOFactory<T>(this.reader, T.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content as an array of DTO.
|
||||||
|
* @return The list of DTO read.
|
||||||
|
* @throws ParseException When the content is not an array
|
||||||
|
* of the expected DTO class.
|
||||||
|
*/
|
||||||
|
public List<T> getArrayContent() throws ParseException {
|
||||||
|
this.reader.startObject(); // Root
|
||||||
|
this.reader.startArray("content");
|
||||||
|
int size = this.reader.getArraySize();
|
||||||
|
List<T> result = new ArrayList<T>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
this.reader.startObject(i);
|
||||||
|
result.add(factory.readObject());
|
||||||
|
this.reader.endObject();
|
||||||
|
}
|
||||||
|
this.reader.endArray(); // "content"
|
||||||
|
this.reader.endObject(); // Root
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content as a single DTO.
|
||||||
|
* @return The DTO read.
|
||||||
|
* @throws ParseException When the content is not a single DTO
|
||||||
|
* of the expected class.
|
||||||
|
*/
|
||||||
|
public T getObjectContent() throws ParseException {
|
||||||
|
this.reader.startObject(); // Root
|
||||||
|
this.reader.startObject("content");
|
||||||
|
T obj = this.factory.readObject();
|
||||||
|
this.reader.endObject(); // "content"
|
||||||
|
this.reader.endObject(); // Root
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Utilities to read and send data from/to the API.
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.datasource.api;
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
package org.pasteque.common.datasource.implementation;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import org.pasteque.common.constants.ModelName;
|
||||||
|
import org.pasteque.common.datasource.CurrencyDataSource;
|
||||||
|
import org.pasteque.common.datasource.ImageDataSource;
|
||||||
|
import org.pasteque.common.datasource.PaymentModeDataSource;
|
||||||
|
import org.pasteque.common.datatransfer.dto.CurrencyDTO;
|
||||||
|
import org.pasteque.common.datatransfer.dto.ImageDTO;
|
||||||
|
import org.pasteque.common.datatransfer.dto.PaymentModeDTO;
|
||||||
|
import org.pasteque.common.model.Currency;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidRecordException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Store DTO in serialized form in individual files.</p>
|
||||||
|
* <p>This source is not efficient and should be used only for a small number
|
||||||
|
* of records.</p>
|
||||||
|
* <p>This implementation is system-dependant and files created from a client
|
||||||
|
* may not be compatible with files created by an other client, even if they
|
||||||
|
* both share the same source.</p>
|
||||||
|
*/
|
||||||
|
public class SerializedFileDataSource
|
||||||
|
implements CurrencyDataSource, ImageDataSource, PaymentModeDataSource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Replacement to convert names that contains separators (i.e. / or \)
|
||||||
|
* to not alter the path when converting a reference to a file name.
|
||||||
|
* Code {@code _SEP_}.
|
||||||
|
*/
|
||||||
|
public static final String SEPARATOR_REPLACEMENT = "_SEP_";
|
||||||
|
|
||||||
|
private File baseDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a source inside the given directory.
|
||||||
|
* @param baseDir The base directory. It must be writeable.
|
||||||
|
* @throws IOException When the directory is not writeable.
|
||||||
|
*/
|
||||||
|
public SerializedFileDataSource(File baseDir) throws IOException {
|
||||||
|
this.baseDir = baseDir;
|
||||||
|
if (!this.baseDir.canWrite()) {
|
||||||
|
throw new IOException("Base directory must be writeable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file for an image.
|
||||||
|
* @param modelName The model name of the image.
|
||||||
|
* @param reference The reference of the record.
|
||||||
|
* @param createDir Whether to create inexistent subdirectories or not.
|
||||||
|
*/
|
||||||
|
private File getImageFile(String modelName, String reference, boolean createDir) {
|
||||||
|
File subDir = new File(this.baseDir, ModelName.IMAGE.getCode());
|
||||||
|
if (!subDir.exists() && createDir) {
|
||||||
|
subDir.mkdirs();
|
||||||
|
}
|
||||||
|
subDir = new File(subDir, modelName);
|
||||||
|
if (!subDir.exists() && createDir) {
|
||||||
|
subDir.mkdirs();
|
||||||
|
}
|
||||||
|
return new File(subDir, this.sanitizeReference(reference));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check and transform a file name to not alter the path.
|
||||||
|
* @param fileName The name of the file to check.
|
||||||
|
* @return A new file name with characters that alter the path replaced
|
||||||
|
* by safe ones.
|
||||||
|
*/
|
||||||
|
private String sanitizeReference(String fileName) {
|
||||||
|
String pathSep = System.getProperty("path.separator");
|
||||||
|
return fileName.replace(pathSep, SEPARATOR_REPLACEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file for a record outside images.
|
||||||
|
* @param modelName The model name of the record.
|
||||||
|
* @param reference The reference of the record.
|
||||||
|
* @param createDir Whether to create inexistent subdirectories or not.
|
||||||
|
*/
|
||||||
|
private File getFile(String modelName, String reference, boolean createDir) {
|
||||||
|
File subDir = new File(this.baseDir, modelName);
|
||||||
|
if (!subDir.exists() && createDir) {
|
||||||
|
subDir.mkdirs();
|
||||||
|
}
|
||||||
|
return new File(subDir, this.sanitizeReference(reference));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************/
|
||||||
|
/* Image Data Source */
|
||||||
|
/*********************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeImage(ImageDTO image) throws IOException {
|
||||||
|
File targetFile = getImageFile(image.getModel(), image.getReference(), true);
|
||||||
|
FileOutputStream fos = new FileOutputStream(targetFile);
|
||||||
|
fos.write(image.getImage());
|
||||||
|
fos.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageDTO getImage(ModelName modelName, String reference) throws IOException {
|
||||||
|
File targetFile = this.getFile(modelName.getCode(), reference, false);
|
||||||
|
if (!targetFile.exists()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!targetFile.isFile()) {
|
||||||
|
throw new IOException(String.format("Target file for image %s %s is not a file",
|
||||||
|
modelName, reference));
|
||||||
|
}
|
||||||
|
if (!targetFile.canRead()) {
|
||||||
|
throw new IOException(String.format("Target file for image %s %s is not readable",
|
||||||
|
modelName, reference));
|
||||||
|
}
|
||||||
|
FileInputStream fis = new FileInputStream(targetFile);
|
||||||
|
byte[] data = fis.readAllBytes();
|
||||||
|
fis.close();
|
||||||
|
return new ImageDTO(modelName.getCode(), reference, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dropImage(ModelName modelName, String reference) throws IOException {
|
||||||
|
File targetFile = this.getFile(modelName.getCode(), reference, false);
|
||||||
|
if (targetFile.exists()) {
|
||||||
|
targetFile.delete();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearImages() throws IOException {
|
||||||
|
for (ModelName modelName : ModelName.values()) {
|
||||||
|
File subDir = new File(this.baseDir, modelName.getCode());
|
||||||
|
if (subDir.exists()) {
|
||||||
|
subDir.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***************************/
|
||||||
|
/* PaymentMode Data Source */
|
||||||
|
/***************************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storePaymentMode(PaymentModeDTO paymentMode) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storePaymentModes(Iterable<PaymentModeDTO> paymentModes, boolean clear) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PaymentModeDTO getPaymentMode(String reference) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PaymentModeDTO> getPaymentModes() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dropPaymentMode(String reference) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearPaymentModes() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/************************/
|
||||||
|
/* Currency Data Source */
|
||||||
|
/************************/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeCurrency(CurrencyDTO currency) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void storeCurrencies(Iterable<CurrencyDTO> currencies, boolean clear) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Currency getCurrency(String reference) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Currency> getCurrencies() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Currency getMainCurrency() throws IOException, InvalidRecordException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dropCurrency(String reference) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearCurrencies() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* <p>Generic implementations of data sources.</p>
|
||||||
|
* <p>These implementations does not rely upon any dependency outside the base
|
||||||
|
* java module. Clients can redefine their own data source optimized for their
|
||||||
|
* target platform.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.datasource.implementation;
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* <p>Read and write data from/to a local source.</p>
|
||||||
|
* <p>Data sources are used for caching, searching and storage,
|
||||||
|
* but are not linked together. Adding or removing a record to/from
|
||||||
|
* a source must not alter the status of other sources.
|
||||||
|
* Persistance and propagation are managed separately.</p>
|
||||||
|
* <p>They are mid-level between DTO and models. Storing raw DTO and
|
||||||
|
* reading ready-to-use models.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.datasource;
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityFieldConstraint;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidFieldException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Pasteque API version Transfer Object.</p>
|
||||||
|
*/
|
||||||
|
public class APIVersionDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 4654741357430549138L;
|
||||||
|
|
||||||
|
/** {@see getLevel()} */
|
||||||
|
private final int level;
|
||||||
|
/** {@see getRevision()} */
|
||||||
|
private final int revision;
|
||||||
|
/** {@see getVersion()} */
|
||||||
|
private final String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a version from all fields.
|
||||||
|
* @param level See {@link getLevel()}.
|
||||||
|
* @param revision See {@link getRevision()}.
|
||||||
|
* @param version See {@link getVersion()}.
|
||||||
|
*/
|
||||||
|
public APIVersionDTO(
|
||||||
|
int level,
|
||||||
|
int revision,
|
||||||
|
String version) {
|
||||||
|
this.level = level;
|
||||||
|
this.revision = revision;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a raw encoded string.
|
||||||
|
* @param reader The reader that will parse the data. It must already be reading
|
||||||
|
* the object with {@link Reader#startObject()} or {@link Reader#startObject(int)}.
|
||||||
|
* @throws ParseException If the data cannot be parsed or is malformed for
|
||||||
|
* this DTO.
|
||||||
|
*/
|
||||||
|
public APIVersionDTO(Reader reader) throws ParseException {
|
||||||
|
this.level = reader.readInt("level");
|
||||||
|
this.revision = reader.readInt("revision");
|
||||||
|
this.version = reader.readString("version");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the compatibility level.
|
||||||
|
* Clients and API must share the same level.
|
||||||
|
* @return The API level.
|
||||||
|
*/
|
||||||
|
public int getLevel() {
|
||||||
|
return this.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the revision. All revisions are retro-compatible within
|
||||||
|
* the same level.
|
||||||
|
* @return The revision number of the API.
|
||||||
|
*/
|
||||||
|
public int getRevision() {
|
||||||
|
return this.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the String representation of this version.
|
||||||
|
* @return The API version, commonly level.revision.
|
||||||
|
*/
|
||||||
|
public String getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
List<IntegrityException> exceptions = new ArrayList<IntegrityException>();
|
||||||
|
if (this.version == null || "".equals(this.version)) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.NOT_NULL,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"version",
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
throw new IntegrityExceptions(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Category of products Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, categories cannot be deactivated.</p>
|
||||||
|
*/
|
||||||
|
public class CategoryDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 8211717550217795453L;
|
||||||
|
|
||||||
|
/** {@see getParent} */
|
||||||
|
private final Integer parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a category from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param dispOrder The display order.
|
||||||
|
* @param hasImage Whether an image is linked to this object.
|
||||||
|
* @param parent See {@link getParent}.
|
||||||
|
*/
|
||||||
|
public CategoryDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
int dispOrder,
|
||||||
|
boolean hasImage,
|
||||||
|
Integer parent) {
|
||||||
|
super(id, reference, label, dispOrder, true, hasImage);
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public CategoryDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, "reference", "label", "dispOrder", null, "hasImage");
|
||||||
|
if (!reader.isNull("parent")) {
|
||||||
|
this.parent = Integer.valueOf(reader.readInt("parent"));
|
||||||
|
} else {
|
||||||
|
this.parent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the parent category.
|
||||||
|
* @return The id of the parent category, null when this category is
|
||||||
|
* a first-level one.
|
||||||
|
*/
|
||||||
|
public Integer getParent() {
|
||||||
|
return this.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,218 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityFieldConstraint;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidFieldException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for common fields.
|
||||||
|
*/
|
||||||
|
/* package */ abstract class CommonDTO implements DTOInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Nullable internal identifier. This field will be deprecated once
|
||||||
|
* the reference is used to link records.
|
||||||
|
*/
|
||||||
|
protected final Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User friendly identifier. In v8 some models do not have reference,
|
||||||
|
* in that case it can return an empty string (but not null)
|
||||||
|
* until the API refactoring is done.
|
||||||
|
*/
|
||||||
|
protected final String reference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display name. In v8 some models may use an other name for that field.
|
||||||
|
*/
|
||||||
|
protected final String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Explicit display order. Records are sorted by dispOrder by default.
|
||||||
|
* When multiple records share the same dispOrder, they are sorted
|
||||||
|
* by label. Other sorting methods can be selected.</p>
|
||||||
|
* <p>In v8, some models do not have a dispOrder, in that case it can
|
||||||
|
* return 0 until the API refactoring is done.</p>
|
||||||
|
*/
|
||||||
|
protected final int dispOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the record should be visible or not. In v8 some DTO do not have
|
||||||
|
* this status, in that case it must return true.
|
||||||
|
*/
|
||||||
|
protected final boolean active;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the record has an image that can be retrieved from elsewhere.
|
||||||
|
* When the model cannot have an image, it must return false.
|
||||||
|
*/
|
||||||
|
protected final boolean hasImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a complete DTO.
|
||||||
|
* @param id The given id
|
||||||
|
* @param reference The reference. Set it to "" when the object
|
||||||
|
* doesn't have a reference yet.
|
||||||
|
* @param label The display name, label, preferred name...
|
||||||
|
* @param dispOrder The display order.
|
||||||
|
* @param active Whether this object is active or not.
|
||||||
|
* @param hasImage Whether an image is linked to this object.
|
||||||
|
*/
|
||||||
|
public CommonDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
int dispOrder,
|
||||||
|
boolean active,
|
||||||
|
boolean hasImage) {
|
||||||
|
this.id = id;
|
||||||
|
this.reference = reference;
|
||||||
|
this.label = label;
|
||||||
|
this.dispOrder = dispOrder;
|
||||||
|
this.active = active;
|
||||||
|
this.hasImage = hasImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Read a DTO from a raw encoded string.</p>
|
||||||
|
* <p>Use arguments to remap keys for generic attributes, set to null
|
||||||
|
* to not read it from the reader.</p>
|
||||||
|
* <p>Subclasses must initialize all other fields and call reader.endObject().</p>
|
||||||
|
* @param reader The reader that will parse the data. It must already be reading
|
||||||
|
* the object with {@link Reader#startObject()} or {@link Reader#startObject(int)}.
|
||||||
|
* @param referenceKey The key to use to read reference, set null to set it to "".
|
||||||
|
* @param labelKey The key to use to read label, set null to set it to "".
|
||||||
|
* @param dispOrderKey The key to use to read dispOrder, set null to set it to 0.
|
||||||
|
* @param activeKey The key to use to read active, set null to set it to true.
|
||||||
|
* @param hasImageKey The key to use to read hasImage, set null to set it to false.
|
||||||
|
* @throws ParseException If the data cannot be parsed or is malformed for
|
||||||
|
* this DTO.
|
||||||
|
*/
|
||||||
|
protected CommonDTO(Reader reader,
|
||||||
|
String referenceKey,
|
||||||
|
String labelKey,
|
||||||
|
String dispOrderKey,
|
||||||
|
String activeKey,
|
||||||
|
String hasImageKey) throws ParseException {
|
||||||
|
this.id = reader.readIntOrNull("id");
|
||||||
|
if (referenceKey != null) {
|
||||||
|
this.reference = reader.readString(referenceKey);
|
||||||
|
} else {
|
||||||
|
this.reference = "";
|
||||||
|
}
|
||||||
|
if (labelKey != null) {
|
||||||
|
this.label = reader.readString(labelKey);
|
||||||
|
} else {
|
||||||
|
this.label = "";
|
||||||
|
}
|
||||||
|
if (dispOrderKey != null) {
|
||||||
|
this.dispOrder = reader.readInt(dispOrderKey);
|
||||||
|
} else {
|
||||||
|
this.dispOrder = 0;
|
||||||
|
}
|
||||||
|
if (activeKey != null) {
|
||||||
|
this.active = reader.readBoolean(activeKey);
|
||||||
|
} else {
|
||||||
|
this.active = true;
|
||||||
|
}
|
||||||
|
if (hasImageKey != null) {
|
||||||
|
this.hasImage = reader.readBoolean(hasImageKey);
|
||||||
|
} else {
|
||||||
|
this.hasImage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the identifier. It may be null when not currently stored
|
||||||
|
* in a database.
|
||||||
|
* @return The identifier. May be null.
|
||||||
|
*/
|
||||||
|
public Integer getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display name.
|
||||||
|
* @return The label, name or preferred display name
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Reference. It is a user friendly identifier.
|
||||||
|
* When the model doesn't have a reference yet, it must be set
|
||||||
|
* to an empty string until a reference is set for all models.
|
||||||
|
* @return The reference.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return this.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get the explicit display order. Records are sorted by dispOrder by default.
|
||||||
|
* When multiple records share the same dispOrder, they are sorted
|
||||||
|
* by label. Other sorting methods can be provided.</p>
|
||||||
|
* <p>In v8, some models do not have a dispOrder, in that case it can
|
||||||
|
* return 0 until the API refactoring is done.</p>
|
||||||
|
* @return The display order. 0 when not set.
|
||||||
|
*/
|
||||||
|
public int getDispOrder() {
|
||||||
|
return this.dispOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the object is active or was deactivated.
|
||||||
|
* When the model doesn't have an active state yet, it must
|
||||||
|
* be set to true until the state is added to all models.
|
||||||
|
* @return The active state.
|
||||||
|
*/
|
||||||
|
public boolean isActive() {
|
||||||
|
return this.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an image is linked to the object.
|
||||||
|
* Images are stored separately. When the model cannot have
|
||||||
|
* images yet, it must be set to false until images are extended
|
||||||
|
* to all models.
|
||||||
|
* @return Whether an image is linked to the object.
|
||||||
|
*/
|
||||||
|
public boolean hasImage() {
|
||||||
|
return this.hasImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common integrity check. It checks whether a reference is set.
|
||||||
|
* It is called from {@link #checkIntegrity}. Subclasses should
|
||||||
|
* call this instead of super.checkIntegrity for common integrity check.
|
||||||
|
* @return The list of exception thrown during the check.
|
||||||
|
*/
|
||||||
|
protected List<IntegrityException> commonIntegrityCheck() {
|
||||||
|
List<IntegrityException> exceptions = new ArrayList<IntegrityException>();
|
||||||
|
if (this.reference == null) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.NOT_NULL,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"reference",
|
||||||
|
String.valueOf(this.getId()),
|
||||||
|
null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return exceptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
List<IntegrityException> exceptions = this.commonIntegrityCheck();
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
throw new IntegrityExceptions(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Composition product Data Transfer Object.</p>
|
||||||
|
* <p>Compositions are products that are composed of multiple other ones,
|
||||||
|
* picked one in multiple selections.</p>
|
||||||
|
*/
|
||||||
|
public class CompositionDTO extends ProductDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -3223110713928387147L;
|
||||||
|
|
||||||
|
/** Always true */
|
||||||
|
protected final boolean composition = true;
|
||||||
|
/** {@see getCompositionGroups()} */
|
||||||
|
private final ImmutableList<CompositionGroupDTO> compositionGroups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a products from all fields.
|
||||||
|
* @param id The product id.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param dispOrder The explicit display order within the category.
|
||||||
|
* @param active If this product should be shown in catalog or not.
|
||||||
|
* @param hasImage Whether an image is associated to this product.
|
||||||
|
* @param barcode See {@link getBarcode}
|
||||||
|
* @param priceBuy See {@link getPriceBuy}
|
||||||
|
* @param priceSell See {@link getPriceSell}
|
||||||
|
* @param scaled See {@link isScaled}
|
||||||
|
* @param scaleType See {@link getScaleType}
|
||||||
|
* @param scaleValue See {@link getScaleValue}
|
||||||
|
* @param discountEnabled See {@link isDiscountEnabled}
|
||||||
|
* @param discountRate See {@link getDiscountRate}
|
||||||
|
* @param prepay See {@link isPrepay}
|
||||||
|
* @param category See {@link getCategory}
|
||||||
|
* @param tax See {@link getTax}
|
||||||
|
* @param taxedPrice See {@link getTaxedPrice}
|
||||||
|
* @param compositionGroups See {@link getCompositionGroups}
|
||||||
|
*/
|
||||||
|
public CompositionDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
int dispOrder,
|
||||||
|
boolean active,
|
||||||
|
boolean hasImage,
|
||||||
|
String barcode,
|
||||||
|
Double priceBuy,
|
||||||
|
double priceSell,
|
||||||
|
boolean scaled,
|
||||||
|
int scaleType,
|
||||||
|
double scaleValue,
|
||||||
|
boolean discountEnabled,
|
||||||
|
double discountRate,
|
||||||
|
boolean prepay,
|
||||||
|
int category,
|
||||||
|
int tax,
|
||||||
|
double taxedPrice,
|
||||||
|
CompositionGroupDTO[] compositionGroups) {
|
||||||
|
super(id, reference, label, dispOrder, active, hasImage, barcode,
|
||||||
|
priceBuy, priceSell, scaled, scaleType, scaleValue,
|
||||||
|
discountEnabled, discountRate, prepay, category, tax, taxedPrice);
|
||||||
|
this.compositionGroups = new ImmutableList<CompositionGroupDTO>(compositionGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Whether this product is a composition or not.</p>
|
||||||
|
* @return True.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isComposition() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the choices available for this composition.
|
||||||
|
* @return The array of groups, containing a set of products for each.
|
||||||
|
*/
|
||||||
|
public ImmutableList<CompositionGroupDTO> getCompositionGroups() {
|
||||||
|
if (this.compositionGroups == null) {
|
||||||
|
return new ImmutableList<CompositionGroupDTO>();
|
||||||
|
}
|
||||||
|
return this.compositionGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Data Transfer Object for a group of choices within a composition.</p>
|
||||||
|
* <p>A composition is completed once a single product has been selected within
|
||||||
|
* each group.</p>
|
||||||
|
*/
|
||||||
|
public class CompositionGroupDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -5237752185922817170L;
|
||||||
|
|
||||||
|
/** {@see getLabel()} */
|
||||||
|
private final String label;
|
||||||
|
/** {@see getDispOrder()} */
|
||||||
|
private final int dispOrder;
|
||||||
|
/** {@see getCompositionProducts()} */
|
||||||
|
private final CompositionProductDTO[] compositionProducts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a composition group from all fields.
|
||||||
|
* @param label See {@link getLabel}
|
||||||
|
* @param dispOrder See {@link getDispOrder}
|
||||||
|
* @param compositionProducts See {@link getCompositionProducts}
|
||||||
|
*/
|
||||||
|
public CompositionGroupDTO(String label, int dispOrder, CompositionProductDTO[] compositionProducts) {
|
||||||
|
super();
|
||||||
|
this.label = label;
|
||||||
|
this.dispOrder = dispOrder;
|
||||||
|
this.compositionProducts = compositionProducts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the group.
|
||||||
|
* @return The name of the group.
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the explicit display order of the group.
|
||||||
|
* @return The explicit display order.
|
||||||
|
*/
|
||||||
|
public int getDispOrder() {
|
||||||
|
return dispOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all choices available within this group.
|
||||||
|
* @return The choices available within this group.
|
||||||
|
*/
|
||||||
|
public CompositionProductDTO[] getCompositionProducts() {
|
||||||
|
return compositionProducts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choice of a product in a composition Data Transfer Object.
|
||||||
|
*/
|
||||||
|
public class CompositionProductDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 4024627602322411550L;
|
||||||
|
|
||||||
|
/** {@see getProduct()} */
|
||||||
|
private final int product;
|
||||||
|
/** {@see getDispOrder()} */
|
||||||
|
private final int dispOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a composition product with all fields.
|
||||||
|
* @param product The id of the product.
|
||||||
|
* @param dispOrder The explicit display order in the group.
|
||||||
|
*/
|
||||||
|
public CompositionProductDTO(int product, int dispOrder) {
|
||||||
|
this.product = product;
|
||||||
|
this.dispOrder = dispOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the product.
|
||||||
|
* @return The id of the product.
|
||||||
|
*/
|
||||||
|
public int getProduct() {
|
||||||
|
return product;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the explicit display order.
|
||||||
|
* @return The display order.
|
||||||
|
*/
|
||||||
|
public int getDispOrder() {
|
||||||
|
return dispOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Currency Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, currencies cannot have an associated image
|
||||||
|
* nor a display order.</p>
|
||||||
|
*/
|
||||||
|
public class CurrencyDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -6259735426241774167L;
|
||||||
|
|
||||||
|
/** {@see getSymbol()} */
|
||||||
|
private final String symbol;
|
||||||
|
/** {@see getDecimalSeparator()} */
|
||||||
|
private final String decimalSeparator;
|
||||||
|
/** {@see getThousandsSeparator()} */
|
||||||
|
private final String thousandsSeparator;
|
||||||
|
/** {@see getFormat} */
|
||||||
|
private final String format;
|
||||||
|
/** {@see getRate()} */
|
||||||
|
private final double rate;
|
||||||
|
/** {@see isMain()} */
|
||||||
|
private final boolean main;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a currency from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param active Whether this object is active or not.
|
||||||
|
* @param symbol See {@link getSymbol}.
|
||||||
|
* @param decimalSeparator See {@link getDecimalSeparator}.
|
||||||
|
* @param thousandsSeparator See {@link getThousandsSeparator}.
|
||||||
|
* @param format See {@link getFormat}.
|
||||||
|
* @param rate See {@link getRate}.
|
||||||
|
* @param main See {@link isMain}.
|
||||||
|
*/
|
||||||
|
public CurrencyDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
boolean active,
|
||||||
|
String symbol,
|
||||||
|
String decimalSeparator,
|
||||||
|
String thousandsSeparator,
|
||||||
|
String format,
|
||||||
|
double rate,
|
||||||
|
boolean main) {
|
||||||
|
super(id, reference, label, 0, active, false);
|
||||||
|
this.symbol = symbol;
|
||||||
|
this.decimalSeparator = decimalSeparator;
|
||||||
|
this.thousandsSeparator = thousandsSeparator;
|
||||||
|
this.format = format;
|
||||||
|
this.rate = rate;
|
||||||
|
this.main = main;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public CurrencyDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, "reference", "label", null, "visible", null);
|
||||||
|
this.symbol = reader.readString("symbol");
|
||||||
|
this.decimalSeparator = reader.readString("decimalSeparator");
|
||||||
|
this.thousandsSeparator = reader.readString("thousandsSeparator");
|
||||||
|
this.format = reader.readString("format");
|
||||||
|
this.rate = reader.readDouble("rate");
|
||||||
|
this.main = reader.readBoolean("rate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the symbol to use for this currency.
|
||||||
|
* @return The symbol. Empty string when not set.
|
||||||
|
* @see getFormat()
|
||||||
|
*/
|
||||||
|
public String getSymbol() {
|
||||||
|
if (this.symbol == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the decimal separator.
|
||||||
|
* @return The string to use as decimal separator. Empty string when not set.
|
||||||
|
* @see getFormat()
|
||||||
|
*/
|
||||||
|
public String getDecimalSeparator() {
|
||||||
|
if (this.decimalSeparator == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.decimalSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the thousands separator.
|
||||||
|
* @return The string to use as thousands separator. Empty string when not set.
|
||||||
|
* @see getFormat()
|
||||||
|
*/
|
||||||
|
public String getThousandsSeparator() {
|
||||||
|
if (this.thousandsSeparator == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.thousandsSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get the format string to display values in this currency.</p>
|
||||||
|
* <p>The format string contains the given symbols:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>'¤' or '$' to print the currency symbol (see {@link getSymbol}</li>
|
||||||
|
* <li>'.' to print the decimal separator (see {@link getDecimalSeparator}</li>
|
||||||
|
* <li>',' to print the thousands separator (see {@link getThousandsSeparator}</li>
|
||||||
|
* <li>'0' for any significative number</li>
|
||||||
|
* <li>'#' for any non-significative number</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>For example for 2 significative numbers and currency after the number: "#,##0.00¤".</p>
|
||||||
|
* @return The format.
|
||||||
|
* {@see java.text.DecimalFormat}
|
||||||
|
* {@see java.text.DecimalFormatSymbols}
|
||||||
|
*/
|
||||||
|
public String getFormat() {
|
||||||
|
return this.format;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the conversion rate to the main currency.
|
||||||
|
* @return The conversion rate. It has no meaning when {@link isMain} is true.
|
||||||
|
*/
|
||||||
|
public double getRate() {
|
||||||
|
return this.rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this currency is the main one.
|
||||||
|
* <p>All prices are computed with the main currency, the value of alternative
|
||||||
|
* currencies are converted to the main one using this rate:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li><pre>Value in main currency = value in alt currency / rate</pre></li>
|
||||||
|
* <li><pre>Value in alt currency = value in main currency * rate</pre></li>
|
||||||
|
* <li><pre>1 in main currency = rate in alt currency</pre></li>
|
||||||
|
* </ul>
|
||||||
|
* @return The conversion rate.
|
||||||
|
* {@see org.pasteque.common.model.Currency#convertToMain}
|
||||||
|
* {@see org.pasteque.common.model.Currency#convertFromMain}
|
||||||
|
*/
|
||||||
|
public boolean isMain() {
|
||||||
|
return this.main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,414 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Customer account Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, customers don't have a reference and it will always be an empty string,
|
||||||
|
* nor have a display order.</p>
|
||||||
|
* <p>All contact fields are informative and can be recycled to hold an other information. Options from the API can
|
||||||
|
* redefine and other meaning by changing the label of the field. All those informations will be redefined
|
||||||
|
* in future version to be more extensible and ease the synchronization with external CRM softwares.</p>
|
||||||
|
*/
|
||||||
|
public class CustomerDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 2904701878615495822L;
|
||||||
|
|
||||||
|
/** {@see getCard()} */
|
||||||
|
private final String card;
|
||||||
|
/** {@see getMaxDebt()} */
|
||||||
|
private final double maxDebt;
|
||||||
|
/** {@see getBalance()} */
|
||||||
|
private final double balance;
|
||||||
|
/** {@see getExpireDate()} */
|
||||||
|
private final Date expireDate;
|
||||||
|
/** {@see getDiscountProfile()} */
|
||||||
|
private final Integer discountProfile;
|
||||||
|
/** {@see getTariffArea()} */
|
||||||
|
private final Integer tariffArea;
|
||||||
|
/** {@see getTax()} */
|
||||||
|
private final Integer tax;
|
||||||
|
|
||||||
|
// Contact fields
|
||||||
|
// These fields may be moved away to ease compatibility with CRM softwares.
|
||||||
|
/** {@see getFirstName()} */
|
||||||
|
private final String firstName;
|
||||||
|
/** {@see getLastName()} */
|
||||||
|
private final String lastName;
|
||||||
|
/** {@see getEmail()} */
|
||||||
|
private final String email;
|
||||||
|
/** {@see getPhone1()} */
|
||||||
|
private final String phone1;
|
||||||
|
/** {@see getPhone2()} */
|
||||||
|
private final String phone2;
|
||||||
|
/** {@see getFax()} */
|
||||||
|
private final String fax;
|
||||||
|
/** {@see getAddr1()} */
|
||||||
|
private final String addr1;
|
||||||
|
/** {@see getAddr2()} */
|
||||||
|
private final String addr2;
|
||||||
|
/** {@see getZipCode()} */
|
||||||
|
private final String zipCode;
|
||||||
|
/** {@see getCity()} */
|
||||||
|
private final String city;
|
||||||
|
/** {@see getRegion()} */
|
||||||
|
private final String region;
|
||||||
|
/** {@see getCountry()} */
|
||||||
|
private final String country;
|
||||||
|
/** {@see getNote()} */
|
||||||
|
private final String note;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a customer from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param label The customer's display name (dispName).
|
||||||
|
* @param card See {@link getCard}.
|
||||||
|
* @param maxDebt See {@link getMaxDebt}.
|
||||||
|
* @param balance See {@link getBalance}
|
||||||
|
* @param expireDate See {@link getExpireDate}.
|
||||||
|
* @param discountProfile See {@link getDiscountProfile}.
|
||||||
|
* @param tariffArea See {@link getTariffArea}.
|
||||||
|
* @param tax See {@link getTax}.
|
||||||
|
* @param active See {@link isActive}.
|
||||||
|
* @param hasImage See {@link hasImage}.
|
||||||
|
* @param firstName See {@link getFirstName}.
|
||||||
|
* @param lastName See {@link getLastName}.
|
||||||
|
* @param email See {@link getEmail}.
|
||||||
|
* @param phone1 See {@link getPhone1}.
|
||||||
|
* @param phone2 See {@link getPhone2}.
|
||||||
|
* @param fax See {@link getFax}.
|
||||||
|
* @param addr1 See {@link getAddr1}.
|
||||||
|
* @param addr2 See {@link getAddr2}.
|
||||||
|
* @param zipCode See {@link getZipCode}.
|
||||||
|
* @param city See {@link getCity}.
|
||||||
|
* @param region See {@link getRegion}.
|
||||||
|
* @param country See {@link getCountry}.
|
||||||
|
* @param note See {@link getNote}.
|
||||||
|
*/
|
||||||
|
public CustomerDTO(
|
||||||
|
Integer id,
|
||||||
|
String label,
|
||||||
|
String card,
|
||||||
|
double maxDebt,
|
||||||
|
double balance,
|
||||||
|
Date expireDate,
|
||||||
|
Integer discountProfile,
|
||||||
|
Integer tariffArea,
|
||||||
|
Integer tax,
|
||||||
|
boolean active,
|
||||||
|
boolean hasImage,
|
||||||
|
String firstName,
|
||||||
|
String lastName,
|
||||||
|
String email,
|
||||||
|
String phone1,
|
||||||
|
String phone2,
|
||||||
|
String fax,
|
||||||
|
String addr1,
|
||||||
|
String addr2,
|
||||||
|
String zipCode,
|
||||||
|
String city,
|
||||||
|
String region,
|
||||||
|
String country,
|
||||||
|
String note) {
|
||||||
|
super(id, "", label, 0, active, hasImage);
|
||||||
|
this.card = card;
|
||||||
|
this.maxDebt = maxDebt;
|
||||||
|
this.balance = balance;
|
||||||
|
this.expireDate = expireDate;
|
||||||
|
this.discountProfile = discountProfile;
|
||||||
|
this.tariffArea = tariffArea;
|
||||||
|
this.tax = tax;
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
this.email = email;
|
||||||
|
this.phone1 = phone1;
|
||||||
|
this.phone2 = phone2;
|
||||||
|
this.fax = fax;
|
||||||
|
this.addr1 = addr1;
|
||||||
|
this.addr2 = addr2;
|
||||||
|
this.zipCode = zipCode;
|
||||||
|
this.city = city;
|
||||||
|
this.region = region;
|
||||||
|
this.country = country;
|
||||||
|
this.note = note;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public CustomerDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, null, "dispName", null, "visible", "hasImage");
|
||||||
|
this.card = reader.readString("card");
|
||||||
|
this.maxDebt = reader.readDouble("maxDebt");
|
||||||
|
this.balance = reader.readDouble("balance");
|
||||||
|
this.expireDate = reader.readDateOrNull("expireDate");
|
||||||
|
this.discountProfile = reader.readIntOrNull("discountProfile");
|
||||||
|
this.tariffArea = reader.readIntOrNull("tariffArea");
|
||||||
|
this.tax = reader.readIntOrNull("tax");
|
||||||
|
this.firstName = reader.readStringOrEmpty("firstName");
|
||||||
|
this.lastName = reader.readStringOrEmpty("lastName");
|
||||||
|
this.email = reader.readStringOrEmpty("email");
|
||||||
|
this.phone1 = reader.readStringOrEmpty("phone1");
|
||||||
|
this.phone2 = reader.readStringOrEmpty("phone2");
|
||||||
|
this.fax = reader.readStringOrEmpty("fax");
|
||||||
|
this.addr1 = reader.readStringOrEmpty("addr1");
|
||||||
|
this.addr2 = reader.readStringOrEmpty("addr2");
|
||||||
|
this.zipCode = reader.readStringOrEmpty("zipCode");
|
||||||
|
this.city = reader.readStringOrEmpty("city");
|
||||||
|
this.region = reader.readStringOrEmpty("region");
|
||||||
|
this.country = reader.readStringOrEmpty("country");
|
||||||
|
this.note = reader.readStringOrEmpty("note");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias of {@link getLabel} to match the technical name.
|
||||||
|
* @return The preferred display name.
|
||||||
|
*/
|
||||||
|
public String getDispName() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional card number or text. When set, it must Start with a 'c'
|
||||||
|
* for compatibility with quick scan.
|
||||||
|
* @return The customer's card number/string. Empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getCard() {
|
||||||
|
if (this.card == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum amount of debt this customer can contract.
|
||||||
|
* The balance may still be above the maximum debt in some cases.
|
||||||
|
* @return The maximum amount.
|
||||||
|
*/
|
||||||
|
public double getMaxDebt() {
|
||||||
|
return this.maxDebt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current balance of this customer. When it is positive,
|
||||||
|
* the customer has prepaid something. When negative, the customer has contracted debt.
|
||||||
|
* @return The current balance.
|
||||||
|
*/
|
||||||
|
public double getBalance() {
|
||||||
|
return this.balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the informative expiration date.
|
||||||
|
* @return The expiration date, can be null.
|
||||||
|
*/
|
||||||
|
public Date getExpireDate() {
|
||||||
|
return this.expireDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the discount profile to assign to this customer.
|
||||||
|
* @return The discount profile id. Can be null.
|
||||||
|
*/
|
||||||
|
public Integer getDiscountProfile() {
|
||||||
|
return this.discountProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the tariff area to assign to this customer.
|
||||||
|
* @return The tariff area id. Can be null.
|
||||||
|
*/
|
||||||
|
public Integer getTariffArea() {
|
||||||
|
return this.tariffArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the alternative tax to assign to orders from this customer.
|
||||||
|
* @return The alternative tax id. Can be null.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Integer getTax() {
|
||||||
|
return this.tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return First name. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getFirstName() {
|
||||||
|
if (this.firstName == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Last name. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getLastName() {
|
||||||
|
if (this.lastName == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Email address. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getEmail() {
|
||||||
|
if (this.email == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Phone number. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getPhone1() {
|
||||||
|
if (this.phone1 == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.phone1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Alternative phone number. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getPhone2() {
|
||||||
|
if (this.phone2 == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.phone2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Fax number. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getFax() {
|
||||||
|
if (this.fax == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.fax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Address, first line. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getAddr1() {
|
||||||
|
if (this.addr1 == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.addr1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Address, second line. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getAddr2() {
|
||||||
|
if (this.addr2 == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.addr2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Zip code. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getZipCode() {
|
||||||
|
if (this.zipCode == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.zipCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return City. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getCity() {
|
||||||
|
if (this.city == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.city;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Region. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getRegion() {
|
||||||
|
if (this.region == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.region;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Country. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getCountry() {
|
||||||
|
if (this.country == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.country;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contact information that can be recycled to other meanings.
|
||||||
|
* These fields may be moved away in future versions to ease compatibility
|
||||||
|
* with CRM softwares.
|
||||||
|
* @return Free note. Will be an empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getNote() {
|
||||||
|
if (this.note == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.note;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// No reference to check.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Discount profile Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, discount profiles cannot have a reference,
|
||||||
|
* nor display order nor image and cannot be deactivated.</p>
|
||||||
|
*/
|
||||||
|
public class DiscountProfileDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -2505636588019239236L;
|
||||||
|
|
||||||
|
/** See {@link getDiscountRate()}. */
|
||||||
|
private final double discountRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a discount profile from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param label The display name, it is used as reference.
|
||||||
|
* @param discountRate See {@link getDiscountRate()}.
|
||||||
|
*/
|
||||||
|
public DiscountProfileDTO(
|
||||||
|
Integer id,
|
||||||
|
String label,
|
||||||
|
double discountRate) {
|
||||||
|
super(id, "", label, 0, true, false);
|
||||||
|
this.discountRate = discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public DiscountProfileDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, null, "label", null, null, null);
|
||||||
|
this.discountRate = reader.readDouble("rate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label. DiscountProfile has no reference and will use
|
||||||
|
* its label as one.
|
||||||
|
* @return The label.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getReference() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount rate to apply. The rate should be between 0.0 and 1.0
|
||||||
|
* but it is not checked.
|
||||||
|
* @return The discount rate to apply for this profile.
|
||||||
|
*/
|
||||||
|
public double getDiscountRate() {
|
||||||
|
return this.discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<IntegrityException> commonIntegrityCheck() {
|
||||||
|
// DiscountProfile has no reference to check.
|
||||||
|
return new ArrayList<IntegrityException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/main/java/org/pasteque/common/datatransfer/dto/FloorDTO.java
Normal file
134
src/main/java/org/pasteque/common/datatransfer/dto/FloorDTO.java
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Restaurant floor or room Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, floors has no reference, cannot be deactivated,
|
||||||
|
* nor have an associated image.</p>
|
||||||
|
*/
|
||||||
|
public class FloorDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -4474106026709243994L;
|
||||||
|
|
||||||
|
/** {@see getPlaces()} */
|
||||||
|
private final ImmutableList<PlaceDTO> places;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a floor from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param label The label.
|
||||||
|
* @param dispOrder The display order.
|
||||||
|
* @param places See {@link getPlaces}.
|
||||||
|
*/
|
||||||
|
public FloorDTO(
|
||||||
|
Integer id,
|
||||||
|
String label,
|
||||||
|
int dispOrder,
|
||||||
|
PlaceDTO[] places) {
|
||||||
|
super(id, "", label, dispOrder, true, false);
|
||||||
|
this.places = new ImmutableList<PlaceDTO>(places);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public FloorDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, null, "label", "dispOrder", null, null);
|
||||||
|
reader.startArray("places");
|
||||||
|
PlaceDTO[] places = new PlaceDTO[reader.getArraySize()];
|
||||||
|
for (int i = 0; i < reader.getArraySize(); i++) {
|
||||||
|
reader.startObject(i);
|
||||||
|
places[i] = new PlaceDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
this.places = new ImmutableList<PlaceDTO>(places);
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of places within this floor.
|
||||||
|
* @return The list of places.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PlaceDTO> getPlaces() {
|
||||||
|
if (this.places == null) {
|
||||||
|
return new ImmutableList<PlaceDTO>();
|
||||||
|
}
|
||||||
|
return this.places;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Place Data Transfer Object. Places are always bound to a floor.</p>
|
||||||
|
* <p>The coordinates are abstract and relative to each other within the floor.
|
||||||
|
* See {@link org.pasteque.common.view.CoordStretcher} to convert abstract
|
||||||
|
* coordinates to coordinates within boundaries.</p>
|
||||||
|
*/
|
||||||
|
public class PlaceDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 8129968704161611106L;
|
||||||
|
|
||||||
|
/** {@see getLabel()} */
|
||||||
|
private final String label;
|
||||||
|
/** {@see getX()} */
|
||||||
|
private final int x;
|
||||||
|
/** {@see getY()} */
|
||||||
|
private final int y;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a place from all fields.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param x See {@link getX}.
|
||||||
|
* @param y See {@link getY}.
|
||||||
|
*/
|
||||||
|
public PlaceDTO(String label, int x, int y) {
|
||||||
|
this.label = label;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public PlaceDTO(Reader reader) throws ParseException {
|
||||||
|
this.label = reader.readStringOrEmpty("label");
|
||||||
|
this.x = reader.readInt("x");
|
||||||
|
this.y = reader.readInt("y");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the place.
|
||||||
|
* @return The name of the place, empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
if (this.label == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the abstract X coordinate of the place within the floor.
|
||||||
|
* @return The X coordinate.
|
||||||
|
*/
|
||||||
|
public int getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the abstract Y coordinate of the place within the floor.
|
||||||
|
* @return The Y coordinate.
|
||||||
|
*/
|
||||||
|
public int getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
src/main/java/org/pasteque/common/datatransfer/dto/ImageDTO.java
Normal file
123
src/main/java/org/pasteque/common/datatransfer/dto/ImageDTO.java
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.constants.ModelName;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityFieldConstraint;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidFieldException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Image Data Transfer Object, to be associated to an other DTO
|
||||||
|
* with the hasImage flag.</p>
|
||||||
|
* <p>Due to the poor performance of encoding binary data as text,
|
||||||
|
* this DTO should be mostly used in binary format.</p>
|
||||||
|
*/
|
||||||
|
public class ImageDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -7807272808383007554L;
|
||||||
|
|
||||||
|
/** {@see getModel()} */
|
||||||
|
private final String model;
|
||||||
|
/** {@see getReference()} */
|
||||||
|
private final String reference;
|
||||||
|
/** {@see getImage()} */
|
||||||
|
private final byte[] imageData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an image from all fields.
|
||||||
|
* @param model See {@link getModel()}.
|
||||||
|
* @param reference See {@link getReference()}.
|
||||||
|
* @param imageData See {@link getImage()}.
|
||||||
|
*/
|
||||||
|
public ImageDTO(
|
||||||
|
String model,
|
||||||
|
String reference,
|
||||||
|
byte[] imageData) {
|
||||||
|
this.model = model;
|
||||||
|
this.reference = reference;
|
||||||
|
this.imageData = imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a raw encoded string.
|
||||||
|
* @param reader The reader that will parse the data. It must already be reading
|
||||||
|
* the object with {@link Reader#startObject()} or {@link Reader#startObject(int)}.
|
||||||
|
* @throws ParseException If the data cannot be parsed or is malformed for
|
||||||
|
* this DTO.
|
||||||
|
*/
|
||||||
|
public ImageDTO(Reader reader) throws ParseException {
|
||||||
|
this.model = reader.readString("model");
|
||||||
|
this.reference = reader.readString("id");
|
||||||
|
this.imageData = reader.readBinary("image");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the model name to with the image is attached.
|
||||||
|
* {@see org.pasteque.common.constants.ModelName}
|
||||||
|
* @return The model name to with the image is attached.
|
||||||
|
*/
|
||||||
|
public String getModel() {
|
||||||
|
return this.model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the identifier of the record this image is attached to.
|
||||||
|
* @return The identifier of the record. Is is always using a String
|
||||||
|
* representation and may not be the actual reference.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return this.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the binary data of the image.
|
||||||
|
* @return The image.
|
||||||
|
*/
|
||||||
|
public byte[] getImage() {
|
||||||
|
return this.imageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
List<IntegrityException> exceptions = new ArrayList<IntegrityException>();
|
||||||
|
boolean checkModelValue = true;
|
||||||
|
if (this.model == null || "".equals(this.model)) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.NOT_NULL,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"model",
|
||||||
|
null, // TODO: id string
|
||||||
|
null));
|
||||||
|
checkModelValue = false;
|
||||||
|
}
|
||||||
|
if (this.reference == null || "".equals(this.reference)) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.NOT_NULL,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"id",
|
||||||
|
null, // TODO: id string
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
if (checkModelValue) {
|
||||||
|
try {
|
||||||
|
ModelName.fromCode(this.model);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.ENUM_REQUIRED,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"model",
|
||||||
|
null, // TODO: id string
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
throw new IntegrityExceptions(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityFieldConstraint;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidFieldException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Misc options Data Transfer Object.
|
||||||
|
*/
|
||||||
|
public class OptionDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 8951727299457218490L;
|
||||||
|
|
||||||
|
/** {@see getName()} */
|
||||||
|
private final String name;
|
||||||
|
/** {@see getContent()} */
|
||||||
|
private final String content;
|
||||||
|
/** {@see isSystem()} */
|
||||||
|
private final boolean system;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an option from all fields.
|
||||||
|
* @param name See {@link getName}.
|
||||||
|
* @param content See {@link getContent}.
|
||||||
|
* @param system See {@link isSystem}.
|
||||||
|
*/
|
||||||
|
public OptionDTO(
|
||||||
|
String name,
|
||||||
|
String content,
|
||||||
|
boolean system) {
|
||||||
|
this.name = name;
|
||||||
|
this.content = content;
|
||||||
|
this.system = system;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a raw encoded string.
|
||||||
|
* @param reader The reader that will parse the data. It must already be reading
|
||||||
|
* the object with {@link Reader#startObject()} or {@link Reader#startObject(int)}.
|
||||||
|
* @throws ParseException If the data cannot be parsed or is malformed for
|
||||||
|
* this DTO.
|
||||||
|
*/
|
||||||
|
public OptionDTO(Reader reader) throws ParseException {
|
||||||
|
this.name = reader.readString("name");
|
||||||
|
this.content = reader.readStringOrEmpty("content");
|
||||||
|
this.system = reader.readBoolean("system");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the option. The name is used as an identifier.
|
||||||
|
* @return The name of the option.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content (aka value) of the option. Its format is not enforced, but is usually
|
||||||
|
* JSON-compatible.
|
||||||
|
* @return The content of the option. Empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getContent() {
|
||||||
|
if (this.content == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this option is a system one or not. System options
|
||||||
|
* cannot be updated by the user, like a version number.
|
||||||
|
* @return Whether the option is a system one or not.
|
||||||
|
*/
|
||||||
|
public boolean isSystem() {
|
||||||
|
return this.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
List<IntegrityException> exceptions = new ArrayList<IntegrityException>();
|
||||||
|
if (this.name == null || "".equals(this.name)) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.NOT_NULL,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"name",
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
throw new IntegrityExceptions(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
367
src/main/java/org/pasteque/common/datatransfer/dto/OrderDTO.java
Normal file
367
src/main/java/org/pasteque/common/datatransfer/dto/OrderDTO.java
Normal file
|
|
@ -0,0 +1,367 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mutable and not paid order. Once an order is paid, it is transformed
|
||||||
|
* into a {@link org.pasteque.coreutil.datatransfer.dto.TicketDTO}.
|
||||||
|
* TODO: experimental
|
||||||
|
*/
|
||||||
|
public class OrderDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -3898628758749878634L;
|
||||||
|
|
||||||
|
/** See {@link getId()}. */
|
||||||
|
private final String id;
|
||||||
|
/** See {@link getName()}. */
|
||||||
|
private final String name;
|
||||||
|
/** See {@link getCustomName()}. */
|
||||||
|
private final String customName;
|
||||||
|
/** See {@link getCustomer()}. */
|
||||||
|
private final Integer customer;
|
||||||
|
/** See {@link getCustCount()}. */
|
||||||
|
private final Integer custCount;
|
||||||
|
/** See {@link getLines()}. */
|
||||||
|
private final ImmutableList<OrderLineDTO> lines;
|
||||||
|
/** See {@link getDiscountProfile()}. */
|
||||||
|
private final Integer discountProfile;
|
||||||
|
/** See {@link getDiscountRate()}. */
|
||||||
|
private final double discountRate;
|
||||||
|
/** See {@link getFinalPrice()}. */
|
||||||
|
private final double finalPrice;
|
||||||
|
/** See {@link getFinalTaxedPrice()}. */
|
||||||
|
private final double finalTaxedPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an order from all fields.
|
||||||
|
* @param id See {@link getId()}.
|
||||||
|
* @param name See {@link getName()}.
|
||||||
|
* @param customName See {@link getCustomName()}.
|
||||||
|
* @param customer See {@link getCustomer()}.
|
||||||
|
* @param custCount See {@link getCustCount()}.
|
||||||
|
* @param lines See {@link getLines()}.
|
||||||
|
* @param discountProfile See {@link getDiscountProfile()}.
|
||||||
|
* @param discountRate See {@link getDiscountRate()}.
|
||||||
|
* @param finalPrice See {@link getFinalPrice()}.
|
||||||
|
* @param finalTaxedPrice See {@link getFinalTaxedPrice()}.
|
||||||
|
*/
|
||||||
|
public OrderDTO(
|
||||||
|
String id,
|
||||||
|
String name,
|
||||||
|
String customName,
|
||||||
|
Integer customer,
|
||||||
|
Integer custCount,
|
||||||
|
OrderLineDTO[] lines,
|
||||||
|
Integer discountProfile,
|
||||||
|
double discountRate,
|
||||||
|
double finalPrice,
|
||||||
|
double finalTaxedPrice) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.customName = customName;
|
||||||
|
this.customer = customer;
|
||||||
|
this.custCount = custCount;
|
||||||
|
this.lines = new ImmutableList<OrderLineDTO>(lines);
|
||||||
|
this.discountProfile = discountProfile;
|
||||||
|
this.discountRate = discountRate;
|
||||||
|
this.finalPrice = finalPrice;
|
||||||
|
this.finalTaxedPrice = finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public OrderDTO(Reader reader) throws ParseException {
|
||||||
|
this.id= reader.readString("id");
|
||||||
|
this.name = reader.readString("name");
|
||||||
|
this.customName = reader.readStringOrNull("customName");
|
||||||
|
this.customer = reader.readIntOrNull("customer");
|
||||||
|
this.custCount = reader.readIntOrNull("custCount");
|
||||||
|
reader.startArray("lines");
|
||||||
|
OrderLineDTO[] lines = new OrderLineDTO[reader.getArraySize()];
|
||||||
|
for (int i = 0; i < reader.getArraySize(); i++) {
|
||||||
|
reader.startObject(i);
|
||||||
|
lines[i] = new OrderLineDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
this.lines = new ImmutableList<OrderLineDTO>(lines);
|
||||||
|
reader.endArray();
|
||||||
|
this.discountProfile = reader.readIntOrNull("discountProfile");
|
||||||
|
this.discountRate = reader.readDouble("discountRate");
|
||||||
|
this.finalPrice = reader.readDouble("finalPrice");
|
||||||
|
this.finalTaxedPrice = reader.readDouble("finalTaxedPrice");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the arbitrary id of this order.
|
||||||
|
* @return The unique identifier of this order.
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the generic name that was set to this order.
|
||||||
|
* @return The generic name of the order.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the custom name of this order.
|
||||||
|
* @return The custom name of this order. Null when not set.
|
||||||
|
*/
|
||||||
|
public String getCustomName() {
|
||||||
|
return this.customName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the customer's account that was linked to this order.
|
||||||
|
* @return The id of the customer's account. Null when not set.
|
||||||
|
*/
|
||||||
|
public Integer getCustomer() {
|
||||||
|
return this.customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of customers set for this order. It is not bound
|
||||||
|
* to the customer's account.
|
||||||
|
* @return The number of customers set for this order. Null when no
|
||||||
|
* number was defined.
|
||||||
|
*/
|
||||||
|
public Integer getCustCount() {
|
||||||
|
return this.custCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of this order.
|
||||||
|
* @return The list of lines in this order.
|
||||||
|
*/
|
||||||
|
public ImmutableList<OrderLineDTO> getLines() {
|
||||||
|
return this.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the discount profile that was linked to this order.
|
||||||
|
* @return The id of the discount profile. Null when not set.
|
||||||
|
*/
|
||||||
|
public Integer getDiscountProfile() {
|
||||||
|
return this.discountProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount rate applied to the whole order.
|
||||||
|
* @return The discount rate to apply to the whole order.
|
||||||
|
*/
|
||||||
|
public double getDiscountRate() {
|
||||||
|
return this.discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without taxes after applying the discount.
|
||||||
|
* @return The price without taxes after applying the discount.
|
||||||
|
*/
|
||||||
|
public double getFinalPrice() {
|
||||||
|
return this.finalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes after applying the discount.
|
||||||
|
* @return The price with taxes after applying the discount.
|
||||||
|
*/
|
||||||
|
public double getFinalTaxedPrice() {
|
||||||
|
return this.finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of an order.
|
||||||
|
*/
|
||||||
|
public class OrderLineDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 8802466032089572015L;
|
||||||
|
|
||||||
|
/** See {@link getDispOrder()}. */
|
||||||
|
private final int dispOrder;
|
||||||
|
/** See {@link getCustomLabel()}. */
|
||||||
|
private final String customLabel;
|
||||||
|
/** See {@link getProduct()}. */
|
||||||
|
private final Integer product;
|
||||||
|
/** See {@link getCustomUnitPrice()}. */
|
||||||
|
private final Double customUnitPrice;
|
||||||
|
/** See {@link getCustomTaxedUnitPrice()}. */
|
||||||
|
private final Double customTaxedUnitPrice;
|
||||||
|
/** See {@link getQuantity()}. */
|
||||||
|
private final double quantity;
|
||||||
|
/** See {@link getCustomTax()}. */
|
||||||
|
private final Integer customTax;
|
||||||
|
/** See {@link getDiscountRate()}. */
|
||||||
|
private final double discountRate;
|
||||||
|
/** See {@link getFinalPrice()}. */
|
||||||
|
private final double finalPrice;
|
||||||
|
/** See {@link getFinalTaxedPrice()}. */
|
||||||
|
private final double finalTaxedPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a line from all fields.
|
||||||
|
* @param dispOrder See {@link getDispOrder()}.
|
||||||
|
* @param customLabel See {@link getCustomLabel()}.
|
||||||
|
* @param product See {@link getProduct()}.
|
||||||
|
* @param customUnitPrice See {@link getCustomUnitPrice()}.
|
||||||
|
* @param customTaxedUnitPrice See {@link getCustomTaxedUnitPrice()}.
|
||||||
|
* @param quantity See {@link getQuantity()}.
|
||||||
|
* @param customTax See {@link getCustomTax()}.
|
||||||
|
* @param discountRate See {@link getDiscountRate()}.
|
||||||
|
* @param finalPrice See {@link getFinalPrice()}.
|
||||||
|
* @param finalTaxedPrice See {@link getFinalTaxedPrice()}.
|
||||||
|
*/
|
||||||
|
public OrderLineDTO(
|
||||||
|
int dispOrder,
|
||||||
|
String customLabel,
|
||||||
|
Integer product,
|
||||||
|
Double customUnitPrice,
|
||||||
|
Double customTaxedUnitPrice,
|
||||||
|
double quantity,
|
||||||
|
Integer customTax,
|
||||||
|
double discountRate,
|
||||||
|
double finalPrice,
|
||||||
|
double finalTaxedPrice) {
|
||||||
|
this.dispOrder = dispOrder;
|
||||||
|
this.customLabel = customLabel;
|
||||||
|
this.product = product;
|
||||||
|
this.customUnitPrice = customUnitPrice;
|
||||||
|
this.customTaxedUnitPrice = customTaxedUnitPrice;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.customTax = customTax;
|
||||||
|
this.discountRate = discountRate;
|
||||||
|
this.finalPrice = finalPrice;
|
||||||
|
this.finalTaxedPrice = finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public OrderLineDTO(Reader reader) throws ParseException {
|
||||||
|
this.dispOrder = reader.readInt("dispOrder");
|
||||||
|
this.customLabel = reader.readStringOrNull("customLabel");
|
||||||
|
this.product = reader.readIntOrNull("product");
|
||||||
|
this.customUnitPrice = reader.readDoubleOrNull("customUnitPrice");
|
||||||
|
this.customTaxedUnitPrice = reader.readDoubleOrNull("customTaxedUnitPrice");
|
||||||
|
this.customTax = reader.readIntOrNull("customTax");
|
||||||
|
this.quantity = reader.readDouble("quantity");
|
||||||
|
this.discountRate = reader.readDouble("discountRate");
|
||||||
|
this.finalPrice = reader.readDouble("finalPrice");
|
||||||
|
this.finalTaxedPrice = reader.readDouble("finalTaxedPrice");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of this line, starting from 0.
|
||||||
|
* @return The index of this line in the order.
|
||||||
|
*/
|
||||||
|
public int getDispOrder() {
|
||||||
|
return this.dispOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label for a custom product.
|
||||||
|
* @return The label of the custom product, null when using
|
||||||
|
* a registered product.
|
||||||
|
* @see getProduct()
|
||||||
|
*/
|
||||||
|
public String getCustomLabel() {
|
||||||
|
return this.customLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the product.
|
||||||
|
* @return The id of the product. Null when using a custom product.
|
||||||
|
*/
|
||||||
|
public Integer getProduct() {
|
||||||
|
return this.product;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the custom price of one unit of the product without taxes.
|
||||||
|
* @return The custom price of one unit of the product without taxes.
|
||||||
|
* Null when using the price from a registered product.
|
||||||
|
* @see getProduct()
|
||||||
|
*/
|
||||||
|
public Double getCustomUnitPrice() {
|
||||||
|
return this.customUnitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the custom price of one unit of the product with taxes.
|
||||||
|
* @return The custom price of one unit of the product with taxes.
|
||||||
|
* Null when using the price from a registered product.
|
||||||
|
*/
|
||||||
|
public Double getCustomTaxedUnitPrice() {
|
||||||
|
return this.customTaxedUnitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the quantity of product in this line.
|
||||||
|
* @return The quantity of product in this line.
|
||||||
|
*/
|
||||||
|
public double getQuantity() {
|
||||||
|
return this.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the custom tax to use.
|
||||||
|
* @return The id of the alternative tax to use. Null when using the
|
||||||
|
* tax from a registered product.
|
||||||
|
* @see getProduct()
|
||||||
|
*/
|
||||||
|
public Integer getCustomTax() {
|
||||||
|
return this.customTax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount rate applied to this line.
|
||||||
|
* @return The discount rate applied only to this line.
|
||||||
|
* It is independent from the discount of the ticket.
|
||||||
|
*/
|
||||||
|
public double getDiscountRate() {
|
||||||
|
return discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without taxes for this line after applying
|
||||||
|
* the discount.
|
||||||
|
* @return The price without taxes for this line after applying
|
||||||
|
* the discount of the line.
|
||||||
|
*/
|
||||||
|
public double getFinalPrice() {
|
||||||
|
return finalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes for this line after applying
|
||||||
|
* the discount.
|
||||||
|
* @return The price with taxes for this line after applying
|
||||||
|
* the discount of the line.
|
||||||
|
*/
|
||||||
|
public double getFinalTaxedPrice() {
|
||||||
|
return finalTaxedPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Payment mode and values Data Transfer Object.</p>
|
||||||
|
* <p>Payment mode values are pre-defined common values for quick-access,
|
||||||
|
* like coins, bills or commonly used values.</p>
|
||||||
|
* <p>Payment mode returns are a set of rules to handle excessive amounts.
|
||||||
|
* The one to use is the one which is above and closest to minAmount.
|
||||||
|
* It indicates which payment mode to use to give back the excess.
|
||||||
|
* When no return is set or the excess is under the minimum amount of all
|
||||||
|
* returns, the excess is kept and registered as exceptional benefit.</p>
|
||||||
|
*/
|
||||||
|
public class PaymentModeDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 5324960228614648256L;
|
||||||
|
|
||||||
|
/** {@see getBackLabel()} */
|
||||||
|
private final String backLabel;
|
||||||
|
/** {@see getType()} */
|
||||||
|
private final int type;
|
||||||
|
/** {@see getThousandsSeparator()} */
|
||||||
|
private final ImmutableList<PaymentModeValueDTO> values;
|
||||||
|
/** {@see getFormat} */
|
||||||
|
private final ImmutableList<PaymentModeReturnDTO> returns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a payment mode from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param dispOrder The display order.
|
||||||
|
* @param active Whether this object is active or not.
|
||||||
|
* @param hasImage Whether an image is linked to this object.
|
||||||
|
* @param backLabel See {@link getBackLabel}.
|
||||||
|
* @param type See {@link getType}.
|
||||||
|
* @param values See {@link getValues}.
|
||||||
|
* @param returns See {@link getReturns}.
|
||||||
|
*/
|
||||||
|
public PaymentModeDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
int dispOrder,
|
||||||
|
boolean active,
|
||||||
|
boolean hasImage,
|
||||||
|
String backLabel,
|
||||||
|
int type,
|
||||||
|
PaymentModeValueDTO[] values,
|
||||||
|
PaymentModeReturnDTO[] returns) {
|
||||||
|
super(id, reference, label, dispOrder, active, hasImage);
|
||||||
|
this.backLabel = backLabel;
|
||||||
|
this.type = type;
|
||||||
|
this.values = new ImmutableList<PaymentModeValueDTO>(values);
|
||||||
|
this.returns = new ImmutableList<PaymentModeReturnDTO>(returns);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public PaymentModeDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, "reference", "label", "dispOrder", "visible", "hasImage");
|
||||||
|
this.backLabel = reader.readStringOrEmpty("backLabel");
|
||||||
|
this.type = reader.readInt("type");
|
||||||
|
reader.startArray("values");
|
||||||
|
PaymentModeValueDTO[] values = new PaymentModeValueDTO[reader.getArraySize()];
|
||||||
|
for (int i = 0; i < reader.getArraySize(); i++) {
|
||||||
|
reader.startObject(i);
|
||||||
|
values[i] = new PaymentModeValueDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
this.values = new ImmutableList<PaymentModeValueDTO>(values);
|
||||||
|
reader.endArray();
|
||||||
|
reader.startArray("returns");
|
||||||
|
PaymentModeReturnDTO[] returns = new PaymentModeReturnDTO[reader.getArraySize()];
|
||||||
|
for (int i = 0; i < reader.getArraySize(); i++) {
|
||||||
|
reader.startObject(i);
|
||||||
|
returns[i] = new PaymentModeReturnDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
this.returns = new ImmutableList<PaymentModeReturnDTO>(returns);
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label to use when returning excess amounts.
|
||||||
|
* @return The label. Empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getBackLabel() {
|
||||||
|
if (this.backLabel == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.backLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of this payment mode. The type is a collection of
|
||||||
|
* flags for some special properties.
|
||||||
|
* @return The string to use as decimal separator. Empty string when not set.
|
||||||
|
* @see org.pasteque.coreutil.constants.PaymentModeType
|
||||||
|
*/
|
||||||
|
public int getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get predefined values for quick-use.
|
||||||
|
* @return The list of predefined common values.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PaymentModeValueDTO> getValues() {
|
||||||
|
if (this.values == null) {
|
||||||
|
return new ImmutableList<PaymentModeValueDTO>();
|
||||||
|
}
|
||||||
|
return this.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rules to return excessive amounts.
|
||||||
|
* @return The list of rules to give back excessive amounts.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PaymentModeReturnDTO> getReturns() {
|
||||||
|
if (this.returns == null) {
|
||||||
|
return new ImmutableList<PaymentModeReturnDTO>();
|
||||||
|
}
|
||||||
|
return this.returns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment mode value Data Transfer Object. Values pre-defined
|
||||||
|
* common values for quick-access, like coins, bills
|
||||||
|
* or commonly used values.
|
||||||
|
*/
|
||||||
|
public class PaymentModeValueDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 5503522521532600750L;
|
||||||
|
|
||||||
|
/** {@see getValue()} */
|
||||||
|
private final double value;
|
||||||
|
/** {@see hasImage()} */
|
||||||
|
private final boolean hasImage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a value from all fields
|
||||||
|
* @param value See {@link getValue()}.
|
||||||
|
* @param hasImage See {@link hasImage()}.
|
||||||
|
*/
|
||||||
|
public PaymentModeValueDTO(double value, boolean hasImage) {
|
||||||
|
super();
|
||||||
|
this.value = value;
|
||||||
|
this.hasImage = hasImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public PaymentModeValueDTO(Reader reader) throws ParseException {
|
||||||
|
this.value = reader.readDouble("value");
|
||||||
|
this.hasImage = reader.readBoolean("hasImage");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of money associated to this value.
|
||||||
|
* @return The value in main currency.
|
||||||
|
*/
|
||||||
|
public double getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an image is linked to the object.
|
||||||
|
* Images are stored separately.
|
||||||
|
* @return Whether an image is linked to the object.
|
||||||
|
*/
|
||||||
|
public boolean hasImage() {
|
||||||
|
return this.hasImage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Payment mode returns are a set of rules to handle excessive amounts.
|
||||||
|
* The one to use is the one which is above and closest to minAmount.
|
||||||
|
* It indicates which payment mode to use to give back the excess.
|
||||||
|
* When no return is set or the excess is under the minimum amount of all
|
||||||
|
* returns, the excess is kept and registered as exceptional benefit.</p>
|
||||||
|
*/
|
||||||
|
public class PaymentModeReturnDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -5076562956398887860L;
|
||||||
|
|
||||||
|
/** {@see getMinAmount()} */
|
||||||
|
private double minAmount;
|
||||||
|
/** {@see getReturnMode()} */
|
||||||
|
private int returnMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a return from all fields
|
||||||
|
* @param minAmount See {@link getMinAmount()}.
|
||||||
|
* @param returnMode See {@link getReturnMode()}.
|
||||||
|
*/
|
||||||
|
public PaymentModeReturnDTO(double minAmount, int returnMode) {
|
||||||
|
super();
|
||||||
|
this.minAmount = minAmount;
|
||||||
|
this.returnMode = returnMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public PaymentModeReturnDTO(Reader reader) throws ParseException {
|
||||||
|
this.minAmount = reader.readDouble("minAmount");
|
||||||
|
this.returnMode = reader.readInt("returnMode");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the minimum excess amount required for this return mode.
|
||||||
|
* The maximum amount is the minimum amount of an other return.
|
||||||
|
* @return The minimum excess amount for this return to be useable.
|
||||||
|
*/
|
||||||
|
public double getMinAmount() {
|
||||||
|
return minAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the payment mode to use for this return.
|
||||||
|
* @return The id of the payment mode to use.
|
||||||
|
*/
|
||||||
|
public int getReturnMode() {
|
||||||
|
return returnMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Product Data Transfer Object.
|
||||||
|
*/
|
||||||
|
public class ProductDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -6083885399091129846L;
|
||||||
|
|
||||||
|
/** {@see getBarcode()} */
|
||||||
|
protected final String barcode;
|
||||||
|
/** {@see getPriceBuy()} */
|
||||||
|
protected final Double priceBuy;
|
||||||
|
/** {@see getPriceSell()} */
|
||||||
|
protected final double priceSell;
|
||||||
|
/** {@see isScaled()} */
|
||||||
|
protected final boolean scaled;
|
||||||
|
/** {@see getScaleType()} */
|
||||||
|
protected final int scaleType;
|
||||||
|
/** {@see getScaleValue()} */
|
||||||
|
protected final double scaleValue;
|
||||||
|
/** {@see isDiscountEnabled()} */
|
||||||
|
protected final boolean discountEnabled;
|
||||||
|
/** {@see getDiscountRate()} */
|
||||||
|
protected final double discountRate;
|
||||||
|
/** {@see isPrepay()} */
|
||||||
|
protected final boolean prepay;
|
||||||
|
/** {@see getCategory()} */
|
||||||
|
protected final int category;
|
||||||
|
/** {@see getTax()} */
|
||||||
|
protected final int tax;
|
||||||
|
/** {@see getTaxedPrice()} */
|
||||||
|
protected final double taxedPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a products from all fields.
|
||||||
|
* @param id The product id.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param dispOrder The explicit display order within the category.
|
||||||
|
* @param active If this product should be shown in catalog or not.
|
||||||
|
* @param hasImage Whether an image is associated to this product.
|
||||||
|
* @param barcode See {@link getBarcode}
|
||||||
|
* @param priceBuy See {@link getPriceBuy}
|
||||||
|
* @param priceSell See {@link getPriceSell}
|
||||||
|
* @param scaled See {@link isScaled}
|
||||||
|
* @param scaleType See {@link getScaleType}
|
||||||
|
* @param scaleValue See {@link getScaleValue}
|
||||||
|
* @param discountEnabled See {@link isDiscountEnabled}
|
||||||
|
* @param discountRate See {@link getDiscountRate}
|
||||||
|
* @param prepay See {@link isPrepay}
|
||||||
|
* @param category See {@link getCategory}
|
||||||
|
* @param tax See {@link getTax}
|
||||||
|
* @param taxedPrice See {@link getTaxedPrice}
|
||||||
|
*/
|
||||||
|
public ProductDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
int dispOrder,
|
||||||
|
boolean active,
|
||||||
|
boolean hasImage,
|
||||||
|
String barcode,
|
||||||
|
Double priceBuy,
|
||||||
|
double priceSell,
|
||||||
|
boolean scaled,
|
||||||
|
int scaleType,
|
||||||
|
double scaleValue,
|
||||||
|
boolean discountEnabled,
|
||||||
|
double discountRate,
|
||||||
|
boolean prepay,
|
||||||
|
int category,
|
||||||
|
int tax,
|
||||||
|
double taxedPrice) {
|
||||||
|
super(id, reference, label, dispOrder, active, hasImage);
|
||||||
|
this.barcode = barcode;
|
||||||
|
this.priceBuy = priceBuy;
|
||||||
|
this.priceSell = priceSell;
|
||||||
|
this.scaled = scaled;
|
||||||
|
this.scaleType = scaleType;
|
||||||
|
this.scaleValue = scaleValue;
|
||||||
|
this.discountEnabled = discountEnabled;
|
||||||
|
this.discountRate = discountRate;
|
||||||
|
this.prepay = prepay;
|
||||||
|
this.category = category;
|
||||||
|
this.tax = tax;
|
||||||
|
this.taxedPrice = taxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public ProductDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, "reference", "label", "dispOrder", "visible", "hasImage");
|
||||||
|
this.barcode = reader.readString("barcode");
|
||||||
|
this.priceBuy = reader.readDoubleOrNull("priceBuy");
|
||||||
|
this.priceSell = reader.readDouble("priceSell");
|
||||||
|
this.scaled = reader.readBoolean("scaled");
|
||||||
|
this.scaleType = reader.readInt("scaleType");
|
||||||
|
this.scaleValue = reader.readDouble("scaleValue");
|
||||||
|
this.discountEnabled = reader.readBoolean("discountEnabled");
|
||||||
|
this.discountRate = reader.readDouble("discountRate");
|
||||||
|
this.prepay = reader.readBoolean("prepay");
|
||||||
|
this.category = reader.readInt("category");
|
||||||
|
this.tax = reader.readInt("tax");
|
||||||
|
this.taxedPrice = reader.readDouble("taxedPrice");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the barcode. It doesn't have to match a specific format.
|
||||||
|
* @return The barcode. Empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getBarcode() {
|
||||||
|
if (this.barcode == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.barcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unit non-taxed buy price, with decimal precision of 5 digits.
|
||||||
|
* @return The non-taxed buy price. Can be null.
|
||||||
|
*/
|
||||||
|
public Double getPriceBuy() {
|
||||||
|
return this.priceBuy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unit non-taxed sell price, with decimal precision of 5 digits.
|
||||||
|
* @return The non-taxed sell price.
|
||||||
|
*/
|
||||||
|
public double getPriceSell() {
|
||||||
|
return this.priceSell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the product should be sold atomically or not.
|
||||||
|
* @return True when the quantity should be asked
|
||||||
|
* every time the product is ordered.
|
||||||
|
*/
|
||||||
|
public boolean isScaled() {
|
||||||
|
return this.scaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the scale type.
|
||||||
|
* @return The scale type code.
|
||||||
|
* {@see fr.pasteque.common.constants.ScaleType}
|
||||||
|
*/
|
||||||
|
public int getScaleType() {
|
||||||
|
return this.scaleType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual quantity stored in one unit of this product.
|
||||||
|
* It has no meaning when {@link isScaled} is true and is mostly used
|
||||||
|
* to compute a reference price for 1 complete unit of its content.
|
||||||
|
* @return The quantity stored in one unit of this product.
|
||||||
|
* {@see org.pasteque.common.model.Product#getReferencePrice()}
|
||||||
|
*/
|
||||||
|
public double getScaleValue() {
|
||||||
|
return this.scaleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the discount should automatically apply.
|
||||||
|
* @return True when a discount should be applied when ordering this product.
|
||||||
|
* {@see getDiscountRate()}
|
||||||
|
*/
|
||||||
|
public boolean isDiscountEnabled() {
|
||||||
|
return discountEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the automatic discount to apply when enabled.
|
||||||
|
* @return The discount rate to apply when ordering this product.
|
||||||
|
* {@see isDiscountEnabled()}
|
||||||
|
*/
|
||||||
|
public double getDiscountRate() {
|
||||||
|
return discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Whether this product fills the customer's balance.</p>
|
||||||
|
* <p>Prepay products should have no tax and must not be counted
|
||||||
|
* in consolidated sales.</p>
|
||||||
|
* @return True when this product fills the customer's balance.
|
||||||
|
*/
|
||||||
|
public boolean isPrepay() {
|
||||||
|
return prepay;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Whether this product is a composition or not.</p>
|
||||||
|
* @return False.
|
||||||
|
* {@see org.pasteque.common.dto.CompositionDTO}
|
||||||
|
*/
|
||||||
|
public boolean isComposition() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the category this product fits in.
|
||||||
|
* @return The id of the category.
|
||||||
|
*/
|
||||||
|
public int getCategory() {
|
||||||
|
return this.category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the tax to apply to this product.
|
||||||
|
* @return The id of the tax.
|
||||||
|
*/
|
||||||
|
public int getTax() {
|
||||||
|
return this.tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sell price with tax, with 2 decimal precision.
|
||||||
|
* @return The sell price with tax.
|
||||||
|
*/
|
||||||
|
public double getTaxedPrice() {
|
||||||
|
return this.taxedPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.constants.ResourceType;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.dto.DTOInterface;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityFieldConstraint;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidFieldException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Resource Transfer Object, previous version of
|
||||||
|
* {@link org.pasteque.common.datatransfer.dto.OptionDTO} and
|
||||||
|
* {@link org.pasteque.common.datatransfer.dto.ImageDTO}.</p>
|
||||||
|
* <p>Resources were used for customization and could hold text/xml as well as
|
||||||
|
* binary images. A few Resources are still in use but they will be replaced
|
||||||
|
* by either Options or Images.</p>
|
||||||
|
*/
|
||||||
|
public class ResourceDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -7746795788087018857L;
|
||||||
|
|
||||||
|
/** {@see getModel()} */
|
||||||
|
private final String label;
|
||||||
|
/** {@see getReference()} */
|
||||||
|
private final int type;
|
||||||
|
/** {@see getImage()} */
|
||||||
|
private final String content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an image from all fields.
|
||||||
|
* @param label See {@link getLabel()}.
|
||||||
|
* @param type See {@link getType()}.
|
||||||
|
* @param content See {@link getContent()}.
|
||||||
|
*/
|
||||||
|
public ResourceDTO(
|
||||||
|
String label,
|
||||||
|
int type,
|
||||||
|
String content) {
|
||||||
|
this.label = label;
|
||||||
|
this.type = type;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a raw encoded string.
|
||||||
|
* @param reader The reader that will parse the data. It must already be reading
|
||||||
|
* the object with {@link Reader#startObject()} or {@link Reader#startObject(int)}.
|
||||||
|
* @throws ParseException If the data cannot be parsed or is malformed for
|
||||||
|
* this DTO.
|
||||||
|
*/
|
||||||
|
public ResourceDTO(Reader reader) throws ParseException {
|
||||||
|
this.label = reader.readString("label");
|
||||||
|
this.type = reader.readInt("type");
|
||||||
|
this.content = reader.readString("content");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the resource. It also serves as its identifier.
|
||||||
|
* {@see org.pasteque.common.constants.ResourceName}
|
||||||
|
* @return The name of the resource.
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of the resource, either text, image or raw binary.
|
||||||
|
* {@see org.pasteque.common.constants.ResourceType}
|
||||||
|
* @return The type of the content of the resource.
|
||||||
|
*/
|
||||||
|
public int getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of this resource. Binary data are converted to base64.
|
||||||
|
* @return The content of the resource, either plain text or base64-encoded
|
||||||
|
* binary. Empty string if not set.
|
||||||
|
*/
|
||||||
|
public String getContent() {
|
||||||
|
if (this.content == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
List<IntegrityException> exceptions = new ArrayList<IntegrityException>();
|
||||||
|
if (this.label == null || "".equals(this.label)) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.NOT_NULL,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"label",
|
||||||
|
null,
|
||||||
|
null));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ResourceType.fromCode(this.type);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
exceptions.add(new InvalidFieldException(
|
||||||
|
IntegrityFieldConstraint.ENUM_REQUIRED,
|
||||||
|
this.getClass().getName(),
|
||||||
|
"type",
|
||||||
|
this.label,
|
||||||
|
String.valueOf(this.type)));
|
||||||
|
}
|
||||||
|
if (!exceptions.isEmpty()) {
|
||||||
|
throw new IntegrityExceptions(exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>User permission group Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, customers don't have a reference and it will always be an empty string,
|
||||||
|
* nor have a display order.</p>
|
||||||
|
* <p>Each permission is a string. The permission is granted if it is in the list of permissions
|
||||||
|
* of its associated role.</p>
|
||||||
|
*/
|
||||||
|
public class RoleDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1835635017595972352L;
|
||||||
|
|
||||||
|
/** {@see getPermissions()} */
|
||||||
|
private final ImmutableList<String> permissions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a role from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param permissions See {@link getPermissions}.
|
||||||
|
*/
|
||||||
|
public RoleDTO(
|
||||||
|
Integer id,
|
||||||
|
String label,
|
||||||
|
String[] permissions) {
|
||||||
|
super(id, "", label, 0, true, false);
|
||||||
|
this.permissions = new ImmutableList<String>(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public RoleDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, null, "label", null, null, null);
|
||||||
|
reader.startArray("permissions");
|
||||||
|
String[] permissions = new String[reader.getArraySize()];
|
||||||
|
for (int i = 0; i < reader.getArraySize(); i++) {
|
||||||
|
permissions[i] = reader.readString(i);
|
||||||
|
}
|
||||||
|
this.permissions = new ImmutableList<String>(permissions);
|
||||||
|
reader.endArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of permissions granted by this role.
|
||||||
|
* @return All permissions granted, empty array when not set.
|
||||||
|
*/
|
||||||
|
public ImmutableList<String> getPermissions() {
|
||||||
|
if (this.permissions == null) {
|
||||||
|
return new ImmutableList<String>();
|
||||||
|
}
|
||||||
|
return this.permissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Tariff Area Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, tariff areas cannot have an associated image
|
||||||
|
* nor can be deactivated.</p>
|
||||||
|
*/
|
||||||
|
public class TariffAreaDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -2639559615366931293L;
|
||||||
|
|
||||||
|
/** {@see getSymbol()} */
|
||||||
|
private final ImmutableList<TariffAreaPriceDTO> prices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tariff area from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param prices See {@link getPrices}.
|
||||||
|
*/
|
||||||
|
public TariffAreaDTO(
|
||||||
|
Integer id,
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
TariffAreaPriceDTO[] prices) {
|
||||||
|
super(id, reference, label, 0, true, false);
|
||||||
|
this.prices = new ImmutableList<TariffAreaPriceDTO>(prices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TariffAreaDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, "reference", "label", "dispOrder", null, null);
|
||||||
|
reader.startArray("prices");
|
||||||
|
TariffAreaPriceDTO[] prices = new TariffAreaPriceDTO[reader.getArraySize()];
|
||||||
|
for (int i = 0; i < reader.getArraySize(); i++) {
|
||||||
|
reader.startObject(i);
|
||||||
|
prices[i] = new TariffAreaPriceDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
reader.endArray();
|
||||||
|
this.prices = new ImmutableList<TariffAreaPriceDTO>(prices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of alternative prices associated to this area.
|
||||||
|
* @return The list of prices. Empty array when not set.
|
||||||
|
*/
|
||||||
|
public ImmutableList<TariffAreaPriceDTO> getPrices() {
|
||||||
|
if (this.prices == null) {
|
||||||
|
return new ImmutableList<TariffAreaPriceDTO>();
|
||||||
|
}
|
||||||
|
return this.prices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Tariff Area Price Data Transfer Object.</p>
|
||||||
|
* <p>Tariff areas can modify the base price or the tax to apply to
|
||||||
|
* a set of products.</p>
|
||||||
|
*/
|
||||||
|
public class TariffAreaPriceDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 3384262202026051762L;
|
||||||
|
|
||||||
|
/** See {@link getProduct()}. */
|
||||||
|
private int product;
|
||||||
|
/** See {@link getPrice()}. */
|
||||||
|
private Double price;
|
||||||
|
/** See {@link getTax()}. */
|
||||||
|
private Integer tax;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tariff area price from all fields.
|
||||||
|
* @param product The id of the product.
|
||||||
|
* @param price The alternative non-taxed price, null to keep
|
||||||
|
* the original price.
|
||||||
|
* @param tax The id of the alternative tax, null to keep
|
||||||
|
* the original one.
|
||||||
|
*/
|
||||||
|
public TariffAreaPriceDTO(int product, Double price, Integer tax) {
|
||||||
|
this.product = product;
|
||||||
|
this.price = price;
|
||||||
|
this.tax = tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TariffAreaPriceDTO(Reader reader) throws ParseException {
|
||||||
|
this.product = reader.readInt("product");
|
||||||
|
this.price = reader.readDoubleOrNull("price");
|
||||||
|
this.tax = reader.readIntOrNull("tax");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the product this price applies to.
|
||||||
|
* @return The id of the product.
|
||||||
|
*/
|
||||||
|
public int getProduct() {
|
||||||
|
return this.product;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the new non-taxed price for this product.
|
||||||
|
* @return The alternative non-taxed price for this product in this area.
|
||||||
|
* Null when the price is not changed.
|
||||||
|
*/
|
||||||
|
public Double getPrice() {
|
||||||
|
return this.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the new tax for this product.
|
||||||
|
* @return The alternative tax id for this product in this area.
|
||||||
|
* Null when the tax is not changed.
|
||||||
|
*/
|
||||||
|
public Integer getTax() {
|
||||||
|
return this.tax;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Tax Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, taxes cannot have a reference,
|
||||||
|
* nor display order nor image and cannot be deactivated.</p>
|
||||||
|
*/
|
||||||
|
public class TaxDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 8232927221600250003L;
|
||||||
|
|
||||||
|
/** See {@link getTaxRate()}. */
|
||||||
|
private final double taxRate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tax from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param label The display name, it is used as reference.
|
||||||
|
* @param taxRate See {@link getTaxRate()}.
|
||||||
|
*/
|
||||||
|
public TaxDTO(
|
||||||
|
Integer id,
|
||||||
|
String label,
|
||||||
|
double taxRate) {
|
||||||
|
super(id, "", label, 0, true, false);
|
||||||
|
this.taxRate = taxRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TaxDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, null, "label", null, null, null);
|
||||||
|
this.taxRate = Double.valueOf(reader.readDouble("rate"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label. Tax has no reference and will use
|
||||||
|
* its label as one.
|
||||||
|
* @return The label.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getReference() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tax rate to apply. The rate should be between 0.0 and 1.0
|
||||||
|
* but it is not checked.
|
||||||
|
* @return The tax rate to apply.
|
||||||
|
*/
|
||||||
|
public double getTaxRate() {
|
||||||
|
return this.taxRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<IntegrityException> commonIntegrityCheck() {
|
||||||
|
// Tax has no reference to check.
|
||||||
|
return new ArrayList<IntegrityException>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Cash register user (operator) Data Transfer Object.</p>
|
||||||
|
* <p>As of Pasteque API version 8, users don't have a reference and it will always be an empty string,
|
||||||
|
* nor have a display order.</p>
|
||||||
|
*/
|
||||||
|
public class UserDTO extends CommonDTO implements Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 5162729826784894378L;
|
||||||
|
|
||||||
|
/** {@see getRole()} */
|
||||||
|
private final int role;
|
||||||
|
/** {@see getCard()} */
|
||||||
|
private final String card;
|
||||||
|
/** {@see getPassword()} */
|
||||||
|
private final String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a user from all fields.
|
||||||
|
* @param id The id.
|
||||||
|
* @param label The user's display name.
|
||||||
|
* @param card See {@link getCard()}.
|
||||||
|
* @param password See {@link getPassword()}.
|
||||||
|
* @param active See {@link isActive()}.
|
||||||
|
* @param hasImage See {@link hasImage()}.
|
||||||
|
* @param role See {@link getRole()}.
|
||||||
|
*/
|
||||||
|
public UserDTO(
|
||||||
|
Integer id,
|
||||||
|
String label,
|
||||||
|
String card,
|
||||||
|
String password,
|
||||||
|
boolean active,
|
||||||
|
boolean hasImage,
|
||||||
|
int role) {
|
||||||
|
super(id, "", label, 0, active, hasImage);
|
||||||
|
this.card = card;
|
||||||
|
this.password = password;
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public UserDTO(Reader reader) throws ParseException {
|
||||||
|
super(reader, null, "name", null, "active", "hasImage");
|
||||||
|
this.card = reader.readStringOrEmpty("card");
|
||||||
|
this.password = reader.readStringOrNull("password");
|
||||||
|
this.role = reader.readInt("role");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional card number or text. It doesn't have any constraint.
|
||||||
|
* @return The user's card number/string. Empty string when not set.
|
||||||
|
*/
|
||||||
|
public String getCard() {
|
||||||
|
if (this.card == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.card;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's password. See {@link org.pasteque.common.util.HashCypher}
|
||||||
|
* for the different formats.
|
||||||
|
* @return The user's password, null or an empty string when not set,
|
||||||
|
* a plain password or a hashed password with a prefix.
|
||||||
|
*/
|
||||||
|
public String getPassword() {
|
||||||
|
return this.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the role assigned to this user (required).
|
||||||
|
* @return The role id.
|
||||||
|
*/
|
||||||
|
public int getRole() {
|
||||||
|
return this.role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* <p>Data Transfer Objects, read-only data for storage and transmission.</p>
|
||||||
|
*
|
||||||
|
* <p>DTO are non-mutable and without behaviour objects. Use DTO to store data
|
||||||
|
* locally or transmit them.</p>
|
||||||
|
*
|
||||||
|
* <p>DTO only have simple data types, like primitives or String to ease
|
||||||
|
* storage and transmission. The actual models may enforce more restriction
|
||||||
|
* like the decimal precision. DTO are not meant to be created manually nor
|
||||||
|
* even manipulated. Use the actual model for these purposes.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.datatransfer.dto;
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* DTO and utilities to read and write data in text format.
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.datatransfer;
|
||||||
80
src/main/java/org/pasteque/common/model/Category.java
Normal file
80
src/main/java/org/pasteque/common/model/Category.java
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.pasteque.common.model;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.pasteque.common.constants.ModelName;
|
||||||
|
import org.pasteque.common.datasource.ImageDataSource;
|
||||||
|
import org.pasteque.common.view.Buttonable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A single category inside a category tree.</p>
|
||||||
|
* <p>The parent is not defined, use a
|
||||||
|
* {@link org.pasteque.common.model.CategoryTree} to navigate between parents
|
||||||
|
* and children.</p>
|
||||||
|
*/
|
||||||
|
public class Category implements Buttonable
|
||||||
|
{
|
||||||
|
private String reference;
|
||||||
|
private String name;
|
||||||
|
private boolean hasImage;
|
||||||
|
private Image imageCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a category from all fields.
|
||||||
|
* @param reference The reference.
|
||||||
|
* @param name The display name.
|
||||||
|
* @param hasImage If a custom image is associated.
|
||||||
|
*/
|
||||||
|
public Category(String reference, String name, boolean hasImage) {
|
||||||
|
this.reference = reference;
|
||||||
|
this.name = name;
|
||||||
|
this.hasImage = hasImage;
|
||||||
|
this.imageCache = new Image(
|
||||||
|
ModelName.CATEGORY,
|
||||||
|
this.reference,
|
||||||
|
this.hasImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference.
|
||||||
|
* @return The reference.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display name.
|
||||||
|
* @return The display name.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from Buttonable
|
||||||
|
public String getLabel() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this category has a custom image.
|
||||||
|
* @return True when a custom image is set.
|
||||||
|
* {@see getImage(ImageDataSource)}
|
||||||
|
*/
|
||||||
|
public boolean hasImage() {
|
||||||
|
return this.hasImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get the image to use to show this category.</p>
|
||||||
|
* @param source The source to read the image from.
|
||||||
|
* @return The image to use. Either the custom one when set or the
|
||||||
|
* default one otherwise.
|
||||||
|
* {@see org.pasteque.common.constants.DefaultImages}
|
||||||
|
* {@see org.pasteque.common.model.Image}
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public byte[] getImage(ImageDataSource source) throws IOException {
|
||||||
|
return this.imageCache.getImage(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
105
src/main/java/org/pasteque/common/model/CategoryTree.java
Normal file
105
src/main/java/org/pasteque/common/model/CategoryTree.java
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
package org.pasteque.common.model;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.common.datasource.CategoryDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Incomplete path to browse the category tree.
|
||||||
|
*/
|
||||||
|
public class CategoryTree
|
||||||
|
{
|
||||||
|
/** Current path, empty at root. */
|
||||||
|
private List<Category> path;
|
||||||
|
/** Cached children categories at the end of the current path. */
|
||||||
|
private List<Category> children;
|
||||||
|
/** The source to read children from. */
|
||||||
|
private CategoryDataSource source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a CategoryTree from the root.
|
||||||
|
* @param source The source to read top categories from.
|
||||||
|
* @throws IOException When an error occurs while reading
|
||||||
|
* the top categories from the source.
|
||||||
|
*/
|
||||||
|
public CategoryTree(CategoryDataSource source) throws IOException {
|
||||||
|
this.path = new LinkedList<Category>();
|
||||||
|
this.source = source;
|
||||||
|
this.children = this.source.getTopCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Go deeper in the tree and update the current state.</p>
|
||||||
|
* <p>Children categories will be loaded from the source.</p>
|
||||||
|
* @param child The child category to browse. It is not checked if this
|
||||||
|
* category is a child of the current tree.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public void browseChild(Category child) throws IOException {
|
||||||
|
this.children = this.source.getSubCategories(child);
|
||||||
|
this.path.add(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Move up in the tree and update the current state.</p>
|
||||||
|
* <p>Children categories will be loaded from the source.
|
||||||
|
* Does nothing if already at the root.</p>
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public void browseParent() throws IOException {
|
||||||
|
if (this.path.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (path.size() == 1) {
|
||||||
|
this.children = this.source.getTopCategories();
|
||||||
|
} else {
|
||||||
|
this.children = this.source.getSubCategories(path.get(path.size() - 1));
|
||||||
|
}
|
||||||
|
this.path.remove(this.path.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current category.
|
||||||
|
* @return The category for which parent and children refers to.
|
||||||
|
* Null if no category is currently selected (at root).
|
||||||
|
* {@see isAtRoot()}
|
||||||
|
*/
|
||||||
|
public Category getCurrentCategory() {
|
||||||
|
if (path.size() == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.path.get(this.path.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get children categories.
|
||||||
|
* @return The list of categories that are children of the current one.
|
||||||
|
*/
|
||||||
|
public List<Category> getSubcategories() {
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Check if the current category has a parent in the tree.</p>
|
||||||
|
* <p>Use this method when there is no dedicated state for when
|
||||||
|
* no category is selected.</p>
|
||||||
|
* @return True if the current path has more than one element.
|
||||||
|
* {@see isAtRoot()}
|
||||||
|
*/
|
||||||
|
public boolean hasParent() {
|
||||||
|
return this.path.size() > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Check if a category is selected in the tree.</p>
|
||||||
|
* <p>Use this method when there is a dedicated state to show only
|
||||||
|
* the categories at the top of the tree.</p>
|
||||||
|
* @return True if no category is selected. Children will all
|
||||||
|
* have no parent in that case.
|
||||||
|
* {@see hasParent}
|
||||||
|
*/
|
||||||
|
public boolean isAtRoot() {
|
||||||
|
return this.path.size() == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
146
src/main/java/org/pasteque/common/model/Currency.java
Normal file
146
src/main/java/org/pasteque/common/model/Currency.java
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
package org.pasteque.common.model;
|
||||||
|
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.DecimalFormatSymbols;
|
||||||
|
import org.pasteque.common.datatransfer.dto.CurrencyDTO;
|
||||||
|
import org.pasteque.coreutil.price.Price2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currency format and conversion.
|
||||||
|
*/
|
||||||
|
public class Currency
|
||||||
|
{
|
||||||
|
/** {@see getName()} */
|
||||||
|
private String name;
|
||||||
|
/** {@see getSymbol()} */
|
||||||
|
private String symbol;
|
||||||
|
/** {@see getDecimalSeparator()} */
|
||||||
|
private String decimalSeparator;
|
||||||
|
/** {@see getThousandsSeparator()} */
|
||||||
|
private String thousandsSeparator;
|
||||||
|
/** {@see getFormat()} */
|
||||||
|
private String format;
|
||||||
|
/** {@see getRate()} */
|
||||||
|
private double rate;
|
||||||
|
/** {@see isMain()} */
|
||||||
|
private boolean main;
|
||||||
|
/** {@see isActive()} */
|
||||||
|
private boolean active;
|
||||||
|
/**
|
||||||
|
* Cached formatter to print values in the format of this currency.
|
||||||
|
* Set within the constructor.
|
||||||
|
*/
|
||||||
|
private DecimalFormat formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a currency from transmitted data.
|
||||||
|
* @param currencyDTO The currency data.
|
||||||
|
*/
|
||||||
|
public Currency(CurrencyDTO currencyDTO) {
|
||||||
|
this.name = currencyDTO.getLabel();
|
||||||
|
this.symbol = currencyDTO.getSymbol();
|
||||||
|
this.decimalSeparator = currencyDTO.getDecimalSeparator();
|
||||||
|
this.thousandsSeparator = currencyDTO.getThousandsSeparator();
|
||||||
|
this.format = currencyDTO.getFormat();
|
||||||
|
this.rate = currencyDTO.getRate();
|
||||||
|
this.main = currencyDTO.isMain();
|
||||||
|
this.active = currencyDTO.isActive();
|
||||||
|
// Create the formatter
|
||||||
|
String format = this.format.replace("$", "¤");
|
||||||
|
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
|
||||||
|
if (this.decimalSeparator != null && !"".equals(this.decimalSeparator)) {
|
||||||
|
symbols.setDecimalSeparator(this.decimalSeparator.charAt(0));
|
||||||
|
symbols.setMonetaryDecimalSeparator(this.decimalSeparator.charAt(0));
|
||||||
|
} else {
|
||||||
|
symbols.setDecimalSeparator('.');
|
||||||
|
symbols.setMonetaryDecimalSeparator('.');
|
||||||
|
}
|
||||||
|
if (this.thousandsSeparator != null && !"".equals(this.thousandsSeparator)) {
|
||||||
|
symbols.setGroupingSeparator(this.thousandsSeparator.charAt(0));
|
||||||
|
} else {
|
||||||
|
symbols.setGroupingSeparator(' ');
|
||||||
|
}
|
||||||
|
if (this.symbol != null) {
|
||||||
|
symbols.setCurrencySymbol(this.symbol);
|
||||||
|
}
|
||||||
|
this.formatter = new DecimalFormat(format, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a value from this currency to the main currency.
|
||||||
|
* @param amount The value in this currency.
|
||||||
|
* @return The value in the main currency.
|
||||||
|
*/
|
||||||
|
public Price2 convertToMain(double amount) {
|
||||||
|
if (this.main) {
|
||||||
|
return new Price2(amount);
|
||||||
|
} else {
|
||||||
|
return new Price2(amount * this.rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a value from the main currency to this currency.
|
||||||
|
* @param amount The value in the main currency.
|
||||||
|
* @return The value in this currency.
|
||||||
|
*/
|
||||||
|
public Price2 convertFromMain(double amount) {
|
||||||
|
if (this.main) {
|
||||||
|
return new Price2(amount);
|
||||||
|
} else {
|
||||||
|
return new Price2(amount / this.rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the string representation of a value.
|
||||||
|
* @param amount The amount to format.
|
||||||
|
* @param digits The number of decimals to display.
|
||||||
|
* @return The formatted string, using format, symbol and separators.
|
||||||
|
*/
|
||||||
|
public String formatValue(double amount, int digits) {
|
||||||
|
this.formatter.setMinimumFractionDigits(digits);
|
||||||
|
this.formatter.setMaximumFractionDigits(digits);
|
||||||
|
return this.formatter.format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic getter.
|
||||||
|
* @return The name.
|
||||||
|
*/
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic getter.
|
||||||
|
* @return The symbol.
|
||||||
|
*/
|
||||||
|
public String getSymbol() {
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic getter.
|
||||||
|
* @return The conversion rate.
|
||||||
|
*/
|
||||||
|
public double getRate() {
|
||||||
|
return rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic getter.
|
||||||
|
* @return If this currency is the main one.
|
||||||
|
*/
|
||||||
|
public boolean isMain() {
|
||||||
|
return main;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic getter.
|
||||||
|
* @return If this currency is active.
|
||||||
|
*/
|
||||||
|
public boolean isActive() {
|
||||||
|
return active;
|
||||||
|
}
|
||||||
|
}
|
||||||
152
src/main/java/org/pasteque/common/model/Customer.java
Normal file
152
src/main/java/org/pasteque/common/model/Customer.java
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
package org.pasteque.common.model;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.price.Price2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer account.
|
||||||
|
*/
|
||||||
|
public class Customer
|
||||||
|
{
|
||||||
|
|
||||||
|
/*public static String getContactFieldName(CustomerContactField field, Option customFields) {
|
||||||
|
|
||||||
|
}*/
|
||||||
|
/**
|
||||||
|
* Nullable internal identifier. This field will be deprecated once
|
||||||
|
* the reference is used to link records.
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
/**
|
||||||
|
* Whether the record has an image that can be retrieved from elsewhere.
|
||||||
|
* When the model cannot have an image, it must return false.
|
||||||
|
*/
|
||||||
|
private boolean hasImage;
|
||||||
|
|
||||||
|
/** {@see getCard()} */
|
||||||
|
private String card;
|
||||||
|
/** {@see getMaxDebt()} */
|
||||||
|
private Price2 maxDebt;
|
||||||
|
/** {@see getBalance()} */
|
||||||
|
private Price2 balance;
|
||||||
|
/** {@see getExpireDate()} */
|
||||||
|
private Date expireDate;
|
||||||
|
/** {@see getDiscountProfile()} */
|
||||||
|
private Integer discountProfile;
|
||||||
|
/** {@see getTariffArea()} */
|
||||||
|
private Integer tariffArea;
|
||||||
|
/** {@see getTax()} */
|
||||||
|
private Integer tax;
|
||||||
|
|
||||||
|
// Contact fields
|
||||||
|
// These fields may be moved away to ease compatibility with CRM softwares.
|
||||||
|
/** {@see getFirstName()} */
|
||||||
|
private String firstName;
|
||||||
|
/** {@see getLastName()} */
|
||||||
|
private String lastName;
|
||||||
|
/** {@see getEmail()} */
|
||||||
|
private String email;
|
||||||
|
/** {@see getPhone1()} */
|
||||||
|
private String phone1;
|
||||||
|
/** {@see getPhone2()} */
|
||||||
|
private String phone2;
|
||||||
|
/** {@see getFax()} */
|
||||||
|
private String fax;
|
||||||
|
/** {@see getAddr1()} */
|
||||||
|
private String addr1;
|
||||||
|
/** {@see getAddr2()} */
|
||||||
|
private String addr2;
|
||||||
|
/** {@see getZipCode()} */
|
||||||
|
private String zipCode;
|
||||||
|
/** {@see getCity()} */
|
||||||
|
private String city;
|
||||||
|
/** {@see getRegion()} */
|
||||||
|
private String region;
|
||||||
|
/** {@see getCountry()} */
|
||||||
|
private String country;
|
||||||
|
/** {@see getNote()} */
|
||||||
|
private String note;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty customer account.
|
||||||
|
*/
|
||||||
|
public Customer() {
|
||||||
|
this.card = "";
|
||||||
|
this.maxDebt = new Price2(0.00);
|
||||||
|
this.balance = new Price2(0.00);
|
||||||
|
this.firstName = "";
|
||||||
|
this.lastName = "";
|
||||||
|
this.email = "";
|
||||||
|
this.phone1 = "";
|
||||||
|
this.phone2 = "";
|
||||||
|
this.fax = "";
|
||||||
|
this.addr1 = "";
|
||||||
|
this.addr2 = "";
|
||||||
|
this.zipCode = "";
|
||||||
|
this.city = "";
|
||||||
|
this.region = "";
|
||||||
|
this.country = "";
|
||||||
|
this.note = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an account from from transmitted data.
|
||||||
|
* @param customerDTO The customer account data.
|
||||||
|
* @param dpDTO The associated discount profile data. Set null if none.
|
||||||
|
* @param taDTO The associated tariff area data.
|
||||||
|
* @param taxDTO The associated alternative tax data.
|
||||||
|
* @throws AssociationInconsistencyException
|
||||||
|
* When either the discount profile, tariff area or tax don't match the
|
||||||
|
* data in the customer account.
|
||||||
|
*/
|
||||||
|
/* public Customer(CustomerDTO customerDTO, DiscountProfileDTO dpDTO,
|
||||||
|
TariffAreaDTO taDTO, TaxDTO taxDTO) throws AssociationInconsistencyException {
|
||||||
|
this.id = customerDTO.getId();
|
||||||
|
this.name = customerDTO.getName();
|
||||||
|
this.card = customerDTO.getCard();
|
||||||
|
this.maxDebt = new Price2(customerDTO.getMaxDebt());
|
||||||
|
this.balance = new Price2(customerDTO.getBalance());
|
||||||
|
if (customerDTO.getExpireDate() != null) {
|
||||||
|
this.expireDate = new Date(customerDTO.getExpireDate());
|
||||||
|
}
|
||||||
|
this.firstName = customerDTO.getFirstName();
|
||||||
|
this.lastName = customerDTO.getLastName();
|
||||||
|
this.email = customerDTO.getEmail();
|
||||||
|
this.phone1 = customerDTO.getPhone1();
|
||||||
|
this.phone2 = customerDTO.getPhone2();
|
||||||
|
this.fax = customerDTO.getFax();
|
||||||
|
this.addr1 = customerDTO.getAddr1();
|
||||||
|
this.addr2 = customerDTO.getAddr2();
|
||||||
|
this.zipCode = customerDTO.getZipCode();
|
||||||
|
this.city = customerDTO.getCity();
|
||||||
|
this.region = customerDTO.getRegion();
|
||||||
|
this.country = customerDTO.getCountry();
|
||||||
|
this.note = customerDTO.getNote();
|
||||||
|
// Link checks
|
||||||
|
Integer discountProfile = customerDTO.getDiscountProfile();
|
||||||
|
AssociationInconsistencyException.autoThrow(
|
||||||
|
"discountProfile", customerDTO.getDiscountProfile(),
|
||||||
|
"id", (dpDTO == null) ? null : dpDTO.getId()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.discountProfile = (dpDTO == null) ? null : new DiscountProfile(dpDTO);
|
||||||
|
Integer tariffArea = customerDTO.getTariffArea();
|
||||||
|
AssociationInconsistencyException.autoThrow(
|
||||||
|
"tariffArea", tariffArea,
|
||||||
|
"id", (taDTO == null) ? null : taDTO.getId()
|
||||||
|
);
|
||||||
|
this.tariffArea = (taDTO == null) ? null : new TariffArea(taDTO);
|
||||||
|
Integer tax = customerDTO.getTay();
|
||||||
|
AssociationInconsistencyException.autoThrow(
|
||||||
|
"tax", tariffArea,
|
||||||
|
"id", (taxDTO == null) ? null : taxDTO.getId()
|
||||||
|
);
|
||||||
|
this.tax = (taxDTO == null) ? null : new Tax(taxDTO);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.name;
|
||||||
|
}*/
|
||||||
|
}
|
||||||
58
src/main/java/org/pasteque/common/model/Image.java
Normal file
58
src/main/java/org/pasteque/common/model/Image.java
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.pasteque.common.model;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.pasteque.common.datasource.ImageDataSource;
|
||||||
|
import org.pasteque.common.datatransfer.dto.ImageDTO;
|
||||||
|
import org.pasteque.common.constants.DefaultImages;
|
||||||
|
import org.pasteque.common.constants.ModelName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to manage image caching and loading.
|
||||||
|
*/
|
||||||
|
public class Image
|
||||||
|
{
|
||||||
|
private ModelName model;
|
||||||
|
private String reference;
|
||||||
|
private boolean hasImage;
|
||||||
|
private byte[] imageCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Image from all fields.
|
||||||
|
* @param model The type of model for this image.
|
||||||
|
* @param reference The reference of the record.
|
||||||
|
* @param hasImage If a custom image is associated to the record.
|
||||||
|
*/
|
||||||
|
public Image(
|
||||||
|
ModelName model,
|
||||||
|
String reference,
|
||||||
|
boolean hasImage) {
|
||||||
|
this.model = model;
|
||||||
|
this.reference = reference;
|
||||||
|
this.hasImage = hasImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get the image to use.</p>
|
||||||
|
* <p>When a custom image is set, it is first loaded from the source
|
||||||
|
* and then cached locally. The default image is never cached.</p>
|
||||||
|
* @param source The source to read the image from, when it is not
|
||||||
|
* already loaded.
|
||||||
|
* @return The image to use. Either the custom one when set or the
|
||||||
|
* default one otherwise.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
* {@see org.pasteque.common.constants.DefaultImages}
|
||||||
|
*/
|
||||||
|
public byte[] getImage(ImageDataSource source) throws IOException {
|
||||||
|
if (!this.hasImage) {
|
||||||
|
return DefaultImages.getDefaultImage(this.model);
|
||||||
|
}
|
||||||
|
if (this.imageCache != null) {
|
||||||
|
return this.imageCache;
|
||||||
|
}
|
||||||
|
ImageDTO imageDTO = source.getImage(this.model, this.reference);
|
||||||
|
if (imageDTO != null) {
|
||||||
|
this.imageCache = imageDTO.getImage();
|
||||||
|
}
|
||||||
|
return this.imageCache;
|
||||||
|
}
|
||||||
|
}
|
||||||
124
src/main/java/org/pasteque/common/model/User.java
Normal file
124
src/main/java/org/pasteque/common/model/User.java
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
package org.pasteque.common.model;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.pasteque.common.datasource.AssociationInconsistencyException;
|
||||||
|
import org.pasteque.common.datasource.ImageDataSource;
|
||||||
|
import org.pasteque.common.datatransfer.dto.UserDTO;
|
||||||
|
import org.pasteque.common.datatransfer.dto.RoleDTO;
|
||||||
|
import org.pasteque.common.util.HashCypher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator of a cash register.
|
||||||
|
*/
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
/** {@see getId()} */
|
||||||
|
private Integer id;
|
||||||
|
/** {@see getName()} */
|
||||||
|
private String name;
|
||||||
|
/** {@see getCard()} */
|
||||||
|
private String card;
|
||||||
|
/** {@see getPassword()} */
|
||||||
|
private String password;
|
||||||
|
/** {@see hasPermission(String)} */
|
||||||
|
private Set<String> permissions;
|
||||||
|
/** {@see isVisible()} */
|
||||||
|
private boolean visible;
|
||||||
|
/** {@see hasImage()} */
|
||||||
|
private boolean hasImage;
|
||||||
|
/** {@see getImage()} */
|
||||||
|
private byte[] image;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty user.
|
||||||
|
*/
|
||||||
|
public User() {
|
||||||
|
this.init();
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a user from transmitted data.
|
||||||
|
* @param userDTO The user data.
|
||||||
|
* @param roleDTO The user's role data.
|
||||||
|
* @throws AssociationInconsistencyException
|
||||||
|
* When the role isn't assigned to the user.
|
||||||
|
*/
|
||||||
|
public User(UserDTO userDTO, RoleDTO roleDTO) throws AssociationInconsistencyException {
|
||||||
|
this.init();
|
||||||
|
this.id = userDTO.getId();
|
||||||
|
this.name = userDTO.getLabel();
|
||||||
|
this.card = userDTO.getCard();
|
||||||
|
this.password = userDTO.getPassword();
|
||||||
|
this.visible = userDTO.isActive();
|
||||||
|
this.hasImage = userDTO.hasImage();
|
||||||
|
if (!java.util.Objects.equals(userDTO.getRole(), roleDTO.getId())) {
|
||||||
|
throw new AssociationInconsistencyException(
|
||||||
|
"role", String.valueOf(userDTO.getRole()),
|
||||||
|
"id", String.valueOf(roleDTO.getId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (String p : roleDTO.getPermissions()) {
|
||||||
|
this.permissions.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common initialization.
|
||||||
|
*/
|
||||||
|
private void init() {
|
||||||
|
this.permissions = new HashSet<String>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this user has the given permission.
|
||||||
|
* @param permission The permission to check.
|
||||||
|
* @return True if the user has the given permission within it's role.
|
||||||
|
*/
|
||||||
|
public boolean hasPermission(String permission) {
|
||||||
|
return this.permissions.contains(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Get the raw image data associated to this user.</p>
|
||||||
|
* <p>If {@link hasImage} is false, null is returned without checking
|
||||||
|
* the source. The image is cached within this user once loaded.</p>
|
||||||
|
* @param source The source to read the image from when not already cached.
|
||||||
|
* @return The user's image.
|
||||||
|
*/
|
||||||
|
public byte[] getImage(ImageDataSource source) {
|
||||||
|
if (!this.hasImage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (this.image != null) {
|
||||||
|
return this.image;
|
||||||
|
} else {
|
||||||
|
//this.image = source.getImage(ModelName.USER, this.id);
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to authenticate without a password.
|
||||||
|
* @return True when the user can authenticate, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean authenticate() {
|
||||||
|
return this.password == null || this.password.equals("")
|
||||||
|
|| this.password.startsWith("empty:");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to authenticate with a password.
|
||||||
|
* @param password The clear password provided to authenticate.
|
||||||
|
* @return True when the user can authenticate, false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean authenticate(String password) {
|
||||||
|
return HashCypher.authenticate(password, this.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
package org.pasteque.common.model.option;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.pasteque.common.constants.OptionName;
|
||||||
|
import org.pasteque.common.datatransfer.dto.OptionDTO;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.JSONReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Option to configure the transmission of sales to the accounting.</p>
|
||||||
|
*/
|
||||||
|
public class AccountingConfigOption
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Name of the option that holds the accounting configuration.
|
||||||
|
*/
|
||||||
|
public static final OptionName NAME = OptionName.ACCOUNTING_CONFIG;
|
||||||
|
|
||||||
|
/** @See {@link getSaleAccount(String). */
|
||||||
|
private Map<String, String> saleAccounts;
|
||||||
|
/** @See {@link getTaxAccount(String). */
|
||||||
|
private Map<String, String> taxAccounts;
|
||||||
|
/** @See {@link getPaymentModeAccount(String). */
|
||||||
|
private Map<String, String> paymentModeAccounts;
|
||||||
|
/** @See {@link getCustomerAccount(String). */
|
||||||
|
private Map<String, String> customerAccounts;
|
||||||
|
/**
|
||||||
|
* Misc. accounts. @See {@link getExtraAccount(String).
|
||||||
|
*/
|
||||||
|
private Map<String, String> extraAccounts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an accounting configuration from an OptionDTO.
|
||||||
|
* @param option The option to read.
|
||||||
|
* @throws IllegalArgumentException If the option is not an accounting
|
||||||
|
* configuration one. See constants.
|
||||||
|
* @throws ParseException When an error occurs while reading the content
|
||||||
|
* of the Option.
|
||||||
|
*/
|
||||||
|
public AccountingConfigOption(OptionDTO option) throws ParseException {
|
||||||
|
this.saleAccounts = new HashMap<String, String>();
|
||||||
|
this.taxAccounts = new HashMap<String, String>();
|
||||||
|
this.paymentModeAccounts = new HashMap<String, String>();
|
||||||
|
this.customerAccounts = new HashMap<String, String>();
|
||||||
|
this.extraAccounts = new HashMap<String, String>();
|
||||||
|
String json = option.getContent();
|
||||||
|
JSONReader reader = new JSONReader(json);
|
||||||
|
reader.startObject();
|
||||||
|
this.addKeys(reader, "sales", this.saleAccounts);
|
||||||
|
this.addKeys(reader, "taxes", this.taxAccounts);
|
||||||
|
this.addKeys(reader, "paymentModes", this.paymentModeAccounts);
|
||||||
|
this.addKeys(reader, "customers", this.customerAccounts);
|
||||||
|
this.addKeys(reader, "extra", this.extraAccounts);
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill a map of accounts from the reader.
|
||||||
|
* @param reader The reader to read accounts from. Its state is not
|
||||||
|
* modified when returning or throwing an exception.
|
||||||
|
* @param object The name of the collection in the reader to read values from.
|
||||||
|
* @param to The map to fill.
|
||||||
|
* @throws ParseException When a parsing error occurs.
|
||||||
|
*/
|
||||||
|
private void addKeys(JSONReader reader, String object, Map<String, String> to)
|
||||||
|
throws ParseException {
|
||||||
|
reader.startObject(object);
|
||||||
|
try {
|
||||||
|
for (String ref : reader.listKeys()) {
|
||||||
|
to.put(ref, reader.readString(ref));
|
||||||
|
}
|
||||||
|
} catch (ParseException e) {
|
||||||
|
reader.endObject();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account to use for sales of the given category.
|
||||||
|
* @param categoryRef The reference of the category.
|
||||||
|
* @return The name of the account, null when not defined.
|
||||||
|
*/
|
||||||
|
public String getSaleAccount(String categoryRef) {
|
||||||
|
return this.saleAccounts.get(categoryRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account to use for collected taxes of the given tax.
|
||||||
|
* @param taxRef The reference of the tax.
|
||||||
|
* @return The name of the account, null when not defined.
|
||||||
|
*/
|
||||||
|
public String getTaxAccount(String taxRef) {
|
||||||
|
return this.taxAccounts.get(taxRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account to use for payments of the given mode.
|
||||||
|
* @param paymentModeRef The reference of the payment mode.
|
||||||
|
* @return The name of the account, null when not defined.
|
||||||
|
*/
|
||||||
|
public String getPaymentModeAccount(String paymentModeRef) {
|
||||||
|
return this.paymentModeAccounts.get(paymentModeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account to use for the balance of the given customer.
|
||||||
|
* @param customerRef The reference of the customer.
|
||||||
|
* @return The name of the account, null when not defined.
|
||||||
|
*/
|
||||||
|
public String getCustomerAccount(String customerRef) {
|
||||||
|
return this.customerAccounts.get(customerRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account to use for extra credits.
|
||||||
|
* @return The name of the account, null when not defiled.
|
||||||
|
*/
|
||||||
|
public String getExtraCreditAccount() {
|
||||||
|
return this.extraAccounts.get("extraCredit");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account to use for extra debits.
|
||||||
|
* @return The name of the account, null when not defiled.
|
||||||
|
*/
|
||||||
|
public String getExtraDebitAccount() {
|
||||||
|
return this.extraAccounts.get("extraDebit");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a generic extra account.
|
||||||
|
* @param ref The reference for the extra account.
|
||||||
|
* @return The name of the account, null when not defiled.
|
||||||
|
*/
|
||||||
|
public String getExtraAccount(String ref) {
|
||||||
|
return this.extraAccounts.get(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as below without extra references. Extra credit and extra debits
|
||||||
|
* are still checked.
|
||||||
|
* @param categoryRefs The list of reference of category to check.
|
||||||
|
* @param taxRefs The list of reference of tax to check.
|
||||||
|
* @param paymentModeRefs The list of reference of payment mode to check.
|
||||||
|
* @param customerRefs The list of reference of customer to check.
|
||||||
|
* @return True when an account is set for each reference. False otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isExhaustive(
|
||||||
|
Collection<String> categoryRefs,
|
||||||
|
Collection<String> taxRefs,
|
||||||
|
Collection<String> paymentModeRefs,
|
||||||
|
Collection<String> customerRefs) {
|
||||||
|
return this.isExhaustive(categoryRefs, taxRefs, paymentModeRefs, customerRefs, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the configuration contains account values for everything.
|
||||||
|
* @param categoryRefs The list of reference of category to check.
|
||||||
|
* @param taxRefs The list of reference of tax to check.
|
||||||
|
* @param paymentModeRefs The list of reference of payment mode to check.
|
||||||
|
* @param customerRefs The list of reference of customer to check.
|
||||||
|
* @param extraRefs The list of extra reference to check. Extra
|
||||||
|
* credit and extra debits are always checked and can be omitted from
|
||||||
|
* this collection. Can be null.
|
||||||
|
* @return True when an account is set for each reference. False otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isExhaustive(
|
||||||
|
Collection<String> categoryRefs,
|
||||||
|
Collection<String> taxRefs,
|
||||||
|
Collection<String> paymentModeRefs,
|
||||||
|
Collection<String> customerRefs,
|
||||||
|
Collection<String> extraRefs) {
|
||||||
|
if (!this.saleAccounts.keySet().containsAll(categoryRefs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.taxAccounts.keySet().containsAll(taxRefs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.paymentModeAccounts.keySet().containsAll(paymentModeRefs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.customerAccounts.keySet().containsAll(customerRefs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!this.extraAccounts.containsKey("extraCredit")
|
||||||
|
|| !this.extraAccounts.containsKey("extraDebit")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (extraRefs != null && !this.extraAccounts.keySet().containsAll(extraRefs)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package org.pasteque.common.model.option;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.pasteque.common.constants.CustomerContactField;
|
||||||
|
import org.pasteque.common.constants.OptionName;
|
||||||
|
import org.pasteque.common.datatransfer.dto.OptionDTO;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.JSONReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Option to rename the contact fields of all customer's account.</p>
|
||||||
|
* <p>In version 8 contact fields are static and most of them are unused.
|
||||||
|
* This option allows to recycle those fields for other purposes.</p>
|
||||||
|
*/
|
||||||
|
public class CustomContactFieldsOption
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Name of the option that holds contact field replacements.
|
||||||
|
*/
|
||||||
|
public static final OptionName NAME = OptionName.CUSTOMER_CONTACT_FIELDS;
|
||||||
|
|
||||||
|
/** See {@link getAltLabel(String). */
|
||||||
|
private Map<CustomerContactField, String> replacements;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a contact field replacement option from an OptionDTO.
|
||||||
|
* @param option The option to read.
|
||||||
|
* @throws IllegalArgumentException If the option is not a replacement
|
||||||
|
* one. See constants.
|
||||||
|
* @throws ParseException When an error occurs while reading the content
|
||||||
|
* of the Option.
|
||||||
|
*/
|
||||||
|
public CustomContactFieldsOption(OptionDTO option) throws ParseException {
|
||||||
|
this.replacements = new HashMap<CustomerContactField, String>();
|
||||||
|
String json = option.getContent();
|
||||||
|
JSONReader reader = new JSONReader(json);
|
||||||
|
reader.startObject();
|
||||||
|
for (CustomerContactField id : CustomerContactField.values()) {
|
||||||
|
if (reader.hasKey(id.getCode())) {
|
||||||
|
this.replacements.put(id, reader.readString(id.getCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this Option will replace the default label
|
||||||
|
* for the given contact field.
|
||||||
|
* @param field The field to check for replacement.
|
||||||
|
* @return True if an alternative label is set for that field.
|
||||||
|
*/
|
||||||
|
public boolean hasAltLabel(CustomerContactField field) {
|
||||||
|
return this.replacements.containsKey(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the alternative label for the given field. It is not translatable.
|
||||||
|
* @param field The field to replace.
|
||||||
|
* @return The alternative label.
|
||||||
|
*/
|
||||||
|
public String getAltLabel(CustomerContactField field) {
|
||||||
|
return this.replacements.get(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Implementations of extra settings for ease of use.
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.model.option;
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.price.Price;
|
||||||
|
import org.pasteque.coreutil.price.Price5;
|
||||||
|
import org.pasteque.coreutil.price.TaxAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A group of prices to return prices of a line from a
|
||||||
|
* {@link PriceRound}.</p>
|
||||||
|
* <p>This class has a limited scope to be used only from a
|
||||||
|
* {@link PriceRound} and apply the prices to a {@link ProductLine}.</p>
|
||||||
|
*/
|
||||||
|
/* package */ final class ComputedPrices
|
||||||
|
{
|
||||||
|
/** See {@link getUnitPrice()}. */
|
||||||
|
private Price basePrice;
|
||||||
|
/** See {@link getUnitTaxedPrice()}. */
|
||||||
|
private Price baseTaxedPrice;
|
||||||
|
/** See {@link getDiscountPrice()}. */
|
||||||
|
private Price discountPrice;
|
||||||
|
/** See {@link getDiscountTaxedPrice()}. */
|
||||||
|
private Price discountTaxedPrice;
|
||||||
|
/** See {@link getTaxes()}. */
|
||||||
|
private ImmutableList<TaxAmount> taxes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a price set of 0.
|
||||||
|
* The prices are set as {@link org.pasteque.coreutil.price.Price5}.
|
||||||
|
*/
|
||||||
|
public ComputedPrices() {
|
||||||
|
this.basePrice = new Price5(0.0);
|
||||||
|
this.baseTaxedPrice = new Price5(0.0);
|
||||||
|
this.discountPrice = new Price5(0.0);
|
||||||
|
this.discountTaxedPrice = new Price5(0.0);
|
||||||
|
this.taxes = new ImmutableList<TaxAmount>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create prices from all fields.
|
||||||
|
* @param basePrice See {@link getBasePrice()}.
|
||||||
|
* @param baseTaxedPrice See {@link getBaseTaxedPrice()}.
|
||||||
|
* @param discountPrice See {@link getDiscountPrice()}.
|
||||||
|
* @param discountTaxedPrice See {@link getDiscountTaxedPrice()}.
|
||||||
|
* @param taxes See {@link getTaxes()}.
|
||||||
|
*/
|
||||||
|
public ComputedPrices(
|
||||||
|
Price basePrice,
|
||||||
|
Price baseTaxedPrice,
|
||||||
|
Price discountPrice,
|
||||||
|
Price discountTaxedPrice,
|
||||||
|
ImmutableList<TaxAmount> taxes) {
|
||||||
|
this.basePrice = basePrice;
|
||||||
|
this.baseTaxedPrice = basePrice;
|
||||||
|
this.discountPrice = discountPrice;
|
||||||
|
this.discountTaxedPrice = discountTaxedPrice;
|
||||||
|
this.taxes = taxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the prices from an other set of prices and return the result.
|
||||||
|
* @param other The other set of prices to add.
|
||||||
|
* @return A new set of prices with all the prices summed and the
|
||||||
|
* tax amount summed and/or completed with other taxes. The precision
|
||||||
|
* of the resulting prices is the lowest of each operand for each price.
|
||||||
|
*/
|
||||||
|
public ComputedPrices add(ComputedPrices other) {
|
||||||
|
List<TaxAmount> taxSum = new ArrayList<TaxAmount>(other.getTaxes().size());
|
||||||
|
for (TaxAmount amount : this.taxes) {
|
||||||
|
taxSum.add(amount);
|
||||||
|
}
|
||||||
|
for (TaxAmount amount : other.taxes) {
|
||||||
|
boolean summed = false;
|
||||||
|
for (int i = 0; i < taxSum.size(); i++) {
|
||||||
|
TaxAmount iAmount = taxSum.get(i);
|
||||||
|
try {
|
||||||
|
TaxAmount sum = iAmount.sum(amount);
|
||||||
|
taxSum.set(i, sum);
|
||||||
|
summed = true;
|
||||||
|
break;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
continue; // Cannot sum: not the same tax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!summed) {
|
||||||
|
taxSum.add(amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ComputedPrices(
|
||||||
|
this.basePrice.add(other.getBasePrice()),
|
||||||
|
this.baseTaxedPrice.add(other.getBaseTaxedPrice()),
|
||||||
|
this.discountPrice.add(other.getDiscountPrice()),
|
||||||
|
this.discountTaxedPrice.add(other.getDiscountTaxedPrice()),
|
||||||
|
new ImmutableList<TaxAmount>(taxSum));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the in-quantity price without taxes before applying the discount.
|
||||||
|
* @return The in-quantity price without taxes before applying the discount.
|
||||||
|
*/
|
||||||
|
public Price getBasePrice() {
|
||||||
|
return this.basePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short alias for {@link getBasePrice()}.
|
||||||
|
* @return The in-quantity price without taxes before applying the discount.
|
||||||
|
*/
|
||||||
|
public Price bp() {
|
||||||
|
return this.basePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the in-quantity price with taxes before applying the discount.
|
||||||
|
* @return The in-quantity price with taxes before applying the discount.
|
||||||
|
*/
|
||||||
|
public Price getBaseTaxedPrice() {
|
||||||
|
return this.baseTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short alias for {@link getBaseTaxedPrice()}.
|
||||||
|
* @return The in-quantity price with taxes before applying the discount.
|
||||||
|
*/
|
||||||
|
public Price btp() {
|
||||||
|
return this.baseTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the in-quantity price without taxes after applying the discount.
|
||||||
|
* @return The in-quantity price without taxes after applying the discount.
|
||||||
|
*/
|
||||||
|
public Price getDiscountPrice() {
|
||||||
|
return this.discountPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short alias for {@link getDiscountPrice()}.
|
||||||
|
* @return The in-quantity price without taxes after applying the discount.
|
||||||
|
*/
|
||||||
|
public Price dp() {
|
||||||
|
return this.discountPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the in-quantity price with taxes after applying the discount.
|
||||||
|
* @return The in-quantity price with taxes after applying the discount.
|
||||||
|
*/
|
||||||
|
public Price getDiscountTaxedPrice() {
|
||||||
|
return this.discountTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short alias for {@link getDiscountTaxedPrice()}.
|
||||||
|
* @return The in-quantity price with taxes after applying the discount. */
|
||||||
|
public Price dtp() {
|
||||||
|
return this.discountTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tax amounts after applying the discount.
|
||||||
|
* There is no method to get the tax amounts before applying the discount
|
||||||
|
* as they have no meaning and may be inaccurate.
|
||||||
|
*/
|
||||||
|
public ImmutableList<TaxAmount> getTaxes() {
|
||||||
|
return this.taxes;
|
||||||
|
}
|
||||||
|
}
|
||||||
245
src/main/java/org/pasteque/common/model/order/Order.java
Normal file
245
src/main/java/org/pasteque/common/model/order/Order.java
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.price.Discount;
|
||||||
|
import org.pasteque.coreutil.price.FinalTaxAmount;
|
||||||
|
import org.pasteque.coreutil.price.Price2;
|
||||||
|
import org.pasteque.coreutil.transition.LineTransition;
|
||||||
|
import org.pasteque.coreutil.transition.OrderTransition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Basic content of an order. This model defines only the final identification
|
||||||
|
* of each line and a few meta data affecting the final price.</p>
|
||||||
|
* <p>A major order can be extended to add more information not related to the
|
||||||
|
* effective payment. Only a base order can be converted to a
|
||||||
|
* {@link org.pasteque.major.domain.MajorTicket} by adding payments to it.</p>
|
||||||
|
*/
|
||||||
|
// TODO WIP, keep OrderManager as a higher-level controller in org.pasteque.common.
|
||||||
|
public class Order implements OrderTransition
|
||||||
|
{
|
||||||
|
private List<ProductLine> lines;
|
||||||
|
/** See {@see getDiscount()}. */
|
||||||
|
private Discount discount;
|
||||||
|
/** The utility to compute prices and taxes. */
|
||||||
|
private OrderPrice orderPrice;
|
||||||
|
private ImmutableList<FinalTaxAmount> taxAmounts;
|
||||||
|
private Price2 finalPrice;
|
||||||
|
private Price2 finalTaxedPrice;
|
||||||
|
// TODO add customer reference for invoices?
|
||||||
|
/* TODO Map ExtraData, JSONlike object|array to pass to DTO */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty order.
|
||||||
|
* @param priceRound The method used to compute prices.
|
||||||
|
*/
|
||||||
|
public Order(PriceRound priceRound) {
|
||||||
|
this.orderPrice = new OrderPrice(this, priceRound);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute prices for the given line. It will reset order prices for
|
||||||
|
* all lines as the total may be changed.
|
||||||
|
* @param lineIndex The index of the line to compute.
|
||||||
|
* @see clearOrderPrices()
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public synchronized void recomputeLinePrice(int lineIndex) {
|
||||||
|
this.orderPrice.computeLocalLinePrice(lineIndex);
|
||||||
|
this.clearOrderPrices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute all prices. It will compute the price of each line
|
||||||
|
* including the discount from the order. If a line is not computed
|
||||||
|
* without the discount, it will be computed first.
|
||||||
|
* @see recomputeLinePrice(int)
|
||||||
|
*/
|
||||||
|
public synchronized void computePrices() {
|
||||||
|
this.orderPrice.computeGlobalPrices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the total prices of the order and the order prices of each line.
|
||||||
|
*/
|
||||||
|
private synchronized void clearOrderPrices() {
|
||||||
|
for (ProductLine l : this.lines) {
|
||||||
|
l.setOrderPrices(null);
|
||||||
|
}
|
||||||
|
this.finalPrice = null;
|
||||||
|
this.finalTaxedPrice = null;
|
||||||
|
this.taxAmounts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a line at the end of the order. It will reset the total prices
|
||||||
|
* of the order if a discount is set.
|
||||||
|
* @param line The line to add.
|
||||||
|
* @see isPriceComputed()
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public final synchronized void addLine(ProductLine line) {
|
||||||
|
this.lines.add(line);
|
||||||
|
if (this.discount != null) {
|
||||||
|
this.clearOrderPrices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the line at the specified index and shift all subsequent lines
|
||||||
|
* to fill the gap. It will reset the total prices of the order if a discount
|
||||||
|
* is set.
|
||||||
|
* @param lineIndex The index of the line to remove from the order.
|
||||||
|
* @throws IndexOutOfBoundsException When requesting an index outside
|
||||||
|
* of the bounds (less than 0 or equal or more than {@link getLineCount()}).
|
||||||
|
* @see isPriceComputed()
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public final synchronized void removeLine(int lineIndex) {
|
||||||
|
this.lines.remove(lineIndex);
|
||||||
|
if (this.discount != null) {
|
||||||
|
this.clearOrderPrices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the line at the specified index. It will reset the total prices
|
||||||
|
* of the order if a discount is set.
|
||||||
|
* @param lineIndex The index of the line to replace.
|
||||||
|
* @param updated The new line.
|
||||||
|
* @throws IndexOutOfBoundsException When requesting an index outside
|
||||||
|
* of the bounds (less than 0 or equal or more than {@link getLineCount()}).
|
||||||
|
* @see isPriceComputed()
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public final synchronized void replaceLine(int lineIndex, ProductLine updated) {
|
||||||
|
this.lines.set(lineIndex, updated);
|
||||||
|
if (this.discount != null) {
|
||||||
|
this.clearOrderPrices();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the line at the specified index.
|
||||||
|
* @param index The index of the line.
|
||||||
|
* @return The line at the specified index.
|
||||||
|
* @throws IndexOutOfBoundsException When requesting an index outside
|
||||||
|
* of the bounds (less than 0 or equal or more than {@link getLineCount()}).
|
||||||
|
*/
|
||||||
|
public synchronized ProductLine getLine(int index) {
|
||||||
|
return this.lines.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of lines in this order.
|
||||||
|
* @return The number of line in this order.
|
||||||
|
*/
|
||||||
|
public synchronized int getLineCount() {
|
||||||
|
return this.lines.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from OrderTransition
|
||||||
|
public synchronized ImmutableList<LineTransition> getLines() {
|
||||||
|
LineTransition[] tlines = new LineTransition[this.lines.size()];
|
||||||
|
for (int i = 0; i < this.lines.size(); i++) {
|
||||||
|
tlines[i] = this.lines.get(i);
|
||||||
|
}
|
||||||
|
return new ImmutableList<LineTransition>(tlines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the discount applied to the whole order. It will reset the total
|
||||||
|
* prices of the order.
|
||||||
|
* @param discount The new discount. It cannot be applied to unit prices.
|
||||||
|
*/
|
||||||
|
public final synchronized void setDiscount(Discount discount) {
|
||||||
|
if ((this.discount == null && discount == null)
|
||||||
|
|| (this.discount != null && this.discount.equals(discount))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.discount = discount;
|
||||||
|
this.clearOrderPrices();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The effective discount to apply to the whole price of the order.
|
||||||
|
* @return The discount.
|
||||||
|
* @see org.pasteque.coreutil.price.Discount#getEffectiveDiscount(Discount)
|
||||||
|
*/
|
||||||
|
public final Discount getDiscount() {
|
||||||
|
return Discount.getEffectiveDiscount(this.discount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount to apply to the whole price of the order.
|
||||||
|
* @return The discount, without effective check.
|
||||||
|
* @see getDiscount()
|
||||||
|
*/
|
||||||
|
public final Discount getRawDiscount() {
|
||||||
|
return this.discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the prices are set or not.
|
||||||
|
* @return True when the prices are set.
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public boolean isPriceComputed() {
|
||||||
|
return this.finalPrice != null
|
||||||
|
&& this.taxAmounts != null
|
||||||
|
&& this.finalTaxedPrice != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without tax of the whole order.
|
||||||
|
* @return The price without tax of the whole order.
|
||||||
|
* Null when not computed.
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public Price2 getFinalPrice() {
|
||||||
|
return this.finalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of taxes of the whole order.
|
||||||
|
* @return The amount of taxes of the whole order. Null when not computed.
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public ImmutableList<FinalTaxAmount> getTaxAmounts() {
|
||||||
|
return this.taxAmounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes of the whole order.
|
||||||
|
* @return The price with taxes of the whole order.
|
||||||
|
* Null when not computed.
|
||||||
|
* @see computePrices()
|
||||||
|
*/
|
||||||
|
public Price2 getFinalTaxedPrice() {
|
||||||
|
return this.finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the prices of the whole order. Limited scope to be called from
|
||||||
|
* {@link OrderPrice}.
|
||||||
|
* @param finalPrice See {@link getFinalPrice()}.
|
||||||
|
* @param taxAmounts See {@link getTaxAmounts()}.
|
||||||
|
* @param finalTaxedPrice See {@link getFinalTaxedPrice()}.
|
||||||
|
*/
|
||||||
|
/* package */ void setOrderPrices(
|
||||||
|
Price2 finalPrice,
|
||||||
|
ImmutableList<FinalTaxAmount> taxAmounts,
|
||||||
|
Price2 finalTaxedPrice) {
|
||||||
|
this.finalPrice = finalPrice;
|
||||||
|
this.taxAmounts = taxAmounts;
|
||||||
|
this.finalTaxedPrice = finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the method used to compute prices.
|
||||||
|
* @return The method used to compute prices for this order.
|
||||||
|
*/
|
||||||
|
public final PriceRound getPriceRound() {
|
||||||
|
return this.orderPrice.getPriceRound();
|
||||||
|
}
|
||||||
|
}
|
||||||
251
src/main/java/org/pasteque/common/model/order/OrderPrice.java
Normal file
251
src/main/java/org/pasteque/common/model/order/OrderPrice.java
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.constants.DiscountTarget;
|
||||||
|
import org.pasteque.coreutil.constants.DiscountType;
|
||||||
|
import org.pasteque.coreutil.price.Discount;
|
||||||
|
import org.pasteque.coreutil.price.FinalTaxAmount;
|
||||||
|
import org.pasteque.coreutil.price.Price2;
|
||||||
|
import org.pasteque.coreutil.price.Price5;
|
||||||
|
import org.pasteque.coreutil.price.TaxAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Price utility. Limited scope to be used by {@link Order} but keep all
|
||||||
|
* price-related functions into a dedicated class.
|
||||||
|
*/
|
||||||
|
// TODO WIP
|
||||||
|
/* package */ final class OrderPrice
|
||||||
|
{
|
||||||
|
private Order order;
|
||||||
|
private PriceRound priceRound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty order.
|
||||||
|
* @param priceRound The method used to compute prices.
|
||||||
|
*/
|
||||||
|
public OrderPrice(Order order, PriceRound priceRound) {
|
||||||
|
this.order = order;
|
||||||
|
this.priceRound = priceRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute or recompute the price of a line without considering the
|
||||||
|
* discount from the order. Limited scope to be called from {@link Order}.
|
||||||
|
*/
|
||||||
|
/* package */ void computeLocalLinePrice(int lineIndex) {
|
||||||
|
ProductLine line = this.order.getLine(lineIndex);
|
||||||
|
this.computeLocalLinePrice(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute or recompute the price of a line without considering the
|
||||||
|
* discount from the order. Use {@link computeGlobalLinePrice(int)}
|
||||||
|
* to ensure the line is not from an other order.
|
||||||
|
*/
|
||||||
|
private void computeLocalLinePrice(ProductLine line) {
|
||||||
|
ComputedPrices prices = this.priceRound.computeLocalPrices(line);
|
||||||
|
line.setLocalPrices(prices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sum the local prices from each line. If the prices of a line are not
|
||||||
|
* computed, they will be computed on the fly. It blocks order changes
|
||||||
|
* during the computation.
|
||||||
|
* Limited scope to be called from {@link Order}.
|
||||||
|
* @return The sum of the local prices of each line. At the end those prices
|
||||||
|
* are rounded to 2 decimals except tax amounts which are not really relevant.
|
||||||
|
*/
|
||||||
|
/* package */ ComputedPrices sumLocalPrices() {
|
||||||
|
synchronized(this.order) {
|
||||||
|
if (this.order.getLineCount() == 0) {
|
||||||
|
return new ComputedPrices(
|
||||||
|
new Price5(0.0), new Price5(0.0),
|
||||||
|
new Price5(0.0), new Price5(0.0),
|
||||||
|
new ImmutableList<TaxAmount>());
|
||||||
|
}
|
||||||
|
ProductLine line = this.order.getLine(0);
|
||||||
|
if (!line.isLocalPriceComputed()) {
|
||||||
|
this.computeLocalLinePrice(line);
|
||||||
|
}
|
||||||
|
ComputedPrices result = line.getLocalPrices();
|
||||||
|
for (int i = 1; i < this.order.getLineCount(); i++) {
|
||||||
|
line = this.order.getLine(i);
|
||||||
|
if (!line.isLocalPriceComputed()) {
|
||||||
|
this.computeLocalLinePrice(line);
|
||||||
|
}
|
||||||
|
result = result.add(this.order.getLine(i).getLocalPrices());
|
||||||
|
}
|
||||||
|
return new ComputedPrices(
|
||||||
|
new Price2(result.bp()), new Price2(result.btp()),
|
||||||
|
new Price2(result.dp()), new Price2(result.dtp()),
|
||||||
|
result.getTaxes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Compute or recompute the price of a line including the discount from
|
||||||
|
* the order. Use {@link computeGlobalPrices()} as it makes no sense to
|
||||||
|
* compute only a single line.</p>
|
||||||
|
* <p>This will spread the discount from the order across all lines. This
|
||||||
|
* method assumes all local prices are computed.</p>
|
||||||
|
* <p>Global prices are always set as
|
||||||
|
* {@link org.pasteque.coreutil.price.Price5}.</p>
|
||||||
|
* @param lineIndex The index of the line from the order.
|
||||||
|
* @param discountedTotal The base total with the discount from the order.
|
||||||
|
* @param rate The rate (total / discountedTotal) which correspond to the
|
||||||
|
* actual discount rate applied to the order.
|
||||||
|
* @param target The target of the discount applied to the order.
|
||||||
|
*/
|
||||||
|
/* package */ void computeOrderLinePrice(int lineIndex,
|
||||||
|
Price2 discountedTotal, double rate, DiscountTarget target) {
|
||||||
|
ProductLine line = this.order.getLine(lineIndex);
|
||||||
|
this.computeOrderLinePrice(line, discountedTotal, rate, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local function to compute global prices of a line. Use
|
||||||
|
* {@link computeGlobalLinePrice(int)} to ensure the line is not from
|
||||||
|
* an other order.
|
||||||
|
* @param line The line to recompute. It will set its global prices.
|
||||||
|
* @param discountedTotal The base total with the discount from the order.
|
||||||
|
* @param rate The rate (total / discountedTotal) which correspond to the
|
||||||
|
* actual discount rate applied to the order.
|
||||||
|
* @param target The target of the discount applied to the order.
|
||||||
|
*/
|
||||||
|
private void computeOrderLinePrice(ProductLine line,
|
||||||
|
Price2 discountedTotal, double rate, DiscountTarget target) {
|
||||||
|
// TODO make sure the sum of global line prices are consistent with the total (see PriceTotalRound)
|
||||||
|
Price5 discountBasePrice;
|
||||||
|
Price5 discountedPrice;
|
||||||
|
ImmutableList<TaxAmount> taxAmounts;
|
||||||
|
Price5 discountedTaxedPrice;
|
||||||
|
Discount discount = new Discount(DiscountType.RATE, target, rate);
|
||||||
|
switch (target) {
|
||||||
|
case TOTAL:
|
||||||
|
discountBasePrice = new Price5(line.getLocalDiscountPrice());
|
||||||
|
discountedPrice = discount.applyTo(discountBasePrice);
|
||||||
|
taxAmounts = PriceNoRound.computeTaxes(line, discountedPrice);
|
||||||
|
discountedTaxedPrice = PriceNoRound.addTaxes(discountedPrice, taxAmounts);
|
||||||
|
break;
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
discountBasePrice = new Price5(line.getLocalDiscountTaxedPrice());
|
||||||
|
discountedTaxedPrice = discount.applyTo(discountBasePrice);
|
||||||
|
taxAmounts = PriceNoRound.computeReverseTaxes(line, discountedTaxedPrice);
|
||||||
|
discountedPrice = PriceNoRound.removeTaxes(discountedTaxedPrice, taxAmounts);
|
||||||
|
break;
|
||||||
|
case UNIT:
|
||||||
|
case UNIT_TAXED:
|
||||||
|
throw new IllegalArgumentException (String.format("Discount of type %s cannot be applied to an order.",
|
||||||
|
target.getCode()));
|
||||||
|
default:
|
||||||
|
assert false : target;
|
||||||
|
line.setOrderPrices(line.getLocalPrices());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ComputedPrices globalPrices = new ComputedPrices(
|
||||||
|
line.getLocalDiscountPrice(), line.getLocalDiscountTaxedPrice(),
|
||||||
|
discountedPrice, discountedTaxedPrice,
|
||||||
|
taxAmounts);
|
||||||
|
line.setOrderPrices(globalPrices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Compute or recompute the price of each line including the discount from
|
||||||
|
* the order. Limited scope to be called from {@link Order}.</p>
|
||||||
|
* <p>This will spread the discount from the order across all lines. This
|
||||||
|
* method will compute local prices if not already set. It blocks order changes
|
||||||
|
* during the computation.</p>
|
||||||
|
* <p>Global prices are always set as
|
||||||
|
* {@link org.pasteque.coreutil.price.Price5}.</p>
|
||||||
|
* @throws IllegalStateException When the discount cannot be applied to an order.
|
||||||
|
* See {@link org.pasteque.coreutil.constants.DiscountTarget}.
|
||||||
|
*/
|
||||||
|
/* package */ void computeGlobalPrices() throws IllegalStateException {
|
||||||
|
synchronized(this.order) {
|
||||||
|
if (this.order.getLineCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Make sure each line has local prices set
|
||||||
|
for (int i = 0; i < this.order.getLineCount(); i++) {
|
||||||
|
ProductLine line = this.order.getLine(i);
|
||||||
|
if (!line.isLocalPriceComputed()) {
|
||||||
|
this.computeLocalLinePrice(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Easy case: no discount
|
||||||
|
if (this.order.getDiscount() == null) {
|
||||||
|
for (int i = 0; i < this.order.getLineCount(); i++) {
|
||||||
|
ProductLine line = this.order.getLine(i);
|
||||||
|
line.setOrderPrices(line.getLocalPrices());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Compute discount
|
||||||
|
Discount discount = this.order.getDiscount();
|
||||||
|
ComputedPrices localSum = this.sumLocalPrices();
|
||||||
|
Price2 basePrice;
|
||||||
|
double discountRate;
|
||||||
|
Price2 discountedPrice;
|
||||||
|
Price2 discountedTaxedPrice;
|
||||||
|
switch (discount.getTarget()) {
|
||||||
|
case UNIT:
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
case UNIT_TAXED:
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
case TOTAL:
|
||||||
|
basePrice = (Price2) localSum.getDiscountPrice();
|
||||||
|
discountedPrice = discount.applyTo(basePrice);
|
||||||
|
discountRate = discountedPrice.getRatio(basePrice);
|
||||||
|
// Report the discount to each line
|
||||||
|
for (int i = 0; i < this.order.getLineCount(); i++) {
|
||||||
|
this.computeOrderLinePrice(i, discountedPrice, discountRate, discount.getTarget());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
basePrice = (Price2) localSum.getDiscountTaxedPrice();
|
||||||
|
discountedTaxedPrice = discount.applyTo(basePrice);
|
||||||
|
discountRate = discountedTaxedPrice.getRatio(basePrice);
|
||||||
|
// Report the discount to each line
|
||||||
|
for (int i = 0; i < this.order.getLineCount(); i++) {
|
||||||
|
this.computeOrderLinePrice(i, discountedTaxedPrice, discountRate, discount.getTarget());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : discount.getType();
|
||||||
|
for (int i = 0; i < this.order.getLineCount(); i++) {
|
||||||
|
ProductLine line = this.order.getLine(i);
|
||||||
|
line.setOrderPrices(line.getLocalPrices());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Sum the new discounted prices
|
||||||
|
ComputedPrices sum = this.order.getLine(0).getOrderPrices();
|
||||||
|
for (int i = 1; i < this.order.getLineCount(); i++) {
|
||||||
|
sum = sum.add(this.order.getLine(i).getOrderPrices());
|
||||||
|
}
|
||||||
|
// Round the final prices to 2 decimals and store them into the order
|
||||||
|
FinalTaxAmount[] finalTaxes = new FinalTaxAmount[sum.getTaxes().size()];
|
||||||
|
for (int i = 0; i < sum.getTaxes().size(); i++) {
|
||||||
|
TaxAmount amount = sum.getTaxes().get(i);
|
||||||
|
finalTaxes[i] = new FinalTaxAmount(
|
||||||
|
amount.getTax(),
|
||||||
|
new Price2(amount.getBase()),
|
||||||
|
new Price2(amount.getAmount()));
|
||||||
|
}
|
||||||
|
this.order.setOrderPrices(
|
||||||
|
new Price2(sum.dp()),
|
||||||
|
new ImmutableList<FinalTaxAmount>(finalTaxes),
|
||||||
|
new Price2(sum.dtp()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the method used to compute prices.
|
||||||
|
* @return The method used to compute prices for this order.
|
||||||
|
*/
|
||||||
|
public final PriceRound getPriceRound() {
|
||||||
|
return this.priceRound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,448 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.constants.DiscountTarget;
|
||||||
|
import org.pasteque.coreutil.constants.DiscountType;
|
||||||
|
import org.pasteque.coreutil.price.Discount;
|
||||||
|
import org.pasteque.coreutil.price.Price;
|
||||||
|
import org.pasteque.coreutil.price.Price2;
|
||||||
|
import org.pasteque.coreutil.price.TaxAmount;
|
||||||
|
import org.pasteque.coreutil.price.Rate;
|
||||||
|
import org.pasteque.coreutil.price.Tax;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Round amounts for each line and compute the sums from each rounded line.
|
||||||
|
* The discount rate applied to the order is added to the discount rate of
|
||||||
|
* each line to compute the total.</p>
|
||||||
|
* <p>This method will produce figures suited for accounting but may show
|
||||||
|
* inconsistencies for common people by moving rounding issues into the total.</p>
|
||||||
|
* <p>It is best suitable when working mostly with non-taxed prices on
|
||||||
|
* 2 decimals. When working with taxed prices on 2 decimals,
|
||||||
|
* {@link org.pasteque.common.model.order.PriceTotalRound} may be more suitable.</p>
|
||||||
|
* <p>Pass the singleton to an {@link Order} to automagically compute
|
||||||
|
* the prices from there.</p>
|
||||||
|
* <h2>Computation method</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>Each line has a unit price without taxes on 2 or 5 decimals and an indicative
|
||||||
|
* taxed unit price on 2 decimals.
|
||||||
|
* Those are multiplied by the quantity and the results are rounded to 2 decimals.
|
||||||
|
* This is the major difference with {@link org.pasteque.common.model.order.PriceTotalRound}
|
||||||
|
* which will keep the non-taxed unit and quantity prices on 5 decimals.</li>
|
||||||
|
* <li>The discount of the line produces new prices, still on 2 decimals.</li>
|
||||||
|
* <li>The total without the discount from the order is the sum of all rounded
|
||||||
|
* lines. The tax amounts will not match when recomputed from the sum of the
|
||||||
|
* bases.</li>
|
||||||
|
* <li>The discount from the order is applied to produce prices on 5 decimals
|
||||||
|
* for the line. This discount is also applied to the subtotals of the order,
|
||||||
|
* to provide results on 2 decimals.</li>
|
||||||
|
* </ul>
|
||||||
|
* <h2>Available figures</h2>
|
||||||
|
* <ul>
|
||||||
|
* <li>Unit prices with and without taxes with any decimal precision.</li>
|
||||||
|
* <li>Line value with and without taxes on 2 decimals.</li>
|
||||||
|
* <li>Tax amounts for each line on 2 decimals</li>
|
||||||
|
* <li>Untaxed line price + tax amounts = taxed line price, without rounding issue</li>
|
||||||
|
* <li>Recomputing tax amounts from the total is not accurate.</li>
|
||||||
|
* <li>Line values with the discount from the order on 2 decimals. Discounts
|
||||||
|
* applied to the total may not be accurate.</li>
|
||||||
|
* </ul>
|
||||||
|
* <h2>Discount target compatibility</h2>
|
||||||
|
* <p>The discount applied to the line provides rounded results.
|
||||||
|
* The discount applied to the order can provide mixed results depending
|
||||||
|
* upon its target:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link org.pasteque.coreutil.constants.DiscountTarget#UNIT} and
|
||||||
|
* {@link org.pasteque.coreutil.constants.DiscountTarget#UNIT_TAXED} combines
|
||||||
|
* the discounts for each line and keep rounded results for each line. This is
|
||||||
|
* the recommended way to keep a consistent rounding method. It provides results
|
||||||
|
* rounded to 2 decimals for each line with a consistent computing method.</li>
|
||||||
|
* <li>{@link org.pasteque.coreutil.constants.DiscountTarget#TOTAL} and
|
||||||
|
* {@link org.pasteque.coreutil.constants.DiscountTarget#TOTAL_TAXED} applies
|
||||||
|
* a fraction of the discount to each line and round it to 2 decimals.
|
||||||
|
* The amount of the discount computed from the total as it would be expected
|
||||||
|
* may not be accurate.</li>
|
||||||
|
* </ul>
|
||||||
|
* @see org.pasteque.common.model.order.PriceTotalRound
|
||||||
|
*/
|
||||||
|
// TODO WIP, missing some price computations
|
||||||
|
public final class PriceLineRound implements PriceRound
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Singleton instance.
|
||||||
|
*/
|
||||||
|
public static final PriceLineRound instance = new PriceLineRound();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor for the singleton.
|
||||||
|
*/
|
||||||
|
private PriceLineRound() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the amounts of taxes from a base price.
|
||||||
|
* @param line The line to get taxes from.
|
||||||
|
* @param basePrice The base price from which to compute taxes.
|
||||||
|
* That is the non-taxed price with or without the discount.
|
||||||
|
* @return The computed tax amounts. The taxed price can be computed
|
||||||
|
* from basePrice by passing the result to
|
||||||
|
* {@link addTaxes(Price2, TaxAmount[])}.
|
||||||
|
*/
|
||||||
|
private ImmutableList<TaxAmount> computeTaxes(ProductLine line, Price2 basePrice) {
|
||||||
|
ImmutableList<Tax> taxes = line.getTaxes();
|
||||||
|
TaxAmount[] taxAmounts = new TaxAmount[taxes.size()];
|
||||||
|
for (int i = 0; i < taxes.size(); i++) {
|
||||||
|
Price2 amount;
|
||||||
|
Tax tax = taxes.get(i);
|
||||||
|
switch (tax.getType()) {
|
||||||
|
case RATE:
|
||||||
|
amount = basePrice.multiply(tax.getAmount());
|
||||||
|
break;
|
||||||
|
case FIXED_QUANTITY:
|
||||||
|
amount = new Price2(line.getQuantity().getQuantity() * tax.getAmount());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : tax.getType();
|
||||||
|
amount = new Price2(0.0);
|
||||||
|
}
|
||||||
|
taxAmounts[i] = new TaxAmount(tax, basePrice, amount);
|
||||||
|
}
|
||||||
|
return new ImmutableList<TaxAmount>(taxAmounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the amounts of taxes from a base taxed price.
|
||||||
|
* @param line The line to get taxes from.
|
||||||
|
* @param baseTaxedPrice The base price from which to compute taxes.
|
||||||
|
* That is the taxed price with or without the discount.
|
||||||
|
* @return The computed tax amounts. The non-taxed price can be computed
|
||||||
|
* from baseTaxedPrice by passing the result to
|
||||||
|
* {@link removeTaxes(Price2, TaxAmount[])}.
|
||||||
|
*/
|
||||||
|
private ImmutableList<TaxAmount> computeReverseTaxes(ProductLine line, Price2 baseTaxedPrice) {
|
||||||
|
ImmutableList<Tax> taxes = line.getTaxes();
|
||||||
|
double totalRate = 0.0;
|
||||||
|
Price2 totalFixed = new Price2(0.0);
|
||||||
|
for (int i = 0; i < taxes.size(); i++) {
|
||||||
|
Tax tax = taxes.get(i);
|
||||||
|
switch (tax.getType()) {
|
||||||
|
case RATE:
|
||||||
|
if (!tax.isIncludedInBase()) {
|
||||||
|
totalRate += tax.getAmount();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FIXED_QUANTITY:
|
||||||
|
if (!tax.isIncludedInBase()) {
|
||||||
|
totalFixed = totalFixed.add(new Price2(line.getQuantity().getQuantity() * tax.getAmount()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : tax.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Price2 basePrice = baseTaxedPrice.subtract(totalFixed);
|
||||||
|
basePrice = basePrice.divide(1.0 + totalRate);
|
||||||
|
return this.computeTaxes(line, basePrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a discount to a base price.
|
||||||
|
* @param line The line from which to get the discount. This method
|
||||||
|
* accepts a null discount.
|
||||||
|
* @param basePrice The price from which to compute the discount.
|
||||||
|
* @return The price with the discount applied.
|
||||||
|
*/
|
||||||
|
private Price2 computeDiscount(Price2 basePrice, Discount discount) {
|
||||||
|
if (discount == null) {
|
||||||
|
return basePrice;
|
||||||
|
}
|
||||||
|
switch (discount.getType()) {
|
||||||
|
case RATE:
|
||||||
|
Rate r = new Rate(discount.getValue(), false);
|
||||||
|
return r.applyTo(basePrice);
|
||||||
|
case VALUE:
|
||||||
|
return basePrice.add(-discount.getValue());
|
||||||
|
default:
|
||||||
|
assert false : discount.getType();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add taxes from a base price.
|
||||||
|
* @param basePrice The price from which to add taxes. Usually the
|
||||||
|
* non-taxed line price, with or without the discount.
|
||||||
|
* @param taxAmounts The amounts of taxes, usually computed from
|
||||||
|
* {@link computeTaxes(ProductLine, Price2)}.
|
||||||
|
* @return The base price with the tax amounts added when required.
|
||||||
|
*/
|
||||||
|
private Price2 addTaxes(Price2 basePrice, ImmutableList<TaxAmount> taxAmounts) {
|
||||||
|
Price taxedPrice = basePrice;
|
||||||
|
for (TaxAmount amount : taxAmounts) {
|
||||||
|
if (!amount.getTax().isIncludedInBase()) {
|
||||||
|
taxedPrice = taxedPrice.add(amount.getAmount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Price2(taxedPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove taxes from a base price.
|
||||||
|
* @param basePrice The price from which to remove taxes. Usually the
|
||||||
|
* taxed line price, with or without the discount.
|
||||||
|
* @param taxAmounts The amounts of taxes, usually computed from
|
||||||
|
* {@link computeReverseTaxes(ProductLine, Price2)}.
|
||||||
|
* @return The base price with the tax amounts subtracted when required.
|
||||||
|
* When using this method, the tax amounts may not be completely accurate
|
||||||
|
* when recomputed from the result due to rounding issues.
|
||||||
|
*/
|
||||||
|
private Price2 removeTaxes(Price2 basePrice, ImmutableList<TaxAmount> taxAmounts) {
|
||||||
|
Price price = basePrice;
|
||||||
|
for (TaxAmount amount : taxAmounts) {
|
||||||
|
if (!amount.getTax().isIncludedInBase()) {
|
||||||
|
price = price.subtract(amount.getAmount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Price2(price);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // From PriceRound
|
||||||
|
public ComputedPrices computeLocalPrices(ProductLine line) {
|
||||||
|
// Compute base prices without any discount
|
||||||
|
Price2 basePrice = new Price2(line.getUnitPrice().multiply(line.getQuantity()));
|
||||||
|
ImmutableList<TaxAmount> taxAmounts = this.computeTaxes(line, basePrice);
|
||||||
|
Price2 baseTaxedPrice = this.addTaxes(basePrice, taxAmounts);
|
||||||
|
// Compute prices with the discount from the line
|
||||||
|
Discount discount = line.getDiscount();
|
||||||
|
if (discount == null) {
|
||||||
|
return new ComputedPrices(basePrice, baseTaxedPrice,
|
||||||
|
basePrice, baseTaxedPrice,
|
||||||
|
taxAmounts);
|
||||||
|
} else {
|
||||||
|
Price2 discountBasePrice;
|
||||||
|
Price2 discountedPrice;
|
||||||
|
Price2 discountedTaxedPrice;
|
||||||
|
ImmutableList<TaxAmount> discountTaxAmounts;
|
||||||
|
// Compute the discount according to the target
|
||||||
|
switch (discount.getTarget()) {
|
||||||
|
case UNIT:
|
||||||
|
discountBasePrice = new Price2(line.getUnitPrice());
|
||||||
|
discountedPrice = this.computeDiscount(discountBasePrice, discount);
|
||||||
|
discountedPrice = discountedPrice.multiply(line.getQuantity());
|
||||||
|
discountTaxAmounts = this.computeTaxes(line, discountedPrice);
|
||||||
|
discountedTaxedPrice = this.addTaxes(discountedPrice, discountTaxAmounts);
|
||||||
|
break;
|
||||||
|
case UNIT_TAXED:
|
||||||
|
discountBasePrice = new Price2(line.getUnitTaxedPrice());
|
||||||
|
discountedTaxedPrice = this.computeDiscount(discountBasePrice, discount);
|
||||||
|
discountedTaxedPrice = discountedTaxedPrice.multiply(line.getQuantity());
|
||||||
|
discountTaxAmounts = this.computeReverseTaxes(line, discountedTaxedPrice);
|
||||||
|
discountedPrice = this.removeTaxes(discountedTaxedPrice, discountTaxAmounts);
|
||||||
|
break;
|
||||||
|
case TOTAL:
|
||||||
|
discountBasePrice = basePrice;
|
||||||
|
discountedPrice = this.computeDiscount(discountBasePrice, discount);
|
||||||
|
discountTaxAmounts = this.computeTaxes(line, discountedPrice);
|
||||||
|
discountedTaxedPrice = this.addTaxes(discountedPrice, discountTaxAmounts);
|
||||||
|
break;
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
discountBasePrice = baseTaxedPrice;
|
||||||
|
discountedTaxedPrice = this.computeDiscount(discountBasePrice, discount);
|
||||||
|
discountTaxAmounts = this.computeReverseTaxes(line, discountedTaxedPrice);
|
||||||
|
discountedPrice = this.removeTaxes(discountedTaxedPrice, discountTaxAmounts);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : discount.getTarget();
|
||||||
|
discountBasePrice = basePrice;
|
||||||
|
discountedPrice = basePrice;
|
||||||
|
discountedTaxedPrice = baseTaxedPrice;
|
||||||
|
discountTaxAmounts = taxAmounts;
|
||||||
|
}
|
||||||
|
return new ComputedPrices(
|
||||||
|
basePrice, baseTaxedPrice,
|
||||||
|
discountedPrice, discountedTaxedPrice,
|
||||||
|
discountTaxAmounts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from PriceRound
|
||||||
|
public Discount computeFinalDiscount(
|
||||||
|
ProductLine line,
|
||||||
|
Discount orderDiscount,
|
||||||
|
Price subtotal)
|
||||||
|
throws IllegalStateException {
|
||||||
|
if (!line.isLocalPriceComputed()) {
|
||||||
|
throw new IllegalStateException("Local prices must be computed before.");
|
||||||
|
}
|
||||||
|
if (Discount.getEffectiveDiscount(line.getDiscount()) == null) {
|
||||||
|
return orderDiscount;
|
||||||
|
}
|
||||||
|
// Compute the ratio the line represents from the total
|
||||||
|
// to distribute the discount amount from TOTAL and TOTAL_TAXED targets.
|
||||||
|
double lineRatio;
|
||||||
|
Price2 lineRatioAmount;
|
||||||
|
switch (orderDiscount.getTarget()) {
|
||||||
|
case TOTAL:
|
||||||
|
lineRatio = line.getLocalDiscountPrice().getRatio(subtotal);
|
||||||
|
lineRatioAmount = new Price2(subtotal.multiply(lineRatio));
|
||||||
|
break;
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
lineRatio = line.getLocalDiscountTaxedPrice().getRatio(subtotal);
|
||||||
|
lineRatioAmount = new Price2(subtotal.multiply(lineRatio));
|
||||||
|
break;
|
||||||
|
case UNIT:
|
||||||
|
case UNIT_TAXED: // lineRatio not used
|
||||||
|
lineRatio = 1.0;
|
||||||
|
lineRatioAmount = new Price2(0.0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : orderDiscount.getTarget();
|
||||||
|
return line.getDiscount();
|
||||||
|
}
|
||||||
|
Discount lineRateDiscount;
|
||||||
|
Discount appliedDiscount; // Combines the line and order discounts
|
||||||
|
Price discountBasePrice;
|
||||||
|
switch (orderDiscount.getType()) {
|
||||||
|
case VALUE:
|
||||||
|
// Dispatch the fixed value within the line and recompute the result
|
||||||
|
switch (orderDiscount.getTarget()) {
|
||||||
|
case UNIT:
|
||||||
|
// Apply the fixed value directly to each line
|
||||||
|
discountBasePrice = orderDiscount.applyTo(new Price2(line.getLocalDiscountPrice()));
|
||||||
|
appliedDiscount = orderDiscount.toUnitRate(line.getLocalPrice(), discountBasePrice);
|
||||||
|
break;
|
||||||
|
case UNIT_TAXED:
|
||||||
|
// Apply the fixed value directly to each line
|
||||||
|
discountBasePrice = orderDiscount.applyTo(new Price2(line.getLocalDiscountTaxedPrice()));
|
||||||
|
appliedDiscount = orderDiscount.toUnitRate(line.getLocalPrice(), discountBasePrice);
|
||||||
|
break;
|
||||||
|
case TOTAL:
|
||||||
|
// Apply a fraction of the fixed value to the line and round the result
|
||||||
|
discountBasePrice = new Price2(orderDiscount.applyPartlyTo(line.getLocalDiscountPrice(), lineRatio));
|
||||||
|
appliedDiscount = orderDiscount.toUnitRate(line.getLocalPrice(), discountBasePrice);
|
||||||
|
break;
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
// Apply a fraction of the fixed value to the line and round the result
|
||||||
|
discountBasePrice = new Price2(orderDiscount.applyPartlyTo(line.getLocalDiscountTaxedPrice(), lineRatio));
|
||||||
|
appliedDiscount = orderDiscount.toUnitRate(line.getLocalDiscountTaxedPrice(), discountBasePrice);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : orderDiscount.getTarget();
|
||||||
|
return line.getDiscount();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case RATE:
|
||||||
|
// Convert the discount from the line to a rate and add the one
|
||||||
|
// from the order
|
||||||
|
switch (orderDiscount.getTarget()) {
|
||||||
|
case UNIT:
|
||||||
|
// Add the rate directly to each line
|
||||||
|
discountBasePrice = line.getLocalPrice();
|
||||||
|
lineRateDiscount = orderDiscount.toUnitRate(line.getLocalPrice(), line.getLocalDiscountPrice());
|
||||||
|
appliedDiscount = new Discount(DiscountType.RATE, DiscountTarget.UNIT,
|
||||||
|
lineRateDiscount.getValue() + orderDiscount.getValue());
|
||||||
|
break;
|
||||||
|
case UNIT_TAXED:
|
||||||
|
// Add the rate directly to each line
|
||||||
|
discountBasePrice = line.getLocalTaxedPrice();
|
||||||
|
lineRateDiscount = orderDiscount.toUnitRate(line.getLocalTaxedPrice(), line.getLocalDiscountTaxedPrice());
|
||||||
|
appliedDiscount = new Discount(DiscountType.RATE, DiscountTarget.UNIT_TAXED,
|
||||||
|
lineRateDiscount.getValue() + orderDiscount.getValue());
|
||||||
|
break;
|
||||||
|
case TOTAL:
|
||||||
|
// Apply the fraction of the total discount to the line and recompute
|
||||||
|
discountBasePrice = line.getLocalDiscountPrice();
|
||||||
|
appliedDiscount = new Discount(DiscountType.RATE, DiscountTarget.TOTAL,
|
||||||
|
1.0 - discountBasePrice.subtract(lineRatioAmount).getRatio(discountBasePrice));
|
||||||
|
break;
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
// Apply the fraction of the total discount to the line and recompute
|
||||||
|
discountBasePrice = line.getLocalTaxedPrice();
|
||||||
|
appliedDiscount = new Discount(DiscountType.RATE, DiscountTarget.TOTAL_TAXED,
|
||||||
|
1.0 - discountBasePrice.subtract(lineRatioAmount).getRatio(subtotal));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : orderDiscount.getTarget();
|
||||||
|
return line.getDiscount();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert false : orderDiscount.getTarget();
|
||||||
|
return line.getDiscount();
|
||||||
|
}
|
||||||
|
return appliedDiscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from PriceRound
|
||||||
|
public ComputedPrices computeGlobalPrices(
|
||||||
|
ProductLine line,
|
||||||
|
Discount finalDiscount,
|
||||||
|
Price subtotal)
|
||||||
|
throws IllegalStateException {
|
||||||
|
if (!line.isLocalPriceComputed()) {
|
||||||
|
throw new IllegalStateException("Local prices must be computed before.");
|
||||||
|
}
|
||||||
|
if (Discount.getEffectiveDiscount(finalDiscount) == null) {
|
||||||
|
return line.getLocalPrices();
|
||||||
|
}
|
||||||
|
// Recompute prices with the new combined discount
|
||||||
|
Price discountedPrice;
|
||||||
|
Price discountedTaxedPrice;
|
||||||
|
ImmutableList<TaxAmount> discountedTaxAmounts;
|
||||||
|
switch (finalDiscount.getTarget()) {
|
||||||
|
case UNIT: // intentional fall-through
|
||||||
|
case TOTAL:
|
||||||
|
discountedPrice = finalDiscount.applyTo(new Price2(line.getLocalPrice()));
|
||||||
|
discountedTaxAmounts = this.computeTaxes(line, (Price2) discountedPrice);
|
||||||
|
discountedTaxedPrice = this.addTaxes((Price2) discountedPrice, discountedTaxAmounts);
|
||||||
|
break;
|
||||||
|
case UNIT_TAXED: // intentional fall-through
|
||||||
|
case TOTAL_TAXED:
|
||||||
|
discountedTaxedPrice = finalDiscount.applyTo(new Price2(line.getLocalTaxedPrice()));
|
||||||
|
discountedTaxAmounts = this.computeReverseTaxes(line, (Price2) discountedTaxedPrice);
|
||||||
|
discountedPrice = this.removeTaxes((Price2) discountedTaxedPrice, discountedTaxAmounts);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : finalDiscount.getTarget();
|
||||||
|
return line.getLocalPrices();
|
||||||
|
}
|
||||||
|
return new ComputedPrices(
|
||||||
|
line.getLocalPrice(), line.getLocalTaxedPrice(),
|
||||||
|
discountedPrice, discountedTaxedPrice,
|
||||||
|
discountedTaxAmounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // From PriceRound
|
||||||
|
public ComputedPrices computeTotal(Order order) throws IllegalStateException {
|
||||||
|
synchronized(order) {
|
||||||
|
if (order.getLineCount() == 0) {
|
||||||
|
return new ComputedPrices(
|
||||||
|
new Price2(0.0), new Price2(0.0),
|
||||||
|
new Price2(0.0), new Price2(0.0),
|
||||||
|
new ImmutableList<TaxAmount>());
|
||||||
|
}
|
||||||
|
if (!order.getLine(0).isLocalPriceComputed()) {
|
||||||
|
throw new IllegalStateException("Prices for line 0 are not computed");
|
||||||
|
}
|
||||||
|
Discount discount = Discount.getEffectiveDiscount(order.getDiscount());
|
||||||
|
if (discount != null) {
|
||||||
|
ComputedPrices subtotal = order.getLine(0).getLocalPrices();
|
||||||
|
for (int i = 1; i < order.getLineCount(); i++) {
|
||||||
|
if (!order.getLine(i).isPriceComputed()) {
|
||||||
|
throw new IllegalStateException("Prices for line " + i + " are not computed");
|
||||||
|
}
|
||||||
|
subtotal = subtotal.add(order.getLine(i).getOrderPrices());
|
||||||
|
}
|
||||||
|
return subtotal;
|
||||||
|
}
|
||||||
|
// The final prices of each line are already rounded to 2 decimals.
|
||||||
|
// Sum everything and that's it.
|
||||||
|
ComputedPrices total = order.getLine(0).getOrderPrices();
|
||||||
|
for (int i = 1; i < order.getLineCount(); i++) {
|
||||||
|
if (!order.getLine(i).isPriceComputed()) {
|
||||||
|
throw new IllegalStateException("Prices for line " + i + " are not computed");
|
||||||
|
}
|
||||||
|
total = total.add(order.getLine(i).getLocalPrices());
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
118
src/main/java/org/pasteque/common/model/order/PriceNoRound.java
Normal file
118
src/main/java/org/pasteque/common/model/order/PriceNoRound.java
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.price.Price5;
|
||||||
|
import org.pasteque.coreutil.price.Tax;
|
||||||
|
import org.pasteque.coreutil.price.TaxAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to compute prices without rounding, always using
|
||||||
|
* {@link org.pasteque.coreutil.price.Price5}.
|
||||||
|
* This class can be used only to compute amounts that are never presented to
|
||||||
|
* the customers.
|
||||||
|
*/
|
||||||
|
/* package */ final class PriceNoRound
|
||||||
|
{
|
||||||
|
/** Static methods only. */
|
||||||
|
private PriceNoRound() { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the amounts of taxes from a base price.
|
||||||
|
* @param line The line to get taxes from.
|
||||||
|
* @param basePrice The base price from which to compute taxes.
|
||||||
|
* @return The computed tax amounts. The taxed price can be computed
|
||||||
|
* from basePrice by passing the result to
|
||||||
|
* {@link addTaxes(Price5, TaxAmount[])}.
|
||||||
|
*/
|
||||||
|
/* package */ static ImmutableList<TaxAmount> computeTaxes(
|
||||||
|
ProductLine line, Price5 basePrice) {
|
||||||
|
ImmutableList<Tax> taxes = line.getTaxes();
|
||||||
|
TaxAmount[] taxAmounts = new TaxAmount[taxes.size()];
|
||||||
|
for (int i = 0; i < taxes.size(); i++) {
|
||||||
|
Price5 amount;
|
||||||
|
Tax tax = taxes.get(i);
|
||||||
|
switch (tax.getType()) {
|
||||||
|
case RATE:
|
||||||
|
amount = basePrice.multiply(tax.getAmount());
|
||||||
|
break;
|
||||||
|
case FIXED_QUANTITY:
|
||||||
|
amount = new Price5(line.getQuantity().getQuantity() * tax.getAmount());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : tax.getType();
|
||||||
|
amount = new Price5(0.0);
|
||||||
|
}
|
||||||
|
taxAmounts[i] = new TaxAmount(tax, basePrice, amount);
|
||||||
|
}
|
||||||
|
return new ImmutableList<TaxAmount>(taxAmounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the amounts of taxes from a base taxed price.
|
||||||
|
* @param line The line to get taxes from.
|
||||||
|
* @param baseTaxedPrice The base price from which to compute taxes.
|
||||||
|
* @return The computed tax amounts. The non-taxed price can be computed
|
||||||
|
* from baseTaxedPrice by passing the result to
|
||||||
|
* {@link removeTaxes(Price5, TaxAmount[])}.
|
||||||
|
*/
|
||||||
|
/* package */ static ImmutableList<TaxAmount> computeReverseTaxes(ProductLine line, Price5 baseTaxedPrice) {
|
||||||
|
ImmutableList<Tax> taxes = line.getTaxes();
|
||||||
|
double totalRate = 0.0;
|
||||||
|
Price5 totalFixed = new Price5(0.0);
|
||||||
|
for (int i = 0; i < taxes.size(); i++) {
|
||||||
|
Tax tax = taxes.get(i);
|
||||||
|
switch (tax.getType()) {
|
||||||
|
case RATE:
|
||||||
|
if (!tax.isIncludedInBase()) {
|
||||||
|
totalRate += tax.getAmount();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FIXED_QUANTITY:
|
||||||
|
if (!tax.isIncludedInBase()) {
|
||||||
|
totalFixed = totalFixed.add(new Price5(line.getQuantity().getQuantity() * tax.getAmount()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert false : tax.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Price5 basePrice = baseTaxedPrice.subtract(totalFixed);
|
||||||
|
basePrice = basePrice.divide(1.0 + totalRate);
|
||||||
|
return computeTaxes(line, basePrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add taxes from a base price.
|
||||||
|
* @param basePrice The price from which to add taxes.
|
||||||
|
* @param taxAmounts The amounts of taxes. All amounts are converted to
|
||||||
|
* {@link org.pasteque.coreutil.price.Price5}.
|
||||||
|
* @return The base price with the tax amounts added when required.
|
||||||
|
*/
|
||||||
|
/* package */ static Price5 addTaxes(Price5 basePrice, ImmutableList<TaxAmount> taxAmounts) {
|
||||||
|
Price5 taxedPrice = basePrice;
|
||||||
|
for (TaxAmount amount : taxAmounts) {
|
||||||
|
if (!amount.getTax().isIncludedInBase()) {
|
||||||
|
taxedPrice = taxedPrice.add(new Price5(amount.getAmount()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Price5(taxedPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove taxes from a base price.
|
||||||
|
* @param basePrice The price from which to remove taxes. Usually the
|
||||||
|
* taxed line price, with or without the discount.
|
||||||
|
* @param taxAmounts The amounts of taxes. All amounts are converted to
|
||||||
|
* {@link org.pasteque.coreutil.price.Price5}.
|
||||||
|
* @return The base price with the tax amounts subtracted when required.
|
||||||
|
*/
|
||||||
|
/* package */ static Price5 removeTaxes(Price5 basePrice, ImmutableList<TaxAmount> taxAmounts) {
|
||||||
|
Price5 price = basePrice;
|
||||||
|
for (TaxAmount amount : taxAmounts) {
|
||||||
|
if (!amount.getTax().isIncludedInBase()) {
|
||||||
|
price = price.subtract(new Price5(amount.getAmount()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.price.Discount;
|
||||||
|
import org.pasteque.coreutil.price.Price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Compute prices and manage rounding issues.</p>
|
||||||
|
* <p>Implementations will provide different precision and summing methods
|
||||||
|
* that moves rounding issues to some prices or others.</p>
|
||||||
|
*/
|
||||||
|
public interface PriceRound
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Compute prices for the line without considering the discount from
|
||||||
|
* the order.
|
||||||
|
* @param line The line to compute the prices from.
|
||||||
|
* @return The computed prices.
|
||||||
|
*/
|
||||||
|
public ComputedPrices computeLocalPrices(ProductLine line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the discount to apply to the price without any discount to
|
||||||
|
* get the result of applying all discounts.
|
||||||
|
* @param line The line to compute the final discount for
|
||||||
|
* @param orderDiscount The discount applied to the whole order.
|
||||||
|
* @param subtotal The total amount of the order before applying
|
||||||
|
* the discount. It is either the total with or without taxes according
|
||||||
|
* to the target of the discount (ignoring whether is is total or by unit).
|
||||||
|
* @return The combined discount of the one from the line and the one
|
||||||
|
* from the order.
|
||||||
|
* @throws IllegalStateException When the local prices of the line
|
||||||
|
* are not computed.
|
||||||
|
*/
|
||||||
|
public Discount computeFinalDiscount(
|
||||||
|
ProductLine line,
|
||||||
|
Discount orderDiscount,
|
||||||
|
Price subtotal)
|
||||||
|
throws IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute prices for the line including the discount from the order.
|
||||||
|
* @param line The line to compute the prices from.
|
||||||
|
* @param finalDiscount The final discount to apply.
|
||||||
|
* @param subtotal The total amount of the order before applying
|
||||||
|
* the discount. It is either the total with or without taxes according
|
||||||
|
* to the target of the discount (ignoring whether is is total or by unit).
|
||||||
|
* @return The computed prices. BasePrice and baseTaxedPrice from this
|
||||||
|
* result are meaningless and should be shared with
|
||||||
|
* {@link computeLocalPrices(ProductLine)}.
|
||||||
|
* @throws IllegalStateException When the local prices of the line
|
||||||
|
* are not computed.
|
||||||
|
* @see computeFinalDiscount(ProductLine, Discount, Price)
|
||||||
|
* @see org.pasteque.common.model.order.PriceNoRound
|
||||||
|
*/
|
||||||
|
public ComputedPrices computeGlobalPrices(
|
||||||
|
ProductLine line,
|
||||||
|
Discount finalDiscount,
|
||||||
|
Price subtotal)
|
||||||
|
throws IllegalStateException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute total prices for an order by applying the discount
|
||||||
|
* to the subtotal. The result may be slightly different from the sum
|
||||||
|
* of the global prices of each line.
|
||||||
|
* Implementations must synchronize on the order.
|
||||||
|
* @param order The order to get the total from.
|
||||||
|
* @return The final prices and tax amounts. All should be rounded to
|
||||||
|
* {@link org.pasteque.coreutil.price.Price2} to be payable and writable
|
||||||
|
* in accounting. The result may be slightly different from the sum
|
||||||
|
* of the global prices of each line due to the rounding of each resulting
|
||||||
|
* figure.
|
||||||
|
* @throws IllegalStateException When the global prices are not set for
|
||||||
|
* each line.
|
||||||
|
*/
|
||||||
|
public ComputedPrices computeTotal(Order order) throws IllegalStateException;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.price.Discount;
|
||||||
|
import org.pasteque.coreutil.price.Price;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Keep precision for each line and round only the total.
|
||||||
|
* The discount rate of the order is applied to the total of the order.</p>
|
||||||
|
* <p>This method is best suitable when hiding non-taxed prices or using them
|
||||||
|
* with 5 decimals. Only the totals are rounded to 2 decimals for accounting.
|
||||||
|
* When showing the prices for each line, {@link org.pasteque.common.model.order.PriceLineRound}
|
||||||
|
* may be more suitable.</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>Each line has a unit price without taxes on 5 decimals to have an
|
||||||
|
* almost always corresponding taxed unit price on 2 decimals.
|
||||||
|
* Those are multiplied by the quantity while keeping the same precision. This is
|
||||||
|
* the major difference with {@link org.pasteque.common.model.order.PriceLineRound} which
|
||||||
|
* will round this results to 2 decimals.</li>
|
||||||
|
* <li>The discount of the line produces new prices, still on 5 and 2 decimals.</li>
|
||||||
|
* <li>The total without the discount from the order is the sum of all unrounded
|
||||||
|
* lines. It may be rounded to 2 decimals for display purposes but is still computed
|
||||||
|
* with 5 decimals.</li>
|
||||||
|
* <li>The discount from the order is applied to produce prices on 5 decimals
|
||||||
|
* for the line. This discount is also applied to the subtotals of the order,
|
||||||
|
* to provide results on 2 decimals.</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>This method produces figures suited for common people regarding the total
|
||||||
|
* prices and moves rounding issues into accounting which can handle only two
|
||||||
|
* digit figures in partial sums.</p>
|
||||||
|
* <p>Pass the singleton to an {@link Order} to automagically compute
|
||||||
|
* the prices from there.</p>
|
||||||
|
*/
|
||||||
|
public final class PriceTotalRound implements PriceRound
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Singleton.
|
||||||
|
*/
|
||||||
|
public static final PriceTotalRound instance = new PriceTotalRound();
|
||||||
|
|
||||||
|
private PriceTotalRound() { }
|
||||||
|
|
||||||
|
@Override // from PriceRound
|
||||||
|
public ComputedPrices computeLocalPrices(ProductLine line) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from PriceRound
|
||||||
|
public Discount computeFinalDiscount(ProductLine line, Discount orderDiscount, Price subtotal)
|
||||||
|
throws IllegalStateException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from PriceRound
|
||||||
|
public ComputedPrices computeGlobalPrices(
|
||||||
|
ProductLine line,
|
||||||
|
Discount discount,
|
||||||
|
Price subtotal) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from PriceRound
|
||||||
|
public ComputedPrices computeTotal(Order order) throws IllegalStateException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
381
src/main/java/org/pasteque/common/model/order/ProductLine.java
Normal file
381
src/main/java/org/pasteque/common/model/order/ProductLine.java
Normal file
|
|
@ -0,0 +1,381 @@
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.price.Discount;
|
||||||
|
import org.pasteque.coreutil.price.Price;
|
||||||
|
import org.pasteque.coreutil.price.Price5;
|
||||||
|
import org.pasteque.coreutil.price.Quantity;
|
||||||
|
import org.pasteque.coreutil.price.Tax;
|
||||||
|
import org.pasteque.coreutil.price.TaxAmount;
|
||||||
|
import org.pasteque.coreutil.transition.LineTransition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A line shared with {@link org.pasteque.common.model.order.Order} and
|
||||||
|
* {@link org.pasteque.major.domain.MajorTicket} to hold basic information
|
||||||
|
* about what was ordered or sold.</p>
|
||||||
|
* <p>A line has two set of prices. The local prices are considering only
|
||||||
|
* the line outside any order. The order prices take in account the discount
|
||||||
|
* from the order.
|
||||||
|
*/
|
||||||
|
// TODO WIP, Immutable notice, add subclasses for compositions and packs, add support for extradata (attributes)
|
||||||
|
public class ProductLine implements LineTransition
|
||||||
|
{
|
||||||
|
/** See {@link getProductReference()}. */
|
||||||
|
private final String productReference;
|
||||||
|
/** See {@link getProductLabel()}. */
|
||||||
|
private final String productLabel;
|
||||||
|
/** See {@link getCategoryReference()}. */
|
||||||
|
private final String categoryReference;
|
||||||
|
/** See {@link getUnitPrice()}. */
|
||||||
|
private final Price unitPrice;
|
||||||
|
/** See {@link getUnitTaxedPrice()}. */
|
||||||
|
private final Price unitTaxedPrice;
|
||||||
|
/** See {@link getTaxes()}. */
|
||||||
|
private final ImmutableList<Tax> taxes;
|
||||||
|
/** See {@link getQuantity()}. */
|
||||||
|
private final Quantity quantity;
|
||||||
|
/** See {@link getDiscount()}. */
|
||||||
|
private final Discount discount;
|
||||||
|
/** See {@link getLocalTaxes()}. */
|
||||||
|
private ImmutableList<TaxAmount> localTaxes;
|
||||||
|
/** See {@link getLocalPrice()}. */
|
||||||
|
private Price localPrice;
|
||||||
|
/** See {@link getLocalTaxedPrice()}. */
|
||||||
|
private Price localTaxedPrice;
|
||||||
|
/** See {@link getLocalDiscountPrice()}. */
|
||||||
|
private Price localDiscountPrice;
|
||||||
|
/** See {@link getLocalDiscountTaxedPrice()}. */
|
||||||
|
private Price localDiscountTaxedPrice;
|
||||||
|
/** See {@link getFinalDiscount} */
|
||||||
|
private Discount finalDiscount;
|
||||||
|
/** See {@link getTotalTaxes()}. */
|
||||||
|
private ImmutableList<TaxAmount> orderTaxes;
|
||||||
|
/** See {@link getTotalPrice()}. */
|
||||||
|
private Price5 orderPrice;
|
||||||
|
/** See {@link getTotalTaxedPrice()}. */
|
||||||
|
private Price5 orderTaxedPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a line from all fields. The prices are not computed.
|
||||||
|
* @param productReference See {@link getProductReference()}.
|
||||||
|
* @param productLabel See {@link getProductLabel()}.
|
||||||
|
* @param categoryReference See {@link getCategoryReference()}.
|
||||||
|
* @param unitPrice See {@link getUnitPrice()}.
|
||||||
|
* @param unitTaxedPrice See {@link getUnitTaxedPrice()}.
|
||||||
|
* @param quantity See {@link getQuantity()}.
|
||||||
|
* @param discount See {@link getDiscount()}.
|
||||||
|
* @param taxes See {@link getTaxes()}.
|
||||||
|
*/
|
||||||
|
public ProductLine(
|
||||||
|
String productReference,
|
||||||
|
String productLabel,
|
||||||
|
String categoryReference,
|
||||||
|
Price unitPrice,
|
||||||
|
Price unitTaxedPrice,
|
||||||
|
Quantity quantity,
|
||||||
|
Discount discount,
|
||||||
|
ImmutableList<Tax> taxes) {
|
||||||
|
this.productReference = productReference;
|
||||||
|
this.productLabel = productLabel;
|
||||||
|
this.categoryReference = categoryReference;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
this.unitTaxedPrice = unitTaxedPrice;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.discount = discount;
|
||||||
|
this.taxes = taxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an other line with an updated quantity.
|
||||||
|
* @param newUnitPrice The new unit price.
|
||||||
|
* @param newQuantity The new quantity.
|
||||||
|
* @return A new line with copied data with the new unit price and
|
||||||
|
* quantity. The prices are not computed.
|
||||||
|
*/
|
||||||
|
/*public ProductLine updated(Price newUnitPrice, Quantity newQuantity) {
|
||||||
|
return new ProductLine(
|
||||||
|
this.productReference),
|
||||||
|
this.productLabel,
|
||||||
|
this.categoryReference,
|
||||||
|
newUnitPrice,
|
||||||
|
newQuantity,
|
||||||
|
copyTax,
|
||||||
|
this.discountRate);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the prices of the line, without the discount from the order.
|
||||||
|
* It will also clear the global prices.
|
||||||
|
* Use null to clear the local prices.
|
||||||
|
* Limited scope to be used by {@link OrderPrice}.
|
||||||
|
* @param prices The prices to set.
|
||||||
|
*/
|
||||||
|
/* package */ void setLocalPrices(ComputedPrices prices) {
|
||||||
|
this.orderPrice = null;
|
||||||
|
this.orderTaxedPrice = null;
|
||||||
|
this.orderTaxes = null;
|
||||||
|
if (prices == null) {
|
||||||
|
this.localPrice = null;
|
||||||
|
this.localTaxedPrice = null;
|
||||||
|
this.localTaxes = null;
|
||||||
|
} else {
|
||||||
|
this.localPrice = prices.getBasePrice();
|
||||||
|
this.localTaxedPrice = prices.getBaseTaxedPrice();
|
||||||
|
this.localDiscountPrice = prices.getDiscountPrice();
|
||||||
|
this.localDiscountTaxedPrice = prices.getDiscountTaxedPrice();
|
||||||
|
this.localTaxes = prices.getTaxes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of prices before applying the discount from the order.
|
||||||
|
* Limited scope to ease price computation. Each price is available
|
||||||
|
* individually with from its own getter.
|
||||||
|
* @return The set of prices before applying the discount from the order.
|
||||||
|
*/
|
||||||
|
/* package */ ComputedPrices getLocalPrices() {
|
||||||
|
return new ComputedPrices(
|
||||||
|
this.localPrice, this.localTaxedPrice,
|
||||||
|
this.localDiscountPrice, this.localDiscountTaxedPrice,
|
||||||
|
this.localTaxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique reference of the product in this line.
|
||||||
|
* @return The unique reference of the product.
|
||||||
|
*/
|
||||||
|
public String getProductReference() {
|
||||||
|
return this.productReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label of the product in this line.
|
||||||
|
* @return The label of the product.
|
||||||
|
*/
|
||||||
|
public String getProductLabel() {
|
||||||
|
return this.productLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference of the category of the product in this line.
|
||||||
|
* The category itself is not copied, the category is identified
|
||||||
|
* for grouping.
|
||||||
|
* @return The reference of the category.
|
||||||
|
*/
|
||||||
|
public String getCategoryReference() {
|
||||||
|
return this.categoryReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference price for 1 unit of the product without taxes.
|
||||||
|
* @return The price for 1 unit. It should be a
|
||||||
|
* {@link org.pasteque.coreutil.price.Price5} to be able to compute a
|
||||||
|
* taxed price as {@link org.pasteque.coreutil.price.Price2} and avoid
|
||||||
|
* rounding issues in most cases.
|
||||||
|
*/
|
||||||
|
public Price getUnitPrice() {
|
||||||
|
return this.unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference price for 1 unit of the product including taxes.
|
||||||
|
* @return The price for 1 unit. For B2C, it should be usable as a
|
||||||
|
* {@link org.pasteque.coreutil.price.Price2} without losing much precision.
|
||||||
|
* For B2B this is rarely used or only informational.
|
||||||
|
*/
|
||||||
|
public Price getUnitTaxedPrice() {
|
||||||
|
return this.unitTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of taxes to apply to the line.
|
||||||
|
* @return The list of taxes applicable to the line.
|
||||||
|
*/
|
||||||
|
public ImmutableList<Tax> getTaxes() {
|
||||||
|
return this.taxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the quantity of product in this line.
|
||||||
|
* @return The quantity of product.
|
||||||
|
*/
|
||||||
|
public Quantity getQuantity() {
|
||||||
|
return this.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the effective discount applied for this line only.
|
||||||
|
* @return The effective discount of this line, can be null.
|
||||||
|
* @see org.pasteque.coreutil.price.Discount#getEffectiveDiscount(Discount)
|
||||||
|
*/
|
||||||
|
public Discount getDiscount() {
|
||||||
|
return Discount.getEffectiveDiscount(discount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount.
|
||||||
|
* @return The discount, without effective check.
|
||||||
|
* @see getDiscount()
|
||||||
|
*/
|
||||||
|
public Discount getRawDiscount() {
|
||||||
|
return this.discount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without taxes of this line before applying any discount.
|
||||||
|
* @return The price without taxes before applying any discount.
|
||||||
|
*/
|
||||||
|
@Override // from LineTransition
|
||||||
|
public Price getPrice() {
|
||||||
|
return this.localPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link getPrice()} for naming consistency.
|
||||||
|
* @return The price without taxes before applying any discount.
|
||||||
|
*/
|
||||||
|
public Price getLocalPrice() {
|
||||||
|
return this.localPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes of this line before applying any discount.
|
||||||
|
* @return The price with taxes before applying any discount.
|
||||||
|
*/
|
||||||
|
public Price getLocalTaxedPrice() {
|
||||||
|
return this.localTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without taxes of this line after applying the discount
|
||||||
|
* of the line.
|
||||||
|
* @return The price without taxes after applying the discount of the line.
|
||||||
|
*/
|
||||||
|
public Price getLocalDiscountPrice() {
|
||||||
|
return this.localDiscountPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes of this line after applying the discount
|
||||||
|
* of the line.
|
||||||
|
* @return The price with taxes after applying the discount of the line.
|
||||||
|
*/
|
||||||
|
public Price getLocalDiscountTaxedPrice() {
|
||||||
|
return this.localDiscountTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tax amounts for the line including the discount.
|
||||||
|
* @return The tax amounts computed from the price without taxes
|
||||||
|
* in quantity and including the discount applied to the line.
|
||||||
|
* The precision is left to the {@link org.pasteque.common.model.order.PriceRound}.
|
||||||
|
*/
|
||||||
|
public ImmutableList<TaxAmount> getLocalTaxes() {
|
||||||
|
return this.localTaxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the price of the line with the discount from the order.
|
||||||
|
* Use null to clear the global prices.
|
||||||
|
* Limited scope to be used by {@link OrderPrice}.
|
||||||
|
* @param prices The prices to set, null to clear prices.
|
||||||
|
* Non-discounted prices are ignored.
|
||||||
|
*/
|
||||||
|
/* package */ void setOrderPrices(ComputedPrices prices) {
|
||||||
|
if (prices == null) {
|
||||||
|
this.orderPrice = null;
|
||||||
|
this.orderTaxedPrice = null;
|
||||||
|
this.orderTaxes = null;
|
||||||
|
} else {
|
||||||
|
this.orderPrice = new Price5(prices.getDiscountPrice());
|
||||||
|
this.orderTaxedPrice = new Price5(prices.getDiscountTaxedPrice());
|
||||||
|
this.orderTaxes = prices.getTaxes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the discount resulting from applying both discount from the line
|
||||||
|
* and from the order.
|
||||||
|
* @param finalDiscount The combined discount.
|
||||||
|
*/
|
||||||
|
/* package */ void setFinalDiscount(Discount finalDiscount) {
|
||||||
|
this.finalDiscount = finalDiscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // From LineTransition
|
||||||
|
public Discount getFinalDiscount() {
|
||||||
|
return this.finalDiscount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without tax including the discount from the order.
|
||||||
|
* @return The price without tax including the discount from the order.
|
||||||
|
* Always set to {@link org.pasteque.coreutil.price.Price5} to avoid rounding
|
||||||
|
* issues when spreading from the total.
|
||||||
|
*/
|
||||||
|
@Override // from LineTransition
|
||||||
|
public Price5 getTotalPrice() {
|
||||||
|
return this.orderPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link getTotalPrice()} for naming consistency.
|
||||||
|
* @return The price without tax including the discount from the order.
|
||||||
|
* Always set to {@link org.pasteque.coreutil.price.Price5} to avoid rounding
|
||||||
|
* issues when spreading from the total.
|
||||||
|
*/
|
||||||
|
public Price5 getOrderPrice() {
|
||||||
|
return this.orderPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes including the discount from the order.
|
||||||
|
* Limited scope as it has no meaning by itself and is used to compute the
|
||||||
|
* total in the {@link Order}.
|
||||||
|
* @return The price without tax including the discount from the order.
|
||||||
|
* Always set to {@link org.pasteque.coreutil.price.Price5} to avoid rounding
|
||||||
|
* issues when spreading from the total.
|
||||||
|
*/
|
||||||
|
/* package */ Price getOrderTaxedPrice() {
|
||||||
|
return this.orderTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // from LineTransition
|
||||||
|
public ImmutableList<TaxAmount> getTotalTaxes() {
|
||||||
|
return this.orderTaxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of prices after applying the discount from the order.
|
||||||
|
* Limited scope to ease price computation. Each price is available
|
||||||
|
* individually with from its own getter. The base prices are copied
|
||||||
|
* from the local prices after applying the discount from the line.
|
||||||
|
* @return The set of prices before applying the discount from the order.
|
||||||
|
*/
|
||||||
|
/* package */ ComputedPrices getOrderPrices() {
|
||||||
|
return new ComputedPrices(
|
||||||
|
this.localDiscountPrice, this.localDiscountTaxedPrice,
|
||||||
|
this.orderPrice, this.orderTaxedPrice,
|
||||||
|
this.orderTaxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the prices of the line are set.
|
||||||
|
* @return True when the local prices are set.
|
||||||
|
*/
|
||||||
|
public boolean isLocalPriceComputed() {
|
||||||
|
return this.localPrice != null && this.localDiscountPrice != null
|
||||||
|
&& this.localTaxes != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether all prices are computed. Limited scope as the order prices
|
||||||
|
* has no meaning outside the total from the {@link Order}.
|
||||||
|
* @return True when local and order prices are set.
|
||||||
|
*/
|
||||||
|
/* package */ boolean isPriceComputed() {
|
||||||
|
return this.isLocalPriceComputed()
|
||||||
|
&& this.orderPrice != null && this.orderTaxedPrice != null
|
||||||
|
&& this.orderTaxes != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Create and manipulate orders, with price computation.
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.model.order;
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* <p>Usage classes</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.model;
|
||||||
8
src/main/java/org/pasteque/common/package-info.java
Normal file
8
src/main/java/org/pasteque/common/package-info.java
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* <p>Common business logic, outside storing critical data.</p>
|
||||||
|
*
|
||||||
|
* <p>This library is designed to be shareable between multiple clients and servers
|
||||||
|
* to define the common code for the minor versions.
|
||||||
|
* It is not related to the major version and extends the basic data.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.common;
|
||||||
60
src/main/java/org/pasteque/common/util/HashCypher.java
Normal file
60
src/main/java/org/pasteque/common/util/HashCypher.java
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.pasteque.common.util;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to manage hashed passwords.
|
||||||
|
*/
|
||||||
|
public class HashCypher
|
||||||
|
{
|
||||||
|
/** Static utility-class, cannot be instantiated. */
|
||||||
|
private HashCypher() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check a password against a hashed password.
|
||||||
|
* @param password The clear password to check
|
||||||
|
* @param hashPassword The hashed version of the password, created with
|
||||||
|
* {@link #hashString(String)}.
|
||||||
|
* @return True When they match, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean authenticate(String password, String hashPassword) {
|
||||||
|
if (hashPassword == null || "".equals(hashPassword) || hashPassword.startsWith("empty:")) {
|
||||||
|
return password == null || "".equals(password);
|
||||||
|
}
|
||||||
|
if (password == null || "".equals(password)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hashPassword.startsWith("sha1:")) {
|
||||||
|
String hash = hashString(password).toLowerCase();
|
||||||
|
return hashPassword.toLowerCase().equals(hash);
|
||||||
|
} else if (hashPassword.startsWith("plain:")) {
|
||||||
|
return hashPassword.equals("plain:" + password);
|
||||||
|
} else {
|
||||||
|
return hashPassword.equals(password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a hashed version of a password.
|
||||||
|
* @param sPassword The password to hash.
|
||||||
|
* @return A hashed version prefixed by the hashing method.
|
||||||
|
*/
|
||||||
|
public static String hashString(String sPassword) {
|
||||||
|
if (sPassword == null || sPassword.equals("")) {
|
||||||
|
return "empty:";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||||
|
md.update(sPassword.getBytes("UTF-8"));
|
||||||
|
byte[] res = md.digest();
|
||||||
|
return "sha1:" + Hexadecimal.byte2hex(res);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return "plain:" + sPassword;
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
return "plain:" + sPassword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/main/java/org/pasteque/common/util/Hexadecimal.java
Normal file
56
src/main/java/org/pasteque/common/util/Hexadecimal.java
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.pasteque.common.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Binary to hexadecimal format encoder/decoder.
|
||||||
|
* Not available natively until Java 17.</p>
|
||||||
|
* <p>This class is there for retrocompatibility with password encoding.
|
||||||
|
* Use {@link org.pasteque.common.datatransfer.dto.format.BinaryDTOFormat}
|
||||||
|
* to encode binary data as base64 strings instead.</p>
|
||||||
|
*/
|
||||||
|
/* package */ class Hexadecimal
|
||||||
|
{
|
||||||
|
private static final char [] hexchars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert binary data to an hexadecimal string.
|
||||||
|
* @param data The raw data to encode.
|
||||||
|
* @return The hexadecimal representation of the binary data,
|
||||||
|
* without any prefix.
|
||||||
|
*/
|
||||||
|
public static String byte2hex(byte[] data) {
|
||||||
|
StringBuffer sb = new StringBuffer(data.length * 2);
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
int high = ((data[i] & 0xF0) >> 4);
|
||||||
|
int low = (data[i] & 0x0F);
|
||||||
|
sb.append(hexchars[high]);
|
||||||
|
sb.append(hexchars[low]);
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an hexadecimal string to binary data.
|
||||||
|
* @param hexa The hexadecimal string, without any prefix.
|
||||||
|
* @return The binary data.
|
||||||
|
* @throws IllegalArgumentException When the hexadecimal string
|
||||||
|
* has an odd number of characters.
|
||||||
|
*/
|
||||||
|
public static byte[] hex2byte(String hexa) {
|
||||||
|
int length = hexa.length();
|
||||||
|
if ((length & 0x01) != 0) {
|
||||||
|
throw new IllegalArgumentException("odd number of characters.");
|
||||||
|
}
|
||||||
|
byte[] out = new byte[length >> 1];
|
||||||
|
// two characters form the hex value.
|
||||||
|
for (int i = 0, j = 0; j < length; i++) {
|
||||||
|
int f = Character.digit(hexa.charAt(j++), 16) << 4;
|
||||||
|
f = f | Character.digit(hexa.charAt(j++), 16);
|
||||||
|
out[i] = (byte) (f & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static utility-class, cannot be instantiated. */
|
||||||
|
private Hexadecimal() { }
|
||||||
|
}
|
||||||
4
src/main/java/org/pasteque/common/util/package-info.java
Normal file
4
src/main/java/org/pasteque/common/util/package-info.java
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Utility classes and general tools.
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.util;
|
||||||
24
src/main/java/org/pasteque/common/view/Buttonable.java
Normal file
24
src/main/java/org/pasteque/common/view/Buttonable.java
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.pasteque.common.view;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.pasteque.common.datasource.ImageDataSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for records that can be represented as a selection button.
|
||||||
|
*/
|
||||||
|
public interface Buttonable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the label to show for this button.
|
||||||
|
* @return The label of the button.
|
||||||
|
*/
|
||||||
|
public String getLabel();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the image to use for this button.
|
||||||
|
* @param source The source to use when the image is not already loaded.
|
||||||
|
* @return The image of the button.
|
||||||
|
* @throws IOException When an error occurs while reading the source.
|
||||||
|
*/
|
||||||
|
public byte[] getImage(ImageDataSource source) throws IOException;
|
||||||
|
}
|
||||||
185
src/main/java/org/pasteque/common/view/CoordStretcher.java
Normal file
185
src/main/java/org/pasteque/common/view/CoordStretcher.java
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
// Openbravo POS is a point of sales application designed for touch screens.
|
||||||
|
// Copyright (C) 2007-2009 Openbravo, S.L.
|
||||||
|
// http://www.openbravo.com/product/pos
|
||||||
|
//
|
||||||
|
// This file is part of Openbravo POS.
|
||||||
|
//
|
||||||
|
// Openbravo POS is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Openbravo POS is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Openbravo POS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package org.pasteque.common.view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to recompute coordinates of place buttons inside a floor panel
|
||||||
|
* according to the size of the container.
|
||||||
|
*/
|
||||||
|
public class CoordStretcher
|
||||||
|
{
|
||||||
|
/** The expected button width in pixels. */
|
||||||
|
private final int buttonWidth;
|
||||||
|
/** The expected button height in pixels. */
|
||||||
|
private final int buttonHeight;
|
||||||
|
/** Minimum x position of a table to crop unused space. */
|
||||||
|
private int xMin;
|
||||||
|
/** Maximum x position of a table to crop unused space. */
|
||||||
|
private int xMax;
|
||||||
|
/** Minimum y position of a table to crop unused space. */
|
||||||
|
private int yMin;
|
||||||
|
/** Maximum y position of a table to crop unused space. */
|
||||||
|
private int yMax;
|
||||||
|
/** The expected width of the container in pixels. */
|
||||||
|
private int containerWidth;
|
||||||
|
/** The expected height of the container in pixels. */
|
||||||
|
private int containerHeight;
|
||||||
|
/** The minimum margin for the container, top, right, bottom, left. */
|
||||||
|
private int[] margin;
|
||||||
|
/** The actual margin to add to center the floor in its container. */
|
||||||
|
private int marginLeft;
|
||||||
|
/** The actual margin to add to center the floor in its container. */
|
||||||
|
private int marginTop;
|
||||||
|
private float coordFactor;
|
||||||
|
/** Whether the size and margins should be recomputed or not. */
|
||||||
|
private boolean dirty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a stretcher to compute coordinate with the given GUI settings.
|
||||||
|
* @param buttonWidth The width in pixel of a button.
|
||||||
|
* @param buttonHeight The height in pixel of a button.
|
||||||
|
* @param margin The minimum margin between the border of the container
|
||||||
|
* and a button.
|
||||||
|
*/
|
||||||
|
public CoordStretcher(int buttonWidth, int buttonHeight, int margin) {
|
||||||
|
this(buttonWidth, buttonHeight, margin, margin, margin, margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a stretcher to compute coordinate with the given GUI settings.
|
||||||
|
* @param buttonWidth The width in pixel of a button.
|
||||||
|
* @param buttonHeight The height in pixel of a button.
|
||||||
|
* @param marginTop The minimum margin between the border of the container
|
||||||
|
* and a button.
|
||||||
|
* @param marginRight The minimum margin between the border of the container
|
||||||
|
* and a button.
|
||||||
|
* @param marginBottom The minimum margin between the border of the container
|
||||||
|
* and a button.
|
||||||
|
* @param marginLeft The minimum margin between the border of the container
|
||||||
|
* and a button.
|
||||||
|
*/
|
||||||
|
public CoordStretcher(int buttonWidth, int buttonHeight, int marginTop,
|
||||||
|
int marginRight, int marginBottom, int marginLeft) {
|
||||||
|
this.buttonWidth = buttonWidth;
|
||||||
|
this.buttonHeight = buttonHeight;
|
||||||
|
this.margin = new int[] {marginTop, marginRight, marginBottom, marginLeft};
|
||||||
|
this.xMin = Integer.MAX_VALUE;
|
||||||
|
this.xMax = Integer.MIN_VALUE;
|
||||||
|
this.yMin = Integer.MAX_VALUE;
|
||||||
|
this.yMax = Integer.MIN_VALUE;
|
||||||
|
this.coordFactor = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the local coordinates to contains the given location.
|
||||||
|
* The coordinates will be recomputed by removing the unused space
|
||||||
|
* on the edges.
|
||||||
|
* @param x The x coordinate of the location. It should match the left side
|
||||||
|
* of a button.
|
||||||
|
* @param y The y coordinate of the location. It should match the top side
|
||||||
|
* of a button.
|
||||||
|
*/
|
||||||
|
public void extendBoundsFor(int x, int y) {
|
||||||
|
if (x < this.xMin) {
|
||||||
|
this.xMin = x;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
if (x > this.xMax) {
|
||||||
|
this.xMax = x;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
if (y < this.yMin) {
|
||||||
|
this.yMin = y;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
if (y > this.yMax) {
|
||||||
|
this.yMax = y;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate the size of the container. The content will be stretched
|
||||||
|
* to fit in this size.
|
||||||
|
* @param width The width in pixel of the container, including margin.
|
||||||
|
* @param height The height in pixel of the container, including margin.
|
||||||
|
*/
|
||||||
|
public void setContainerSize(int width, int height) {
|
||||||
|
this.containerWidth = width;
|
||||||
|
this.containerHeight = height;
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute the stretching factor to fill the container while respecting
|
||||||
|
* the aspect ratio.
|
||||||
|
*/
|
||||||
|
private void recomputeRatio() {
|
||||||
|
int contentWidth = this.containerWidth - this.margin[3] - this.margin[1];
|
||||||
|
int contentHeight = this.containerHeight - this.margin[0] - this.margin[2];
|
||||||
|
int floorWidth = this.xMax - this.xMin;
|
||||||
|
int floorHeight = this.yMax - this.yMin;
|
||||||
|
this.coordFactor = Math.min(
|
||||||
|
Float.valueOf(contentWidth - this.buttonWidth) / floorWidth,
|
||||||
|
Float.valueOf(contentHeight - this.buttonHeight) / floorHeight);
|
||||||
|
this.marginLeft = Double.valueOf(Math.floor((contentWidth - (floorWidth * coordFactor + this.buttonWidth)) / 2)).intValue() + this.margin[3];
|
||||||
|
this.marginTop = Double.valueOf(Math.floor((contentHeight - (floorHeight * coordFactor + this.buttonHeight)) / 2)).intValue() + this.margin[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute new coordinates to use most space inside the container.
|
||||||
|
* @param x The x coordinate. Often the coordinate of a Place.
|
||||||
|
* @param y The y coordinate. Often the coordiante of a Place.
|
||||||
|
* @return The new stretched coordinates
|
||||||
|
*/
|
||||||
|
public Point stretchCoord(int x, int y) {
|
||||||
|
if (this.containerWidth == 0 || this.containerHeight == 0) {
|
||||||
|
return new Point(x, y);
|
||||||
|
}
|
||||||
|
if (this.dirty) {
|
||||||
|
this.recomputeRatio();
|
||||||
|
this.dirty = false;
|
||||||
|
}
|
||||||
|
int localX = x - this.xMin;
|
||||||
|
int localY = y - this.yMin;
|
||||||
|
int posX = Double.valueOf(Math.floor(localX * this.coordFactor)).intValue() + this.marginLeft;
|
||||||
|
int posY = Double.valueOf(Math.floor(localY * this.coordFactor)).intValue() + this.marginTop;
|
||||||
|
return new Point(posX, posY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A coordinate couple.
|
||||||
|
*/
|
||||||
|
public class Point {
|
||||||
|
/** The X coordinate. */
|
||||||
|
public int x;
|
||||||
|
/** The Y coordinate. */
|
||||||
|
public int y;
|
||||||
|
/**
|
||||||
|
* Create a new Point
|
||||||
|
* @param x The x coordinate.
|
||||||
|
* @param y The y coordinate.
|
||||||
|
*/
|
||||||
|
public Point(int x, int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/main/java/org/pasteque/common/view/Formatter.java
Normal file
141
src/main/java/org/pasteque/common/view/Formatter.java
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
package org.pasteque.common.view;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.common.model.Currency;
|
||||||
|
import org.pasteque.coreutil.price.Price2;
|
||||||
|
import org.pasteque.coreutil.price.Price5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class to display various values in a user-friendly format.
|
||||||
|
*/
|
||||||
|
public class Formatter
|
||||||
|
{
|
||||||
|
/** See {@link formatPercent(double). */
|
||||||
|
private NumberFormat percentFormat;
|
||||||
|
private DateFormat dateFormat;
|
||||||
|
private DateFormat timeFormat;
|
||||||
|
private DateFormat dateTimeFormat;
|
||||||
|
/** The default currency to use for prices. */
|
||||||
|
private Currency defaultCurrency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a formatter.
|
||||||
|
* @param defaultCurrency The currency to use by default to format prices.
|
||||||
|
* Null for no formatting.
|
||||||
|
*/
|
||||||
|
public Formatter(Currency defaultCurrency) {
|
||||||
|
this.percentFormat = new DecimalFormat("#,##0.##%");
|
||||||
|
this.dateFormat = DateFormat.getDateInstance();
|
||||||
|
this.timeFormat = DateFormat.getTimeInstance();
|
||||||
|
this.dateTimeFormat = DateFormat.getDateTimeInstance();
|
||||||
|
this.defaultCurrency = defaultCurrency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a rate as a percent.
|
||||||
|
* @param rate The rate to convert.
|
||||||
|
* @return The formatted percent with either no decimals or 2 decimals.
|
||||||
|
*/
|
||||||
|
public String formatPercent(double rate) {
|
||||||
|
double value = rate * 100.00;
|
||||||
|
long alldigits = Math.round(Math.abs(rate) * 100000.0);
|
||||||
|
boolean showDigits = (alldigits % 1000 >= 5);
|
||||||
|
if (showDigits) {
|
||||||
|
this.percentFormat.setMinimumFractionDigits(0);
|
||||||
|
this.percentFormat.setMaximumFractionDigits(0);
|
||||||
|
} else {
|
||||||
|
this.percentFormat.setMinimumFractionDigits(2);
|
||||||
|
this.percentFormat.setMaximumFractionDigits(2);
|
||||||
|
}
|
||||||
|
return this.percentFormat.format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatNumber(double value, int decimals) {
|
||||||
|
NumberFormat format = new DecimalFormat("#,##0.##");
|
||||||
|
format.setMaximumFractionDigits(decimals);
|
||||||
|
format.setMinimumFractionDigits(decimals);
|
||||||
|
return format.format(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a price with 2 decimals according to the default currency.
|
||||||
|
* @param price The price to format.
|
||||||
|
* @return The formatted price as text.
|
||||||
|
*/
|
||||||
|
public String formatPrice(Price2 price) {
|
||||||
|
if (this.defaultCurrency == null) {
|
||||||
|
return this.formatNumber(price.toDouble(), 2);
|
||||||
|
}
|
||||||
|
return this.defaultCurrency.formatValue(price.toDouble(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a price with 2 decimals.
|
||||||
|
* @param price The price to format.
|
||||||
|
* @param currency The currency of the price. When null, format
|
||||||
|
* as a plain number with 2 decimals.
|
||||||
|
* @return The formatted price as text.
|
||||||
|
*/
|
||||||
|
public String formatPrice(Price2 price, Currency currency) {
|
||||||
|
if (currency == null) {
|
||||||
|
return this.formatNumber(price.toDouble(), 2);
|
||||||
|
}
|
||||||
|
return currency.formatValue(price.toDouble(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a price with 5 decimals according to the default currency.
|
||||||
|
* @param price The price to format.
|
||||||
|
* @return The formatted price as text.
|
||||||
|
*/
|
||||||
|
public String formatPrice(Price5 price) {
|
||||||
|
if (this.defaultCurrency == null) {
|
||||||
|
return this.formatNumber(price.toDouble(), 5);
|
||||||
|
}
|
||||||
|
return this.defaultCurrency.formatValue(price.toDouble(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a price with 5 decimals.
|
||||||
|
* @param price The price to format.
|
||||||
|
* @param currency The currency of the price. When null, format
|
||||||
|
* as a plain number with 5 decimals.
|
||||||
|
* @return The formatted price as text.
|
||||||
|
*/
|
||||||
|
public String formatPrice(Price5 price, Currency currency) {
|
||||||
|
if (currency == null) {
|
||||||
|
return this.formatNumber(price.toDouble(), 5);
|
||||||
|
}
|
||||||
|
return currency.formatValue(price.toDouble(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date.
|
||||||
|
* @param date The date to print.
|
||||||
|
* @return The full date in the system locale.
|
||||||
|
*/
|
||||||
|
public String formatDate(Date date) {
|
||||||
|
return this.dateFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a time.
|
||||||
|
* @param date The date to print the time from.
|
||||||
|
* @return The time in the system locale.
|
||||||
|
*/
|
||||||
|
public String formatTime(Date date) {
|
||||||
|
return this.timeFormat.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date with time.
|
||||||
|
* @param dateTime The date to print.
|
||||||
|
* @return The date and time in the system locale.
|
||||||
|
*/
|
||||||
|
public String formatDateTime(Date dateTime) {
|
||||||
|
return this.dateTimeFormat.format(dateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main/java/org/pasteque/common/view/package-info.java
Normal file
10
src/main/java/org/pasteque/common/view/package-info.java
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* <p>Utilities to define views and widgets. This package contains tools
|
||||||
|
* to present data and ease the creation of widgets or components.
|
||||||
|
* As views are system-dependent, this package will not contain GUI
|
||||||
|
* components directly, but utilities to ease the presentation and
|
||||||
|
* provide a consistent behaviour between implementations.</p>
|
||||||
|
* <p>Widgets should restrict themselves to the view layer and rely as
|
||||||
|
* much as possible on this package to get data.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.common.view;
|
||||||
111
src/main/java/org/pasteque/coreutil/ImmutableList.java
Normal file
111
src/main/java/org/pasteque/coreutil/ImmutableList.java
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package org.pasteque.coreutil;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable proxy-class of a list.
|
||||||
|
* @param <T> The type of content of the list.
|
||||||
|
*/
|
||||||
|
public final class ImmutableList<T> implements Iterable<T>, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 4982637247874555568L;
|
||||||
|
|
||||||
|
/** See {@link get(int)} or {@link iterator()}. */
|
||||||
|
private List<T> content;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an empty immutable list.
|
||||||
|
*/
|
||||||
|
public ImmutableList() {
|
||||||
|
this.content = new ArrayList<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an immutable list from an existing collection.
|
||||||
|
* The references are copied, modifying the initial list
|
||||||
|
* will not change the content of this list.
|
||||||
|
* @param list The original list to make an immutable copy from.
|
||||||
|
* If null, an empty list is created.
|
||||||
|
*/
|
||||||
|
public ImmutableList(Collection<T> list) {
|
||||||
|
if (list == null) {
|
||||||
|
this.content = new ArrayList<T>();
|
||||||
|
} else {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] copy = (T[]) list.toArray();
|
||||||
|
this.content = Arrays.asList(copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an immutable list from an existing array.
|
||||||
|
* The references are copied, modifying the initial array
|
||||||
|
* will not change the content of this list.
|
||||||
|
* @param elements The original array to make an immutable copy from.
|
||||||
|
* If null, an empty list is created.
|
||||||
|
*/
|
||||||
|
public ImmutableList(T[] elements) {
|
||||||
|
if (elements == null) {
|
||||||
|
this.content = new ArrayList<T>();
|
||||||
|
} else {
|
||||||
|
T[] copy = Arrays.copyOf(elements, elements.length);
|
||||||
|
this.content = Arrays.asList(copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link java.util.List#get(int)}.
|
||||||
|
* @param index The index of the element.
|
||||||
|
* @return The element at the given index.
|
||||||
|
*/
|
||||||
|
public T get(int index) {
|
||||||
|
return this.content.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link java.util.List#size()}.
|
||||||
|
* @return The size of the list.
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return this.content.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link java.lang.Iterable#forEach(java.util.function.Consumer)}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void forEach(Consumer<? super T> action) {
|
||||||
|
this.content.forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link java.util.List#iterator()}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return this.content.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link java.util.List#spliterator()}.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Spliterator<T> spliterator() {
|
||||||
|
return this.content.spliterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return this.content.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/main/java/org/pasteque/coreutil/ParseException.java
Normal file
26
src/main/java/org/pasteque/coreutil/ParseException.java
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.pasteque.coreutil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data cannot be parsed.
|
||||||
|
*/
|
||||||
|
public class ParseException extends Exception
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 3552338953002779225L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a generic exception with only a message.
|
||||||
|
* @param message {@inheritDoc}.
|
||||||
|
*/
|
||||||
|
public ParseException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an exception caused by a more specific exception.
|
||||||
|
* @param message {@inheritDoc}.
|
||||||
|
* @param cause The more specific exception.
|
||||||
|
*/
|
||||||
|
public ParseException(String message, Exception cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Rule to check from where the discount applies itself. It defines where the
|
||||||
|
* rounding will happen first.</p>
|
||||||
|
*/
|
||||||
|
public enum DiscountTarget
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* <p>Apply the discount to the unit price without taxes.</p>
|
||||||
|
* <p>When applied to a line, the unit price is reduced, the unit price
|
||||||
|
* with taxes is recomputed from there.</p>
|
||||||
|
* <p>When applied to an order, the discount is added to the one
|
||||||
|
* from the line.</p>
|
||||||
|
*/
|
||||||
|
UNIT("unit"),
|
||||||
|
/**
|
||||||
|
* <p>Apply the discount to the unit price with taxes.
|
||||||
|
* The discount will not affect fixed tax amounts and will apply a
|
||||||
|
* greater discount rate to the base price to compensate them.</p>
|
||||||
|
* <p>When applied to a line, the unit price with taxes is reduced, which
|
||||||
|
* will result in a new unit price without taxes on 5 decimals.</p>
|
||||||
|
* <p>When applied to an order, the discount is added to the one
|
||||||
|
* from the line.</p>
|
||||||
|
*/
|
||||||
|
UNIT_TAXED("unitTaxed"),
|
||||||
|
/**
|
||||||
|
* <p>Target for a discount applied to the non-taxed total.</p>
|
||||||
|
* <p>When applied to a line, the total without tax is reduced, the total
|
||||||
|
* with taxes is recomputed from there.</p>
|
||||||
|
* <p>When applied to an order, the total without tax is reduced and the
|
||||||
|
* amount is also dispatched into each line with a precision of 5 decimal.
|
||||||
|
* Thus each line will also have a total on 5 decimals. The taxed prices
|
||||||
|
* are recomputed from there and may introduce some unavoidable rounding
|
||||||
|
* approximations in the base amounts for each tax.</p>
|
||||||
|
*/
|
||||||
|
TOTAL("total"),
|
||||||
|
/**
|
||||||
|
* <p>Target for a discount applied to the taxed total.
|
||||||
|
* The discount will not affect fixed tax amounts and will apply a
|
||||||
|
* greater discount rate to the base price to compensate them.</p>
|
||||||
|
* <p>When applied to a line, the total with tax is reduced, which will
|
||||||
|
* result in new total without taxes on 5 decimals.</p>
|
||||||
|
* <p>When applied to an order, the total with taxes is reduced. New total
|
||||||
|
* without taxes are recomputed from there and may introduce some unavoidable
|
||||||
|
* rounding approximations.</p>
|
||||||
|
*/
|
||||||
|
TOTAL_TAXED("totalTaxed");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static final DiscountTarget fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (DiscountTarget v : DiscountTarget.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
private DiscountTarget(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public final String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types for discounts. The type defines which rule is used to compute the
|
||||||
|
* discount.
|
||||||
|
*/
|
||||||
|
public enum DiscountType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Type for a discount relative to the price.
|
||||||
|
*/
|
||||||
|
RATE("rate"),
|
||||||
|
/**
|
||||||
|
* Type for a discount of a fixed amount.
|
||||||
|
*/
|
||||||
|
VALUE("value");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static final DiscountType fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (DiscountType v : DiscountType.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
private DiscountType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public final String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types for fiscal tickets. Fiscal tickets can contain multiple kind
|
||||||
|
* of tickets in immutable raw format. The type is somehow like a
|
||||||
|
* MIME type.
|
||||||
|
*/
|
||||||
|
public enum FiscalTicketType
|
||||||
|
{
|
||||||
|
/** Type for regular tickets, for a single transaction. Code {@code tkt}. */
|
||||||
|
TICKET("tkt"),
|
||||||
|
/**
|
||||||
|
* Type for aggregated tickets, one for each cash session with all
|
||||||
|
* the consolidated figures. Code {@code z}.
|
||||||
|
*/
|
||||||
|
Z_TICKET("z");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static final FiscalTicketType fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (FiscalTicketType v : FiscalTicketType.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
private FiscalTicketType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public final String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Payment mode type enumeration.</p>
|
||||||
|
* <p>Payment mode are using flags to determine if special behaviors or checks
|
||||||
|
* should be triggered when the payment mode is used. But theses flags are not
|
||||||
|
* Independent and only a limited set of values make sense.</p>
|
||||||
|
* <p>This enum lists the common values for payment mode types.</p>
|
||||||
|
*/
|
||||||
|
public enum PaymentModeType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The payment mode does not have special effects.
|
||||||
|
* Code: {@code 0}.
|
||||||
|
*/
|
||||||
|
DEFAULT(0),
|
||||||
|
/**
|
||||||
|
* The payment mode requires a customer account to be used.
|
||||||
|
* Code: {@code 1}.
|
||||||
|
*/
|
||||||
|
CUSTOMER(1),
|
||||||
|
/**
|
||||||
|
* The payment mode requires a customer account, uses their balance
|
||||||
|
* and allows to contract debt (negative balance).
|
||||||
|
* Code: {@code 3}.
|
||||||
|
*/
|
||||||
|
DEBT(3),
|
||||||
|
/**
|
||||||
|
* The payment mode requires a customer account, uses their balance
|
||||||
|
* but do not allow them to contract debt (keep a positive or zero balance).
|
||||||
|
* Code: {@code 5}.
|
||||||
|
*/
|
||||||
|
PREPAID(5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit that indicates that the payment mode requires a customer account
|
||||||
|
* to be used.
|
||||||
|
* Code: {@code 0x01}.
|
||||||
|
* @see requiresCustomer()
|
||||||
|
*/
|
||||||
|
public static final int FLAG_CUSTOMER = 0x01;
|
||||||
|
/**
|
||||||
|
* Bit that indicates that the payment mode will use the customer's balance,
|
||||||
|
* and allows the customer to contract debt.
|
||||||
|
* Code: {@code 0x02}.
|
||||||
|
* @see allowsDebt()
|
||||||
|
*/
|
||||||
|
public static final int FLAG_DEBT = 0x02;
|
||||||
|
/**
|
||||||
|
* Bit that indicates that the payment mode will use the customer's balance,
|
||||||
|
* but not allow the customer to contract debt.
|
||||||
|
* Code: {@code 0x04}.
|
||||||
|
* {@see allowsPrepaid()}
|
||||||
|
*/
|
||||||
|
public static final int FLAG_PREPAID = 0x04;
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static PaymentModeType fromCode(int code) throws IllegalArgumentException {
|
||||||
|
for (PaymentModeType v : PaymentModeType.values()) {
|
||||||
|
if (v.getCode() == code) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(Integer.valueOf(code).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
PaymentModeType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether payment modes of this type requires a customer account.
|
||||||
|
* @return True when the type has the {@link FLAG_CUSTOMER} flag set.
|
||||||
|
*/
|
||||||
|
public boolean requiresCustomer() {
|
||||||
|
return (this.code & FLAG_CUSTOMER) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether payment modes of this type allows to contract debt.
|
||||||
|
* @return True when the type has the {@link FLAG_DEBT} flag set.
|
||||||
|
*/
|
||||||
|
public boolean allowsDebt() {
|
||||||
|
return (this.code & FLAG_DEBT) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether payment modes of this type uses the customer's positive balance.
|
||||||
|
* @return True when the type has the {@link FLAG_PREPAID}
|
||||||
|
* or {@link FLAG_DEBT} flag set.
|
||||||
|
*/
|
||||||
|
public boolean allowPrepaid() {
|
||||||
|
return (this.code & (FLAG_PREPAID | FLAG_DEBT)) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of period to close when closing a session.
|
||||||
|
* TODO: experimental
|
||||||
|
*/
|
||||||
|
public enum SessionCloseType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Close only the current session which is commonly a day of work.
|
||||||
|
* Code {@code 0}.
|
||||||
|
*/
|
||||||
|
SIMPLE(0),
|
||||||
|
/**
|
||||||
|
* Close the current session and the current period,
|
||||||
|
* which is commonly a month. Code {@code 1}.
|
||||||
|
*/
|
||||||
|
PERIOD(1),
|
||||||
|
/**
|
||||||
|
* Close the current session and the fiscal year, which is commonly a year.
|
||||||
|
* Code {@code 2}.
|
||||||
|
*/
|
||||||
|
FISCAL_YEAR(2);
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static SessionCloseType fromCode(int code) throws IllegalArgumentException {
|
||||||
|
for (SessionCloseType v : SessionCloseType.values()) {
|
||||||
|
if (v.getCode() == code) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(String.valueOf(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
SessionCloseType(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
52
src/main/java/org/pasteque/coreutil/constants/TaxType.java
Normal file
52
src/main/java/org/pasteque/coreutil/constants/TaxType.java
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types for taxes. The type defines which rule is used to compute tax amounts
|
||||||
|
* from a base amount.
|
||||||
|
*/
|
||||||
|
public enum TaxType
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Type for rate applied to a base amount.
|
||||||
|
*/
|
||||||
|
RATE("rate"),
|
||||||
|
/**
|
||||||
|
* Type for a fixed amount applied to each unit.
|
||||||
|
*/
|
||||||
|
FIXED_QUANTITY("fixedQuantity");
|
||||||
|
|
||||||
|
/** {@see getCode()} */
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from it's code.
|
||||||
|
* @param code The code value.
|
||||||
|
* @return The according enumeration value.
|
||||||
|
* @throws IllegalArgumentException When code is not found
|
||||||
|
* within the enumerated values
|
||||||
|
*/
|
||||||
|
public static final TaxType fromCode(String code) throws IllegalArgumentException {
|
||||||
|
for (TaxType v : TaxType.values()) {
|
||||||
|
if (v.getCode().equals(code)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal constructor.
|
||||||
|
* @param code See {@link getCode()}.
|
||||||
|
*/
|
||||||
|
private TaxType(String code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the associated constant.
|
||||||
|
* @return The code for DTO.
|
||||||
|
*/
|
||||||
|
public final String getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
/**
|
||||||
|
* Core feature constants and enumerations.
|
||||||
|
*/
|
||||||
|
package org.pasteque.coreutil.constants;
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.InvalidFieldException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Physical cash register hardware Data Transfer Object.</p>
|
||||||
|
* <p>All tickets must be assigned to a cash register and a cash session.</p>
|
||||||
|
*/
|
||||||
|
public final class CashRegisterDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1079581813013284003L;
|
||||||
|
|
||||||
|
/** {@see getReference()} */
|
||||||
|
private String reference;
|
||||||
|
|
||||||
|
/** {@see getLabel()} */
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
/** {@see getNextTicketNumber()} */
|
||||||
|
private int nextTicketNumber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from all fields.
|
||||||
|
* @param reference The unique reference.
|
||||||
|
* @param label The display name.
|
||||||
|
* @param nextTicketNumber See {@link getNextTicketNumber}.
|
||||||
|
*/
|
||||||
|
public CashRegisterDTO(
|
||||||
|
String reference,
|
||||||
|
String label,
|
||||||
|
int nextTicketNumber) {
|
||||||
|
this.reference = reference;
|
||||||
|
this.label = label;
|
||||||
|
this.nextTicketNumber = nextTicketNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public CashRegisterDTO(Reader reader) throws ParseException {
|
||||||
|
this.reference = reader.readString("reference");
|
||||||
|
this.label = reader.readString("label");
|
||||||
|
this.nextTicketNumber = reader.readInt("nextTicketId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference. It is a user friendly identifier.
|
||||||
|
* @return The reference.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return this.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display name.
|
||||||
|
* @return The label of this cash register.
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number to assign to the next ticket from the situation known by the sender.
|
||||||
|
* It may not be solely accurate when other tickets are stored locally.
|
||||||
|
* @return The number to assign to the next ticket.
|
||||||
|
*/
|
||||||
|
public int getNextTicketNumber() {
|
||||||
|
return nextTicketNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the reference and labels are set and nextTicketNumber is
|
||||||
|
* positive or zero.
|
||||||
|
* @throws IntegrityExceptions When the constraints are not satisfied.
|
||||||
|
*/
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
List<IntegrityException> list = new LinkedList<IntegrityException>();
|
||||||
|
IntegrityExceptions.addCheck(list, InvalidFieldException.nonNull(
|
||||||
|
this.reference, "CashRegister", "reference", this.reference));
|
||||||
|
IntegrityExceptions.addCheck(list, InvalidFieldException.nonNull(
|
||||||
|
this.label, "CashRegister", "label", this.reference));
|
||||||
|
IntegrityExceptions.addCheck(list, InvalidFieldException.positive(
|
||||||
|
this.nextTicketNumber, "CashRegister", "nextTicketNumber", this.reference));
|
||||||
|
if (!list.isEmpty()) {
|
||||||
|
throw new IntegrityExceptions(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.DTOFactory;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Non finished cash session Data Transfer Object.</p>
|
||||||
|
* <p>There can be only one active session for each
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.dto.CashRegisterDTO}. Once a session is
|
||||||
|
* closed, a {@link org.pasteque.coreutil.datatransfer.dto.FiscalTicketDTO} is created
|
||||||
|
* and the next session is created in non-opened state.</p>
|
||||||
|
*/
|
||||||
|
public final class CashSessionDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 2312930086844551950L;
|
||||||
|
|
||||||
|
/** See {@link getCashRegister()}. */
|
||||||
|
private WeakAssociationDTO cashRegister;
|
||||||
|
/** See {@link getSequence()}. */
|
||||||
|
private int sequence;
|
||||||
|
/** See {@link isContinuous()}. */
|
||||||
|
private boolean continuous;
|
||||||
|
/** See {@link getOpenDate()}. */
|
||||||
|
private Date openDate;
|
||||||
|
/** See {@link getMovements()}. */
|
||||||
|
private ImmutableList<MovementDTO> movements;
|
||||||
|
/** See {@link getTickets()}. */
|
||||||
|
private ImmutableList<FiscalTicketDTO> tickets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a cash session from all fields.
|
||||||
|
* @param cashRegister See {@link getCashRegister()}.
|
||||||
|
* @param sequence See {@link getSequence()}.
|
||||||
|
* @param continuous See {@link isContinuous()}.
|
||||||
|
* @param openDate See {@link getOpenDate()}.
|
||||||
|
* @param movements See {@link getMovements()}.
|
||||||
|
* @param tickets See {@link getTickets()}.
|
||||||
|
*/
|
||||||
|
public CashSessionDTO(
|
||||||
|
WeakAssociationDTO cashRegister,
|
||||||
|
int sequence,
|
||||||
|
boolean continuous,
|
||||||
|
Date openDate,
|
||||||
|
ImmutableList<MovementDTO> movements,
|
||||||
|
ImmutableList<FiscalTicketDTO> tickets) {
|
||||||
|
this.cashRegister = cashRegister;
|
||||||
|
this.sequence = sequence;
|
||||||
|
this.continuous = continuous;
|
||||||
|
this.openDate = openDate;
|
||||||
|
this.movements = movements;
|
||||||
|
this.tickets = tickets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public CashSessionDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("cashRegister");
|
||||||
|
this.cashRegister = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.sequence = reader.readInt("sequence");
|
||||||
|
this.continuous = reader.readBoolean("continuous");
|
||||||
|
this.openDate = reader.readDateOrNull("openDate");
|
||||||
|
DTOFactory<MovementDTO> mvtFacto = new DTOFactory<MovementDTO>(reader, MovementDTO.class);
|
||||||
|
this.movements = mvtFacto.immutableReadObjects("movements");
|
||||||
|
DTOFactory<FiscalTicketDTO> tktFacto = new DTOFactory<FiscalTicketDTO>(reader, FiscalTicketDTO.class);
|
||||||
|
this.tickets = tktFacto.immutableReadObjects("tickets");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cash register associated to this session.
|
||||||
|
* @return The cash register. It may not link to an existing cash register.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCashRegister() {
|
||||||
|
return this.cashRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sequence number of this session. The sequence starts from 1
|
||||||
|
* and is increased every time a new session is opened. There can be only
|
||||||
|
* one active (i.e. not closed) session for a cash register at a time.
|
||||||
|
* @return The sequence number of this session.
|
||||||
|
*/
|
||||||
|
public int getSequence() {
|
||||||
|
return this.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the session was continuous starting from the previous one.
|
||||||
|
* The session is continuous when it started on the same machine than
|
||||||
|
* the previous one and with the previous one in cache.
|
||||||
|
* @return True when the session is marked as continuous.
|
||||||
|
* @see org.pasteque.coreutil.datatransfer.dto.ZTicketDTO#isContinuous()
|
||||||
|
*/
|
||||||
|
public boolean isContinuous() {
|
||||||
|
return this.continuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the date when the session was opened.
|
||||||
|
* @return The open date. It may be null if the session was not opened yet.
|
||||||
|
* @see org.pasteque.coreutil.datatransfer.dto.ZTicketDTO#getOpenDate()
|
||||||
|
*/
|
||||||
|
public Date getOpenDate() {
|
||||||
|
return this.openDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the movements not justified by a ticket, including the starting
|
||||||
|
* amounts.
|
||||||
|
* @return The movements. Empty when not counted.
|
||||||
|
* @see org.pasteque.coreutil.datatransfer.dto.ZTicketDTO#getMovements()
|
||||||
|
*/
|
||||||
|
public ImmutableList<MovementDTO> getMovements() {
|
||||||
|
return this.movements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of tickets currently registered within this session.
|
||||||
|
* @return The tickets registered within this session.
|
||||||
|
*/
|
||||||
|
public ImmutableList<FiscalTicketDTO> getTickets() {
|
||||||
|
return this.tickets;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
/*import org.pasteque.common.dto.parser.Writer;*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Common interface for DTO.</p>
|
||||||
|
* <p>All DTO must have a constructor from a
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.parser.Reader}
|
||||||
|
* and some data.</p>
|
||||||
|
*/
|
||||||
|
public interface DTOInterface
|
||||||
|
{
|
||||||
|
/* public DTOImplementation(Reader reader) throws ParseException */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the DTO is well formed, have all required data
|
||||||
|
* and fulfill other integrity constraints.
|
||||||
|
* @throws IntegrityExceptions When at least one
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.integrity.IntegrityException}
|
||||||
|
* is thrown.
|
||||||
|
*/
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions;
|
||||||
|
|
||||||
|
/*public String encode(Writer writer);*/
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Immutable and signed ticket records. Fiscal tickets are used to comply
|
||||||
|
* with the immutability and integrity check of the underlying ticket.</p>
|
||||||
|
* <p>Fiscal tickets contains a ticket in raw format to stay immutable even
|
||||||
|
* after updates. The ticket is stored in the {@link getContent() content} and
|
||||||
|
* signed with the previous one.</p>
|
||||||
|
* <p>The sequence, number and date of the fiscal ticket can differ from
|
||||||
|
* the one of the ticket in content. Fiscal tickets are stored in independent
|
||||||
|
* sequences, usually matching a cash register, with incremental number.
|
||||||
|
* The date of the fiscal ticket is the date of registration of the fiscal
|
||||||
|
* ticket, which may differ from the date of the ticket in content.</p>
|
||||||
|
* <p>Number 0 is reserved for the special and only mutable fiscal ticket for
|
||||||
|
* the "End Of Sequence" (EOS). This fiscal ticket is always signed with the
|
||||||
|
* last fiscal ticket of the sequence and allows to check if a sequence is
|
||||||
|
* complete.</p>
|
||||||
|
* <p>The signature is chained to the previous fiscal ticket in the same
|
||||||
|
* sequence. The signature is computed based upon the sequence, number and
|
||||||
|
* content and the signature of the previous fiscal ticket in the same sequence.</p>
|
||||||
|
* <p>For tickets that can be read easily,
|
||||||
|
* see {@link org.pasteque.coreutil.datatransfer.dto.TicketDTO} and
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.dto.ZTicketDTO}.
|
||||||
|
*/
|
||||||
|
public final class FiscalTicketDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 1622469748625149642L;
|
||||||
|
|
||||||
|
/** See {@link getType()}. */
|
||||||
|
private String type;
|
||||||
|
/** See {@link getSequence()}. */
|
||||||
|
private String sequence;
|
||||||
|
/** See {@link getNumber()}. */
|
||||||
|
private int number;
|
||||||
|
/** See {@link getCreateDate()}. */
|
||||||
|
private Date createDate;
|
||||||
|
/** See {@link getContent()}. */
|
||||||
|
private String content;
|
||||||
|
/** See {@link getSignature()}. */
|
||||||
|
private String signature;
|
||||||
|
/** See {@link getWriteDate()}. */
|
||||||
|
private Date writeDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create from all fields.
|
||||||
|
* @param type The type of ticket.
|
||||||
|
* @param sequence The sequence of the fiscal ticket.
|
||||||
|
* @param number The number of the fiscal ticket.
|
||||||
|
* @param createDate The creation date of the fiscal ticket.
|
||||||
|
* @param content The content of the underlying ticket.
|
||||||
|
* @param signature The chained signature of the record.
|
||||||
|
* @param writeDate The date when the fiscal ticket was received.
|
||||||
|
*/
|
||||||
|
public FiscalTicketDTO(
|
||||||
|
String type,
|
||||||
|
String sequence,
|
||||||
|
int number,
|
||||||
|
Date createDate,
|
||||||
|
String content,
|
||||||
|
String signature,
|
||||||
|
Date writeDate) {
|
||||||
|
this.type = type;
|
||||||
|
this.sequence = sequence;
|
||||||
|
this.number = number;
|
||||||
|
this.createDate = createDate;
|
||||||
|
this.content = content;
|
||||||
|
this.signature = signature;
|
||||||
|
this.writeDate = writeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public FiscalTicketDTO(Reader reader) throws ParseException {
|
||||||
|
this.type = reader.readString("type");
|
||||||
|
this.sequence = reader.readString("sequence");
|
||||||
|
this.number = reader.readInt("number");
|
||||||
|
this.createDate = reader.readDate("date");
|
||||||
|
this.content = reader.readString("content");
|
||||||
|
this.signature = reader.readString("signature");
|
||||||
|
this.writeDate = reader.readDate("writeDate");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of this ticket.
|
||||||
|
* @see org.pasteque.coreutil.constants.FiscalTicketType
|
||||||
|
* @return The type of ticket in content.
|
||||||
|
*/
|
||||||
|
public String getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the identifier of the sequence. A sequence is generally identifying
|
||||||
|
* a cash register.
|
||||||
|
* @return The sequence of this ticket.
|
||||||
|
*/
|
||||||
|
public String getSequence() {
|
||||||
|
return this.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of this fiscal ticket. The number is incremental
|
||||||
|
* within each session and starts from 1.
|
||||||
|
* Number 0 is reserved for the special "End Of Sequence" (EOF) ticket.
|
||||||
|
* @return The number of this ticket.
|
||||||
|
*/
|
||||||
|
public int getNumber() {
|
||||||
|
return this.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the registration date of this fiscal ticket.
|
||||||
|
* It may be different from the date of the ticket in content.
|
||||||
|
* @return The registration date.
|
||||||
|
*/
|
||||||
|
public Date getCreateDate() {
|
||||||
|
return this.createDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the raw representation of the registered ticket.
|
||||||
|
* @return The content of the ticket.
|
||||||
|
*/
|
||||||
|
public String getContent() {
|
||||||
|
return this.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the signature of this fiscal ticket, chained with the previous one.
|
||||||
|
* @return The signature.
|
||||||
|
* @see org.pasteque.major.domain.FiscalTicket#checkSignature(org.pasteque.major.model.FiscalTicket)
|
||||||
|
*/
|
||||||
|
public String getSignature() {
|
||||||
|
return this.signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the registration date of this fiscal ticket.
|
||||||
|
* It may be different from the date of the ticket in content
|
||||||
|
* and is only informational.
|
||||||
|
* @return The registration date.
|
||||||
|
*/
|
||||||
|
public Date getWriteDate() {
|
||||||
|
return this.writeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Change in currency amounts not introduced by the payment of a ticket.
|
||||||
|
* This can be the initial amount in the cash drawer, or movements during
|
||||||
|
* the active session from external reasons, like paying an invoice
|
||||||
|
* or adding or removing cash from the drawer.</p>
|
||||||
|
* <p>Movements are only used to detect errors when closing the session.
|
||||||
|
* They are not exhaustive for accounting.</p>
|
||||||
|
*/
|
||||||
|
public final class MovementDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 2409481140667639418L;
|
||||||
|
|
||||||
|
/** See {@link getPaymentMode()}. */
|
||||||
|
private WeakAssociationDTO paymentMode;
|
||||||
|
/** See {@link getCurrency()}. */
|
||||||
|
private WeakAssociationDTO currency;
|
||||||
|
/** See {@link getCurrencyAmount()}. */
|
||||||
|
private double currencyAmount;
|
||||||
|
/** See {@link getDate()}. */
|
||||||
|
private Date date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a movement from all fields.
|
||||||
|
* @param paymentMode See {@link getPaymentMode()}.
|
||||||
|
* @param currency See {@link getCurrency()}.
|
||||||
|
* @param currencyAmount See {@link getCurrencyAmount()}.
|
||||||
|
* @param date See {@link getDate()}.
|
||||||
|
*/
|
||||||
|
public MovementDTO(
|
||||||
|
WeakAssociationDTO paymentMode,
|
||||||
|
WeakAssociationDTO currency,
|
||||||
|
double currencyAmount,
|
||||||
|
Date date) {
|
||||||
|
this.paymentMode = paymentMode;
|
||||||
|
this.currency = currency;
|
||||||
|
this.currencyAmount = currencyAmount;
|
||||||
|
this.date = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public MovementDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("paymentMode");
|
||||||
|
this.paymentMode = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
reader.startObject("currency");
|
||||||
|
this.currency = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.currencyAmount = reader.readDouble("currencyAmount");
|
||||||
|
this.date = reader.readDate("date");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the payment mode used for this movement.
|
||||||
|
* @return The payment mode. It may not link to an existing payment mode.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getPaymentMode() {
|
||||||
|
return this.paymentMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currency used for this movement.
|
||||||
|
* @return The currency. It may not link to an existing currency.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCurrency() {
|
||||||
|
return this.currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of the movement, in the currency of the movement.
|
||||||
|
* @return The amount of the movement, in the currency of the movement.
|
||||||
|
*/
|
||||||
|
public double getCurrencyAmount() {
|
||||||
|
return this.currencyAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the date when the movement was done.
|
||||||
|
* @return The date when the movement was done.
|
||||||
|
*/
|
||||||
|
public Date getDate() {
|
||||||
|
return this.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Payment amount Data Transfer Object. A payment may refer to a single one
|
||||||
|
* or aggregated data for the same mode and currency.</p>
|
||||||
|
* <p>Used in {@link org.pasteque.coreutil.datatransfer.dto.TicketDTO} for
|
||||||
|
* individual payment in {@link org.pasteque.coreutil.datatransfer.dto.ZTicketDTO}
|
||||||
|
* for aggregated amount by payment mode and currency.</p>
|
||||||
|
*/
|
||||||
|
public final class PaymentDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 3195260108460565293L;
|
||||||
|
|
||||||
|
/** See {@link getPaymentMode()}. */
|
||||||
|
private WeakAssociationDTO paymentMode;
|
||||||
|
/** See {@link getCurrency()}. */
|
||||||
|
private WeakAssociationDTO currency;
|
||||||
|
/** See {@link getAmount()}. */
|
||||||
|
private double amount;
|
||||||
|
/** See {@link getCurrencyAmount()}. */
|
||||||
|
private double currencyAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a payment from all fields.
|
||||||
|
* @param paymentMode See {@link getPaymentMode()}.
|
||||||
|
* @param amount See {@link getAmount()}.
|
||||||
|
* @param currency See {@link getCurrency()}.
|
||||||
|
* @param currencyAmount See {@link getCurrencyAmount()}.
|
||||||
|
*/
|
||||||
|
public PaymentDTO(
|
||||||
|
WeakAssociationDTO paymentMode,
|
||||||
|
double amount,
|
||||||
|
WeakAssociationDTO currency,
|
||||||
|
double currencyAmount) {
|
||||||
|
this.paymentMode = paymentMode;
|
||||||
|
this.amount = amount;
|
||||||
|
this.currency = currency;
|
||||||
|
this.currencyAmount = currencyAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public PaymentDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("paymentMode");
|
||||||
|
this.paymentMode = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.amount = reader.readDouble("amount");
|
||||||
|
reader.startObject("currency");
|
||||||
|
this.currency = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.currencyAmount = reader.readDouble("currencyAmount");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the payment mode used at registration time.
|
||||||
|
* @return The payment mode. It may not link to a registered payment mode.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getPaymentMode() {
|
||||||
|
return this.paymentMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount in main currency for this payment.
|
||||||
|
* @return The amount of this payment in the main currency.
|
||||||
|
*/
|
||||||
|
public double getAmount() {
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currency used at registration time.
|
||||||
|
* @return The currency. It may not link to a registered currency.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCurrency() {
|
||||||
|
return this.currency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount in the currency used for this payment.
|
||||||
|
* @return The amount in the currency of this payment.
|
||||||
|
*/
|
||||||
|
public double getCurrencyAmount() {
|
||||||
|
return this.currencyAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Tax amount Data Transfer Object. A tax amount may refer to a single one
|
||||||
|
* or aggregated data for a single line, a ticket or a Z ticket.</p>
|
||||||
|
* <p>Due to aggregation and rounding, the amount may not be strictly equals
|
||||||
|
* from the base multiplied by the tax rate.</p>
|
||||||
|
* <p>A tax amount is always expressed in the main currency</p>
|
||||||
|
*/
|
||||||
|
public final class TaxAmountDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 7429827880917696693L;
|
||||||
|
|
||||||
|
/** See {@link getTax()}. */
|
||||||
|
private WeakAssociationDTO tax;
|
||||||
|
/** See {@link getTaxRate()}. */
|
||||||
|
private double taxRate;
|
||||||
|
/** See {@link getBase()}. */
|
||||||
|
private double base;
|
||||||
|
/** See {@link getAmount()}. */
|
||||||
|
private double amount;
|
||||||
|
/** See {@link getIncludedInBase()}. */
|
||||||
|
private boolean includedInBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tax from all fields.
|
||||||
|
* @param tax See {@link getTax()}.
|
||||||
|
* @param taxRate See {@link getTaxRate()}.
|
||||||
|
* @param base See {@link getBase()}.
|
||||||
|
* @param amount See {@link getAmount()}.
|
||||||
|
* @param includedInBase See {@link getIncludedInBase()}.
|
||||||
|
*/
|
||||||
|
public TaxAmountDTO(
|
||||||
|
WeakAssociationDTO tax,
|
||||||
|
double taxRate,
|
||||||
|
double base,
|
||||||
|
double amount,
|
||||||
|
boolean includedInBase) {
|
||||||
|
this.tax = tax;
|
||||||
|
this.taxRate = taxRate;
|
||||||
|
this.base = base;
|
||||||
|
this.amount = amount;
|
||||||
|
this.includedInBase = includedInBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TaxAmountDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("tax");
|
||||||
|
this.tax = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.taxRate = reader.readDouble("taxRate");
|
||||||
|
this.base = reader.readDouble("base");
|
||||||
|
this.amount = reader.readDouble("amount");
|
||||||
|
this.includedInBase = reader.readBoolean("includedInBase");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tax at registration time.
|
||||||
|
* @return The tax. It may not link to a registered tax.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getTax() {
|
||||||
|
return this.tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the rate to apply.
|
||||||
|
* @return The tax rate.
|
||||||
|
*/
|
||||||
|
public double getTaxRate() {
|
||||||
|
return this.taxRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base amount associated to this tax, used to compute the amount.
|
||||||
|
* @return The base amount in the main currency.
|
||||||
|
*/
|
||||||
|
public double getBase() {
|
||||||
|
return this.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of taxes collected for this tax.
|
||||||
|
* @return The amount of tax in the main currency.
|
||||||
|
*/
|
||||||
|
public double getAmount() {
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether this tax amount is included in the base or
|
||||||
|
* is added to it.
|
||||||
|
* @return True when the tax amount is included in the base amount.
|
||||||
|
* False when it must be added to it.
|
||||||
|
*/
|
||||||
|
public final boolean getIncludedInBase() {
|
||||||
|
return this.includedInBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tax Data Transfer Object.
|
||||||
|
*/
|
||||||
|
public final class TaxDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 525507510660586665L;
|
||||||
|
/** See {@link getType()}. */
|
||||||
|
private String type;
|
||||||
|
/** See {@link getAmount()}. */
|
||||||
|
private double amount;
|
||||||
|
/** See {@link getIncludedInBase()}. */
|
||||||
|
private boolean includedInBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a tax from all fields.
|
||||||
|
* @param type See {@link getType()}.
|
||||||
|
* @param amount See {@link getAmount()}.
|
||||||
|
* @param includedInBase See {@link getIncludedInBase()}.
|
||||||
|
*/
|
||||||
|
public TaxDTO(
|
||||||
|
String type,
|
||||||
|
double amount,
|
||||||
|
boolean includedInBase) {
|
||||||
|
this.type = type;
|
||||||
|
this.amount = amount;
|
||||||
|
this.includedInBase = includedInBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TaxDTO(Reader reader) throws ParseException {
|
||||||
|
this.type = reader.readString("type");
|
||||||
|
this.amount = reader.readDouble("amount");
|
||||||
|
this.includedInBase = reader.readBoolean("includedInBase");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of the tax.
|
||||||
|
* @return The type of the tax, which determines how amounts are computed.
|
||||||
|
* @see org.pasteque.coreutil.constants.TaxType
|
||||||
|
* @see org.pasteque.coreutil.price.Tax#getType()
|
||||||
|
*/
|
||||||
|
public String getType() {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount used to compute tax amounts.
|
||||||
|
* @return The amount used to compute tax amounts.
|
||||||
|
* @see org.pasteque.coreutil.price.Tax#getAmount()
|
||||||
|
*/
|
||||||
|
public double getAmount() {
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get whether this tax amount is included in the base or
|
||||||
|
* is added to it.
|
||||||
|
* @return True when the tax amount is included in the base amount.
|
||||||
|
* False when it must be added to it.
|
||||||
|
* @see org.pasteque.coreutil.price.Tax#isIncludedInBase()
|
||||||
|
*/
|
||||||
|
public final boolean getIncludedInBase() {
|
||||||
|
return this.includedInBase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,547 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.DTOFactory;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Receipt of a finalized and paid order. Tickets are immutable unless changes
|
||||||
|
* are applied to the data structure. Because of that they cannot be signed.</p>
|
||||||
|
* <p>All data are copied into this DTO to make tickets completely independent.</p>
|
||||||
|
* <p>For completely immutable tickets that can be stored and archived see
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.dto.FiscalTicketDTO}.
|
||||||
|
*/
|
||||||
|
public final class TicketDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 8831777799341373103L;
|
||||||
|
|
||||||
|
/** See {@link getCashRegister()}. */
|
||||||
|
private WeakAssociationDTO cashRegister;
|
||||||
|
/** See {@link getSequence()}. */
|
||||||
|
private int sequence;
|
||||||
|
/** See {@link getNumber()}. */
|
||||||
|
private int number;
|
||||||
|
/** See {@link getDate()}. */
|
||||||
|
private Date date;
|
||||||
|
/** See {@link getUser()}. */
|
||||||
|
private WeakAssociationDTO user;
|
||||||
|
/** See {@link getCustCount()}. */
|
||||||
|
private Integer custCount;
|
||||||
|
/** See {@link getCustomer()}. */
|
||||||
|
private WeakAssociationDTO customer;
|
||||||
|
/** See {@link getCustBalance()}. */
|
||||||
|
private double custBalance;
|
||||||
|
/** See {@link getTariffArea()}. */
|
||||||
|
private WeakAssociationDTO tariffArea;
|
||||||
|
/** See {@link getLines()}. */
|
||||||
|
private ImmutableList<TicketLineDTO> lines;
|
||||||
|
/** See {@link getPayments()}. */
|
||||||
|
private ImmutableList<PaymentDTO> payments;
|
||||||
|
/** See {@link getTaxes()}. */
|
||||||
|
private ImmutableList<TaxAmountDTO> taxes;
|
||||||
|
/** See {@link getPrice()}. */
|
||||||
|
private double price;
|
||||||
|
/** See {@link getTaxedPrice()}. */
|
||||||
|
private double taxedPrice;
|
||||||
|
/** See {@link getDiscountProfile()}. */
|
||||||
|
private WeakAssociationDTO discountProfile;
|
||||||
|
/** See {@link getDiscountRate()}. */
|
||||||
|
private double discountRate;
|
||||||
|
/** See {@link getFinalPrice()}. */
|
||||||
|
private double finalPrice;
|
||||||
|
/** See {@link getFinalTaxedPrice()}. */
|
||||||
|
private double finalTaxedPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ticket from all fields.
|
||||||
|
* @param cashRegister See {@link getCashRegister()}.
|
||||||
|
* @param sequence See {@link getSequence()}.
|
||||||
|
* @param number See {@link getNumber()}.
|
||||||
|
* @param date See {@link getDate()}.
|
||||||
|
* @param user See {@link getUser()}.
|
||||||
|
* @param custCount See {@link getCustCount()}.
|
||||||
|
* @param customer See {@link getCustomer()}.
|
||||||
|
* @param custBalance See {@link getCustBalance()}.
|
||||||
|
* @param tariffArea See {@link getTariffArea()}.
|
||||||
|
* @param lines See {@link getLines()}.
|
||||||
|
* @param payments See {@link getPayments()}.
|
||||||
|
* @param taxes See {@link getTaxes()}.
|
||||||
|
* @param price See {@link getPrice()}.
|
||||||
|
* @param taxedPrice See {@link getTaxedPrice()}.
|
||||||
|
* @param discountProfile See {@link getDiscountProfile()}.
|
||||||
|
* @param discountRate See {@link getDiscountRate()}.
|
||||||
|
* @param finalPrice See {@link getFinalPrice()}.
|
||||||
|
* @param finalTaxedPrice See {@link getFinalTaxedPrice()}.
|
||||||
|
*/
|
||||||
|
public TicketDTO(
|
||||||
|
WeakAssociationDTO cashRegister,
|
||||||
|
int sequence,
|
||||||
|
int number,
|
||||||
|
Date date,
|
||||||
|
WeakAssociationDTO user,
|
||||||
|
Integer custCount,
|
||||||
|
WeakAssociationDTO customer,
|
||||||
|
double custBalance,
|
||||||
|
WeakAssociationDTO tariffArea,
|
||||||
|
TicketLineDTO[] lines,
|
||||||
|
PaymentDTO[] payments,
|
||||||
|
TaxAmountDTO[] taxes,
|
||||||
|
double price,
|
||||||
|
double taxedPrice,
|
||||||
|
WeakAssociationDTO discountProfile,
|
||||||
|
double discountRate,
|
||||||
|
double finalPrice,
|
||||||
|
double finalTaxedPrice) {
|
||||||
|
this.cashRegister = cashRegister;
|
||||||
|
this.sequence = sequence;
|
||||||
|
this.number = number;
|
||||||
|
this.date = date;
|
||||||
|
this.user = user;
|
||||||
|
this.custCount = custCount;
|
||||||
|
this.customer = customer;
|
||||||
|
this.custBalance = custBalance;
|
||||||
|
this.tariffArea = tariffArea;
|
||||||
|
this.lines = new ImmutableList<TicketLineDTO>(lines);
|
||||||
|
this.payments = new ImmutableList<PaymentDTO>(payments);
|
||||||
|
this.taxes = new ImmutableList<TaxAmountDTO>(taxes);
|
||||||
|
this.price = price;
|
||||||
|
this.taxedPrice = taxedPrice;
|
||||||
|
this.discountProfile = discountProfile;
|
||||||
|
this.discountRate = discountRate;
|
||||||
|
this.finalPrice = finalPrice;
|
||||||
|
this.finalTaxedPrice = finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TicketDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("cashRegister");
|
||||||
|
this.cashRegister = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.sequence = reader.readInt("sequence");
|
||||||
|
this.number = reader.readInt("number");
|
||||||
|
this.date = reader.readDate("date");
|
||||||
|
this.user = WeakAssociationDTO.fromReader(reader, "user");
|
||||||
|
this.custCount = reader.readIntOrNull("custCount");
|
||||||
|
this.customer = WeakAssociationDTO.fromReader(reader, "customer");
|
||||||
|
this.custBalance = reader.readDouble("custBalance");
|
||||||
|
this.tariffArea = WeakAssociationDTO.fromReader(reader, "tariffArea");
|
||||||
|
this.price = reader.readDouble("price");
|
||||||
|
this.taxedPrice = reader.readDouble("taxedPrice");
|
||||||
|
this.discountProfile = WeakAssociationDTO.fromReader(reader, "discountProfile");
|
||||||
|
this.discountRate = reader.readDouble("discountRate");
|
||||||
|
this.finalPrice = reader.readDouble("finalPrice");
|
||||||
|
this.finalTaxedPrice = reader.readDouble("finalTaxedPrice");
|
||||||
|
DTOFactory<TicketLineDTO> lineFacto = new DTOFactory<TicketLineDTO>(reader, TicketLineDTO.class);
|
||||||
|
this.lines = lineFacto.immutableReadObjects("lines");
|
||||||
|
DTOFactory<TaxAmountDTO> taxFacto = new DTOFactory<TaxAmountDTO>(reader, TaxAmountDTO.class);
|
||||||
|
this.taxes = taxFacto.immutableReadObjects("taxes");
|
||||||
|
DTOFactory<PaymentDTO> pmtFacto = new DTOFactory<PaymentDTO>(reader, PaymentDTO.class);
|
||||||
|
this.payments = pmtFacto.immutableReadObjects("payments");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique reference for this ticket.
|
||||||
|
* @return The reference, formatted as {@code <cash register>-<sequence>-<number>}.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return String.format("%d-%d-%d",
|
||||||
|
this.cashRegister,
|
||||||
|
this.sequence,
|
||||||
|
this.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the cash register.
|
||||||
|
* @return The id of the cash register.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCashRegister() {
|
||||||
|
return this.cashRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sequence of the cash register.
|
||||||
|
* @return The sequence of the cash register in which this ticket
|
||||||
|
* was created.
|
||||||
|
*/
|
||||||
|
public int getSequence() {
|
||||||
|
return this.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of the ticket.
|
||||||
|
* @return The unique number of the ticket for the cash register.
|
||||||
|
*/
|
||||||
|
public int getNumber() {
|
||||||
|
return this.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the date of creation (payment) of the ticket.
|
||||||
|
* @return The date when this ticket was paid and created.
|
||||||
|
*/
|
||||||
|
public Date getDate() {
|
||||||
|
return this.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the user that created this ticket.
|
||||||
|
* @return The user.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of customers set for this ticket. It is not bound
|
||||||
|
* to the customer's account.
|
||||||
|
* @return The number of customers set for this ticket. Null when no
|
||||||
|
* number was defined.
|
||||||
|
*/
|
||||||
|
public Integer getCustCount() {
|
||||||
|
return this.custCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the customer's account linked to this ticket.
|
||||||
|
* @return The customer's account. Null when not assigned
|
||||||
|
* to a customer's account.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCustomer() {
|
||||||
|
return this.customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the variation of the customer's balance introduced by this ticket.
|
||||||
|
* @return The variation of the customer's balance.
|
||||||
|
*/
|
||||||
|
public double getCustBalance() {
|
||||||
|
return this.custBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tariff area used for this ticket.
|
||||||
|
* @return The tariff area. Null when not using a tariff area.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getTariffArea() {
|
||||||
|
return this.tariffArea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the content of the order.
|
||||||
|
* @return The list of article lines.
|
||||||
|
*/
|
||||||
|
public ImmutableList<TicketLineDTO> getLines() {
|
||||||
|
return this.lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of payments used to pay this ticket.
|
||||||
|
* @return The list of payments used to pay this ticket.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PaymentDTO> getPayments() {
|
||||||
|
return this.payments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of consolidated taxes after discounts.
|
||||||
|
* @return The list of tax amounts by tax.
|
||||||
|
*/
|
||||||
|
public ImmutableList<TaxAmountDTO> getTaxes() {
|
||||||
|
return this.taxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price of the whole ticket without tax and before
|
||||||
|
* ticket discount.
|
||||||
|
* @return The price without tax nor ticket discount.
|
||||||
|
*/
|
||||||
|
public double getPrice() {
|
||||||
|
return this.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price of the whole ticket before applying the ticket discount.
|
||||||
|
* @return The price with taxes, without ticket discount.
|
||||||
|
*/
|
||||||
|
public double getTaxedPrice() {
|
||||||
|
return this.taxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount profile assigned to this ticket.
|
||||||
|
* @return The discount profile, null when not discount profile
|
||||||
|
* was assigned.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getDiscountProfile() {
|
||||||
|
return this.discountProfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actual discount rate applied to the whole ticket.
|
||||||
|
* @return The discount rate applied to the whole ticket. It may be set
|
||||||
|
* even without using a discount profile.
|
||||||
|
*/
|
||||||
|
public double getDiscountRate() {
|
||||||
|
return this.discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without taxes including the ticket discount.
|
||||||
|
* @return The price without taxes after the ticket discount.
|
||||||
|
*/
|
||||||
|
public double getFinalPrice() {
|
||||||
|
return this.finalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the taxed price including the ticket discount. This is the
|
||||||
|
* amount that had to be paid.
|
||||||
|
* @return The price with taxes after the ticket discount.
|
||||||
|
*/
|
||||||
|
public double getFinalTaxedPrice() {
|
||||||
|
return this.finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of the ticket. The line doesn't make use of a
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.dto.WeakAssociationDTO} for
|
||||||
|
* the product because the reference can be null. It duplicates all the
|
||||||
|
* required informations directly because most of them can be modified
|
||||||
|
* on the fly when making an order.
|
||||||
|
*/
|
||||||
|
public class TicketLineDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -9065013247136552814L;
|
||||||
|
|
||||||
|
/** See {@link getDispOrder()}. */
|
||||||
|
private final int dispOrder;
|
||||||
|
/** See {@link getProductReference()}. */
|
||||||
|
private final String productReference;
|
||||||
|
/** See {@link getProductLabel()}. */
|
||||||
|
private final String productLabel;
|
||||||
|
/** See {@link getUnitPrice()}. */
|
||||||
|
private final double unitPrice;
|
||||||
|
/** See {@link getTaxedUnitPrice()}. */
|
||||||
|
private final double taxedUnitPrice;
|
||||||
|
/** See {@link getQuantity()}. */
|
||||||
|
private final double quantity;
|
||||||
|
/** See {@link getPrice()}. */
|
||||||
|
private final double price;
|
||||||
|
/** See {@link getTaxedPrice()}. */
|
||||||
|
private final double taxedPrice;
|
||||||
|
/** See {@link getTax()}. */
|
||||||
|
private final WeakAssociationDTO tax;
|
||||||
|
/** See {@link getTaxRate()}. */
|
||||||
|
private final double taxRate;
|
||||||
|
/** See {@link getDiscountRate()}. */
|
||||||
|
private final double discountRate;
|
||||||
|
/** See {@link getFinalPrice()}. */
|
||||||
|
private final double finalPrice;
|
||||||
|
/** See {@link getFinalTaxedPrice()}. */
|
||||||
|
private final double finalTaxedPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a line from all fields.
|
||||||
|
* @param dispOrder See {@link getDispOrder()}.
|
||||||
|
* @param productReference See {@link getProductReference()}.
|
||||||
|
* @param productLabel See {@link getProductLabel()}.
|
||||||
|
* @param unitPrice See {@link getUnitPrice()}.
|
||||||
|
* @param taxedUnitPrice See {@link getTaxedUnitPrice()}.
|
||||||
|
* @param quantity See {@link getQuantity()}.
|
||||||
|
* @param price See {@link getPrice()}.
|
||||||
|
* @param taxedPrice See {@link getTaxedPrice()}.
|
||||||
|
* @param tax See {@link getTax()}.
|
||||||
|
* @param taxRate See {@link getTaxRate()}.
|
||||||
|
* @param discountRate See {@link getDiscountRate()}.
|
||||||
|
* @param finalPrice See {@link getFinalPrice()}.
|
||||||
|
* @param finalTaxedPrice See {@link getFinalTaxedPrice()}.
|
||||||
|
*/
|
||||||
|
public TicketLineDTO(
|
||||||
|
int dispOrder,
|
||||||
|
String productReference,
|
||||||
|
String productLabel,
|
||||||
|
double unitPrice,
|
||||||
|
double taxedUnitPrice,
|
||||||
|
double quantity,
|
||||||
|
double price,
|
||||||
|
double taxedPrice,
|
||||||
|
WeakAssociationDTO tax,
|
||||||
|
double taxRate,
|
||||||
|
double discountRate,
|
||||||
|
double finalPrice,
|
||||||
|
double finalTaxedPrice) {
|
||||||
|
this.dispOrder = dispOrder;
|
||||||
|
this.productReference = productReference;
|
||||||
|
this.productLabel = productLabel;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
this.taxedUnitPrice = taxedUnitPrice;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.price = price;
|
||||||
|
this.taxedPrice = taxedPrice;
|
||||||
|
this.tax = tax;
|
||||||
|
this.taxRate = taxRate;
|
||||||
|
this.discountRate = discountRate;
|
||||||
|
this.finalPrice = finalPrice;
|
||||||
|
this.finalTaxedPrice = finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public TicketLineDTO(Reader reader) throws ParseException {
|
||||||
|
this.dispOrder = reader.readInt("dispOrder");
|
||||||
|
this.productReference = reader.readStringOrNull("productReference");
|
||||||
|
this.productLabel = reader.readString("productLabel");
|
||||||
|
this.unitPrice = reader.readDouble("unitPrice");
|
||||||
|
this.taxedUnitPrice = reader.readDouble("taxedUnitPrice");
|
||||||
|
this.quantity = reader.readDouble("quantity");
|
||||||
|
this.price = reader.readDouble("price");
|
||||||
|
this.taxedPrice = reader.readDouble("taxedPrice");
|
||||||
|
reader.startObject("tax");
|
||||||
|
this.tax = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.taxRate = reader.readDouble("taxRate");
|
||||||
|
this.discountRate = reader.readDouble("discountRate");
|
||||||
|
this.finalPrice = reader.readDouble("finalPrice");
|
||||||
|
this.finalTaxedPrice = reader.readDouble("finalTaxedPrice");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of this line, starting from 0.
|
||||||
|
* @return The index of this line in the ticket.
|
||||||
|
*/
|
||||||
|
public int getDispOrder() {
|
||||||
|
return this.dispOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference of the product.
|
||||||
|
* @return The reference of the product. Null when using a custom product.
|
||||||
|
*/
|
||||||
|
public String getProductReference() {
|
||||||
|
return this.productReference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label of the product. It is always set even if no
|
||||||
|
* product was associated.
|
||||||
|
* @return The label of the product.
|
||||||
|
*/
|
||||||
|
public String getProductLabel() {
|
||||||
|
return this.productLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price of one unit of the product without taxes.
|
||||||
|
* @return The price of one unit of the product without taxes.
|
||||||
|
*/
|
||||||
|
public double getUnitPrice() {
|
||||||
|
return this.unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price of one unit of the product with taxes.
|
||||||
|
* @return The price of one unit of the product with taxes.
|
||||||
|
*/
|
||||||
|
public double getTaxedUnitPrice() {
|
||||||
|
return this.taxedUnitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the quantity of product in this line.
|
||||||
|
* @return The quantity of product in this line.
|
||||||
|
*/
|
||||||
|
public double getQuantity() {
|
||||||
|
return this.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price of the line without taxes before discount.
|
||||||
|
* @return The price of the line without taxes before applying
|
||||||
|
* the discount of the line. It is not affected by the discount
|
||||||
|
* of the ticket.
|
||||||
|
*/
|
||||||
|
public double getPrice() {
|
||||||
|
return this.price;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price of the line with taxes before discount.
|
||||||
|
* @return The price of the line with taxes before applying
|
||||||
|
* the discount of the line. It is not affected by the discount
|
||||||
|
* of the ticket.
|
||||||
|
*/
|
||||||
|
public double getTaxedPrice() {
|
||||||
|
return this.taxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the tax of the product.
|
||||||
|
* @return The id of the tax of the product.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getTax() {
|
||||||
|
return this.tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tax rate of this line.
|
||||||
|
* @return The tax rate applied for the product.
|
||||||
|
*/
|
||||||
|
public double getTaxRate() {
|
||||||
|
return this.taxRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discount rate applied to this line.
|
||||||
|
* @return The discount rate applied only to this line.
|
||||||
|
* It is independent from the discount of the ticket.
|
||||||
|
*/
|
||||||
|
public double getDiscountRate() {
|
||||||
|
return this.discountRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price without taxes for this line after applying
|
||||||
|
* the discount.
|
||||||
|
* @return The price without taxes for this line after applying
|
||||||
|
* the discount of the line.
|
||||||
|
*/
|
||||||
|
public double getFinalPrice() {
|
||||||
|
return this.finalPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the price with taxes for this line after applying
|
||||||
|
* the discount.
|
||||||
|
* @return The price with taxes for this line after applying
|
||||||
|
* the discount of the line.
|
||||||
|
*/
|
||||||
|
public double getFinalTaxedPrice() {
|
||||||
|
return this.finalTaxedPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal identifier to reference an other DTO. A WeakLinkDTO allows to track
|
||||||
|
* the referenced DTO but only includes the minimal requirements to be useable
|
||||||
|
* even if the referenced DTO is deleted or modified.
|
||||||
|
*/
|
||||||
|
public class WeakAssociationDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -4033459839844194734L;
|
||||||
|
|
||||||
|
/** See {@link getReference()}. */
|
||||||
|
private final String reference;
|
||||||
|
/** See {@link getLabel()}. */
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a weak association from all fields.
|
||||||
|
* @param reference The reference of the associated element at the time
|
||||||
|
* of creation.
|
||||||
|
* @param label The label of the associated element at the time
|
||||||
|
* of creation.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO(String reference, String label) {
|
||||||
|
this.reference = reference;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO(Reader reader) throws ParseException {
|
||||||
|
this.reference = reader.readString("reference");
|
||||||
|
this.label = reader.readString("label");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader. It may be null.
|
||||||
|
* @param reader The reader that must be currently pointing to the parent
|
||||||
|
* of this object.
|
||||||
|
* @param objectName The key to read from the reader.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
* @return A new WeakAssociationDTO or null if the value of the key
|
||||||
|
* is null.
|
||||||
|
*/
|
||||||
|
public static WeakAssociationDTO fromReader(Reader reader, String objectName) throws ParseException {
|
||||||
|
if (reader.isNull(objectName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
reader.startObject(objectName);
|
||||||
|
WeakAssociationDTO ret = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reference of the element as it was at the time of creation.
|
||||||
|
* It is not guaranteed that the associated element still even exists.
|
||||||
|
* @return The reference of the associated element at the time of creation.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return this.reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the label of the element as it was at the time of creation.
|
||||||
|
* It is not guaranteed that the associated element still even exists
|
||||||
|
* or still has the same label.
|
||||||
|
* @return The label of the associated element at the time of creation.
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO: check integrity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,574 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ImmutableList;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
import org.pasteque.coreutil.datatransfer.integrity.IntegrityExceptions;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.DTOFactory;
|
||||||
|
import org.pasteque.coreutil.datatransfer.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Receipt of a finalized cash session. Once a cash session is closed,
|
||||||
|
* a Z MajorTicket is generated to summarize sales, taxes, payments and changes to
|
||||||
|
* customer's balance. This summary can be registered in accounting.</p>
|
||||||
|
* <p>For consistency with mutable data the ticket references to, only
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.dto.WeakAssociationDTO} are used to
|
||||||
|
* keep ZTickets completely independent.</p>
|
||||||
|
* <p>Z Tickets are immutable unless changes are applied to the data structure.
|
||||||
|
* Because of that they cannot be signed. For completely immutable tickets that
|
||||||
|
* can be stored and archived see
|
||||||
|
* {@link org.pasteque.coreutil.datatransfer.dto.FiscalTicketDTO}.
|
||||||
|
*/
|
||||||
|
public final class ZTicketDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -2784800199238764159L;
|
||||||
|
|
||||||
|
/** See {@link getCashRegister()}. */
|
||||||
|
private WeakAssociationDTO cashRegister;
|
||||||
|
/** See {@link getSequence()}. */
|
||||||
|
private int sequence;
|
||||||
|
/** See {@link isContinuous()}. */
|
||||||
|
private boolean continuous;
|
||||||
|
/** See {@link getOpenDate()}. */
|
||||||
|
private Date openDate;
|
||||||
|
/** See {@link getCloseDate()}. */
|
||||||
|
private Date closeDate;
|
||||||
|
/** See {@link getMovements()}. */
|
||||||
|
private ImmutableList<MovementDTO> movements;
|
||||||
|
/** See {@link getCloseAmounts()}. */
|
||||||
|
private ImmutableList<PaymentDTO> closeAmounts;
|
||||||
|
/** See {@link getExpectedAmounts()}. */
|
||||||
|
private ImmutableList<PaymentDTO> expectedAmounts;
|
||||||
|
/** See {@link getUser()}. */
|
||||||
|
private WeakAssociationDTO user;
|
||||||
|
/** See {@link getTicketCount()}. */
|
||||||
|
private int ticketCount;
|
||||||
|
/** See {@link getCustCount()}. */
|
||||||
|
private Integer custCount;
|
||||||
|
/** See {@link getCs()}. */
|
||||||
|
private double cs;
|
||||||
|
/** See {@link getCsPeriod()}. */
|
||||||
|
private double csPeriod;
|
||||||
|
/** See {@link getCsFYear()}. */
|
||||||
|
private double csFYear;
|
||||||
|
/** See {@link getCsPerpetual()}. */
|
||||||
|
private double csPerpetual;
|
||||||
|
/** See {@link getTaxes()}. */
|
||||||
|
private ImmutableList<TaxAmountDTO> taxes;
|
||||||
|
/** See {@link getCatSales()}. */
|
||||||
|
private ImmutableList<ZTicketCatSalesDTO> catSales;
|
||||||
|
/** See {@link getCatTaxes()}. */
|
||||||
|
private ImmutableList<ZTicketCatTaxesDTO> catTaxes;
|
||||||
|
/** See {@link getPayments()}. */
|
||||||
|
private ImmutableList<PaymentDTO> payments;
|
||||||
|
/** See {@link getCustBalances()}. */
|
||||||
|
private ImmutableList<ZTicketCustBalanceDTO> custBalances;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an ticket from all fields.
|
||||||
|
* @param cashRegister See {@link getCashRegister()}.
|
||||||
|
* @param sequence See {@link getSequence()}.
|
||||||
|
* @param continuous See {@link isContinuous()}.
|
||||||
|
* @param openDate See {@link getOpenDate()}.
|
||||||
|
* @param closeDate See {@link getCloseDate()}.
|
||||||
|
* @param movements See {@link getMovements()}.
|
||||||
|
* @param closeAmounts See {@link getCloseAmounts()}.
|
||||||
|
* @param expectedAmounts See {@link getExpectedAmounts()}.
|
||||||
|
* @param user See {@link getUser()}.
|
||||||
|
* @param ticketCount See {@link getTicketCount()}.
|
||||||
|
* @param custCount See {@link getCustCount()}.
|
||||||
|
* @param cs See {@link getCs()}.
|
||||||
|
* @param csPeriod See {@link getCsPeriod()}.
|
||||||
|
* @param csFYear See {@link getCsFYear()}.
|
||||||
|
* @param csPerpetual See {@link getCsPerpetual()}.
|
||||||
|
* @param taxes See {@link getTaxes()}.
|
||||||
|
* @param catSales See {@link getCatSales()}.
|
||||||
|
* @param catTaxes See {@link getCatTaxes()}.
|
||||||
|
* @param payments See {@link getPayments()}.
|
||||||
|
* @param custBalances See {@link getCustBalances()}.
|
||||||
|
*/
|
||||||
|
public ZTicketDTO(
|
||||||
|
WeakAssociationDTO cashRegister,
|
||||||
|
int sequence,
|
||||||
|
boolean continuous,
|
||||||
|
Date openDate,
|
||||||
|
Date closeDate,
|
||||||
|
MovementDTO[] movements,
|
||||||
|
PaymentDTO[] closeAmounts,
|
||||||
|
PaymentDTO[] expectedAmounts,
|
||||||
|
WeakAssociationDTO user,
|
||||||
|
int ticketCount,
|
||||||
|
Integer custCount,
|
||||||
|
Double cs,
|
||||||
|
double csPeriod,
|
||||||
|
double csFYear,
|
||||||
|
double csPerpetual,
|
||||||
|
TaxAmountDTO[] taxes,
|
||||||
|
ZTicketCatSalesDTO[] catSales,
|
||||||
|
ZTicketCatTaxesDTO[] catTaxes,
|
||||||
|
PaymentDTO[] payments,
|
||||||
|
ZTicketCustBalanceDTO[] custBalances) {
|
||||||
|
this.cashRegister = cashRegister;
|
||||||
|
this.sequence = sequence;
|
||||||
|
this.continuous = continuous;
|
||||||
|
this.openDate = openDate;
|
||||||
|
this.closeDate = closeDate;
|
||||||
|
this.movements = new ImmutableList<MovementDTO>(movements);
|
||||||
|
this.closeAmounts = new ImmutableList<PaymentDTO>(closeAmounts);
|
||||||
|
this.expectedAmounts = new ImmutableList<PaymentDTO>(expectedAmounts);
|
||||||
|
this.user = user;
|
||||||
|
this.ticketCount = ticketCount;
|
||||||
|
this.custCount = custCount;
|
||||||
|
this.cs = cs;
|
||||||
|
this.csPeriod = csPeriod;
|
||||||
|
this.csFYear = csFYear;
|
||||||
|
this.csPerpetual = csPerpetual;
|
||||||
|
this.taxes = new ImmutableList<TaxAmountDTO>(taxes);
|
||||||
|
this.catSales = new ImmutableList<ZTicketCatSalesDTO>(catSales);
|
||||||
|
this.catTaxes = new ImmutableList<ZTicketCatTaxesDTO>(catTaxes);
|
||||||
|
this.payments = new ImmutableList<PaymentDTO>(payments);
|
||||||
|
this.custBalances = new ImmutableList<ZTicketCustBalanceDTO>(custBalances);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public ZTicketDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("cashRegister");
|
||||||
|
this.cashRegister = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.sequence = reader.readInt("sequence");
|
||||||
|
this.continuous = reader.readBoolean("continuous");
|
||||||
|
this.openDate = reader.readDateOrNull("openDate");
|
||||||
|
this.closeDate = reader.readDateOrNull("closeDate");
|
||||||
|
DTOFactory<MovementDTO> mvtFacto = new DTOFactory<MovementDTO>(reader, MovementDTO.class);
|
||||||
|
this.movements = mvtFacto.immutableReadObjects("movements");
|
||||||
|
DTOFactory<PaymentDTO> pmtFacto = new DTOFactory<PaymentDTO>(reader, PaymentDTO.class);
|
||||||
|
this.closeAmounts = pmtFacto.immutableReadObjects("closeAmounts");
|
||||||
|
this.expectedAmounts = pmtFacto.immutableReadObjects("expectedAmounts");
|
||||||
|
reader.startObject("user");
|
||||||
|
this.user = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.ticketCount = reader.readIntOrNull("ticketCount");
|
||||||
|
this.custCount = reader.readIntOrNull("custCount");
|
||||||
|
this.cs = reader.readDoubleOrNull("cs");
|
||||||
|
this.csPeriod = reader.readDouble("csPeriod");
|
||||||
|
this.csFYear = reader.readDouble("csFYear");
|
||||||
|
this.csPerpetual = reader.readDouble("csPerpetual");
|
||||||
|
DTOFactory<TaxAmountDTO> taxFacto = new DTOFactory<TaxAmountDTO>(reader, TaxAmountDTO.class);
|
||||||
|
this.taxes = taxFacto.immutableReadObjects("taxes");
|
||||||
|
DTOFactory<ZTicketCatSalesDTO> catSFacto = new DTOFactory<ZTicketCatSalesDTO>(reader, ZTicketCatSalesDTO.class);
|
||||||
|
this.catSales = catSFacto.immutableReadObjects("catSales");
|
||||||
|
DTOFactory<ZTicketCatTaxesDTO> catTFacto = new DTOFactory<ZTicketCatTaxesDTO>(reader, ZTicketCatTaxesDTO.class);
|
||||||
|
this.catTaxes = catTFacto.immutableReadObjects("catTaxes");
|
||||||
|
this.payments = pmtFacto.immutableReadObjects("payments");
|
||||||
|
DTOFactory<ZTicketCustBalanceDTO> custBFacto = new DTOFactory<ZTicketCustBalanceDTO>(reader, ZTicketCustBalanceDTO.class);
|
||||||
|
this.custBalances = custBFacto.immutableReadObjects("custBalances");
|
||||||
|
reader.startArray("custBalances");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the unique reference for this ticket.
|
||||||
|
* @return The reference, formatted as {@code <cash register>-<sequence>}.
|
||||||
|
*/
|
||||||
|
public String getReference() {
|
||||||
|
return String.format("%d-%d",
|
||||||
|
this.cashRegister.getReference(),
|
||||||
|
this.sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cash register.
|
||||||
|
* @return The reference and label of the cash register.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCashRegister() {
|
||||||
|
return this.cashRegister;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the sequence of the cash register.
|
||||||
|
* @return The sequence of the cash register in which this ticket
|
||||||
|
* was created.
|
||||||
|
*/
|
||||||
|
public int getSequence() {
|
||||||
|
return this.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the session was continuous starting from the previous one.
|
||||||
|
* The session is continuous when it started on the same machine than
|
||||||
|
* the previous one and with the previous one in cache.
|
||||||
|
* @return True when the session is marked as continuous.
|
||||||
|
* @see org.pasteque.coreutil.datatransfer.dto.CashSessionDTO#isContinuous()
|
||||||
|
*/
|
||||||
|
public boolean isContinuous() {
|
||||||
|
return continuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the date when the session was opened.
|
||||||
|
* @return The open date.
|
||||||
|
* @see org.pasteque.coreutil.datatransfer.dto.CashSessionDTO#getOpenDate()
|
||||||
|
*/
|
||||||
|
public Date getOpenDate() {
|
||||||
|
return this.openDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the date when the session was closed.
|
||||||
|
* @return The close date.
|
||||||
|
*/
|
||||||
|
public Date getCloseDate() {
|
||||||
|
return this.closeDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the movements not justified by a ticket, including the starting
|
||||||
|
* amounts.
|
||||||
|
* @return The movements. Empty when not counted.
|
||||||
|
* @see org.pasteque.coreutil.datatransfer.dto.CashSessionDTO#getMovements()
|
||||||
|
*/
|
||||||
|
public ImmutableList<MovementDTO> getMovements() {
|
||||||
|
return this.movements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ending amounts.
|
||||||
|
* @return The ending amounts. Empty when not counted.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PaymentDTO> getCloseAmounts() {
|
||||||
|
return this.closeAmounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the expected ending amounts.
|
||||||
|
* @return The expected ending cash amounts, from movements + payments.
|
||||||
|
* Empty when not counted.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PaymentDTO> getExpectedAmounts() {
|
||||||
|
return this.expectedAmounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the id of the user that closed this session.
|
||||||
|
* @return The reference of label of the user.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getUser() {
|
||||||
|
return this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of tickets registered withing this session.
|
||||||
|
* @return The number of tickets registered withing this session.
|
||||||
|
*/
|
||||||
|
public int getTicketCount() {
|
||||||
|
return ticketCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of customers set for this ticket. It is not bound
|
||||||
|
* to the customer's account.
|
||||||
|
* @return The number of customers set for this ticket. Null when no
|
||||||
|
* number was defined.
|
||||||
|
*/
|
||||||
|
public Integer getCustCount() {
|
||||||
|
return this.custCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the consolidated sales amount for this session.
|
||||||
|
* @return The consolidated sales amount for this session.
|
||||||
|
*/
|
||||||
|
public double getCs() {
|
||||||
|
return cs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cumulative consolidated sales for the period.
|
||||||
|
* @return The cumulative consolidated sales for the period.
|
||||||
|
*/
|
||||||
|
public double getCsPeriod() {
|
||||||
|
return csPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cumulative consolidated sales for the fiscal year.
|
||||||
|
* @return The cumulative consolidated sales for the fiscal year.
|
||||||
|
*/
|
||||||
|
public double getCsFYear() {
|
||||||
|
return csFYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the perpetual cumulative consolidated sales for this cash register.
|
||||||
|
* @return The perpetual cumulative consolidated sales for this cash
|
||||||
|
* register.
|
||||||
|
*/
|
||||||
|
public double getCsPerpetual() {
|
||||||
|
return csPerpetual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of consolidated taxes after discounts.
|
||||||
|
* @return The list of tax amounts by tax.
|
||||||
|
*/
|
||||||
|
public ImmutableList<TaxAmountDTO> getTaxes() {
|
||||||
|
return this.taxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of consolidated sales by category.
|
||||||
|
* @return The list of consolidated sales by category.
|
||||||
|
*/
|
||||||
|
public ImmutableList<ZTicketCatSalesDTO> getCatSales() {
|
||||||
|
return this.catSales;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the informative list of consolidated taxes by category.
|
||||||
|
* @return The list of consolidated taxes by category.
|
||||||
|
*/
|
||||||
|
public ImmutableList<ZTicketCatTaxesDTO> getCatTaxes() {
|
||||||
|
return this.catTaxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of consolidated amounts per payments.
|
||||||
|
* @return The list of consolidated amounts of payments.
|
||||||
|
*/
|
||||||
|
public ImmutableList<PaymentDTO> getPayments() {
|
||||||
|
return this.payments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of consolidated variation of customer's balance
|
||||||
|
* per customer.
|
||||||
|
* @return The list of consolidated variation of customer's balance
|
||||||
|
* per customer.
|
||||||
|
*/
|
||||||
|
public ImmutableList<ZTicketCustBalanceDTO> getCustBalances() {
|
||||||
|
return this.custBalances;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consolidated sales for a category.
|
||||||
|
*/
|
||||||
|
public class ZTicketCatSalesDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -6392552469204214096L;
|
||||||
|
|
||||||
|
/** See {@link getCategory()}. */
|
||||||
|
private final WeakAssociationDTO category;
|
||||||
|
/** See {@link getAmount()}. */
|
||||||
|
private final double amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a category sales from all fields.
|
||||||
|
* @param category See {@link getCategory()}.
|
||||||
|
* @param amount See {@link getAmount()}.
|
||||||
|
*/
|
||||||
|
public ZTicketCatSalesDTO(
|
||||||
|
WeakAssociationDTO category,
|
||||||
|
double amount) {
|
||||||
|
this.category = category;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public ZTicketCatSalesDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("category");
|
||||||
|
this.category = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.amount = reader.readDouble("amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the category.
|
||||||
|
* @return The reference and label of the category.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCategory() {
|
||||||
|
return this.category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of sales for this category.
|
||||||
|
* @return The amount of sales in the main currency.
|
||||||
|
*/
|
||||||
|
public double getAmount() {
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informative taxes collected by category.
|
||||||
|
* The sum of amount may differ because of rounding issues if rounding
|
||||||
|
* happens. Use ZTicketTaxesDTO for the actual amount of taxes.
|
||||||
|
*/
|
||||||
|
public class ZTicketCatTaxesDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = -8934368775977813408L;
|
||||||
|
|
||||||
|
/** See {@link getCategory()}. */
|
||||||
|
private final WeakAssociationDTO category;
|
||||||
|
/** See {@link getTax()}. */
|
||||||
|
private final WeakAssociationDTO tax;
|
||||||
|
/** See {@link getBase()}. */
|
||||||
|
private final double base;
|
||||||
|
/** See {@link getAmount()}. */
|
||||||
|
private final double amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a category taxes from all fields.
|
||||||
|
* @param category See {@link getCategory()}.
|
||||||
|
* @param tax See {@link getTax()}.
|
||||||
|
* @param base See {@link getBase()}.
|
||||||
|
* @param amount See {@link getAmount()}.
|
||||||
|
*/
|
||||||
|
public ZTicketCatTaxesDTO(
|
||||||
|
WeakAssociationDTO category,
|
||||||
|
WeakAssociationDTO tax,
|
||||||
|
double base,
|
||||||
|
double amount) {
|
||||||
|
this.category = category;
|
||||||
|
this.tax = tax;
|
||||||
|
this.base = base;
|
||||||
|
this.amount = amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public ZTicketCatTaxesDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("category");
|
||||||
|
this.category = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
reader.startObject("tax");
|
||||||
|
this.tax = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.base = reader.readDouble("base");
|
||||||
|
this.amount = reader.readDouble("amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the category.
|
||||||
|
* @return The reference and label of the category.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCategory() {
|
||||||
|
return this.category;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the tax.
|
||||||
|
* @return The reference and label of the tax.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getTax() {
|
||||||
|
return this.tax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base amount associated to this tax.
|
||||||
|
* @return The base amount in the main currency.
|
||||||
|
*/
|
||||||
|
public double getBase() {
|
||||||
|
return this.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the amount of tax for this category.
|
||||||
|
* @return The amount of sales in the main currency.
|
||||||
|
*/
|
||||||
|
public double getAmount() {
|
||||||
|
return this.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consolidated variation of a customer's balance.
|
||||||
|
*/
|
||||||
|
public class ZTicketCustBalanceDTO implements DTOInterface, Serializable
|
||||||
|
{
|
||||||
|
private static final long serialVersionUID = 7698608167553284402L;
|
||||||
|
|
||||||
|
/** See {@link getCustomer()}. */
|
||||||
|
private final WeakAssociationDTO customer;
|
||||||
|
/** See {@link getBalance()}. */
|
||||||
|
private final double balance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a customer's balance variation from all fields.
|
||||||
|
* @param customer See {@link getCustomer()}.
|
||||||
|
* @param balance See {@link getBalance()}.
|
||||||
|
*/
|
||||||
|
public ZTicketCustBalanceDTO(
|
||||||
|
WeakAssociationDTO customer,
|
||||||
|
double balance) {
|
||||||
|
this.customer = customer;
|
||||||
|
this.balance = balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate from raw data from a reader.
|
||||||
|
* @param reader The reader that must be currently pointing to this object.
|
||||||
|
* @throws ParseException When an error occurs while parsing the object.
|
||||||
|
* See {@link org.pasteque.coreutil.datatransfer.parser.Reader} for details.
|
||||||
|
*/
|
||||||
|
public ZTicketCustBalanceDTO(Reader reader) throws ParseException {
|
||||||
|
reader.startObject("customer");
|
||||||
|
this.customer = new WeakAssociationDTO(reader);
|
||||||
|
reader.endObject();
|
||||||
|
this.balance = reader.readDouble("balance");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the variation of the balance for this customer.
|
||||||
|
* @return The variation of the balance in the main currency.
|
||||||
|
*/
|
||||||
|
public double getBalance() {
|
||||||
|
return this.balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the customer.
|
||||||
|
* @return The reference and label of the customer.
|
||||||
|
*/
|
||||||
|
public WeakAssociationDTO getCustomer() {
|
||||||
|
return this.customer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void checkIntegrity() throws IntegrityExceptions {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* <p>Data Transfer Object, immutable records with only primitive typed data
|
||||||
|
* and getters.</p>
|
||||||
|
* <p>All the DTO are final to ensure the immutability. Extensions should use
|
||||||
|
* encapsulation to add more data or ease-of-use, to ensure the major package
|
||||||
|
* using them cannot be alterated.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.coreutil.datatransfer.dto;
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.format;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert binary data from/to String encoding.</p>
|
||||||
|
* <p>It will link to various base64 encoder/decoder based upon their
|
||||||
|
* availability on the system:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>java.util.Base64: since Java SE 1.8, Android 8.0 (level 26)</li>
|
||||||
|
* <li>android.util.Base64: since Android 2.2 (level 8)</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>The detection of a suitable class is done on startup (within static { }).
|
||||||
|
* If no suitable encoder/decoder is found, it will throw an Error.</p>
|
||||||
|
*/
|
||||||
|
public class BinaryDTOFormat
|
||||||
|
{
|
||||||
|
/** Reflective object for encoding. */
|
||||||
|
private static final Object encodeObject;
|
||||||
|
/** Reflective method for encoding. */
|
||||||
|
private static final Method encodeFunction;
|
||||||
|
/** Reflective object for decoding. */
|
||||||
|
private static final Object decodeObject;
|
||||||
|
/** Reflective method for decoding. */
|
||||||
|
private static final Method decodeFunction;
|
||||||
|
/** Information about how to call reflective methods. */
|
||||||
|
private static final EncodeDecode encodeDecode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for an available encoder/decoder on the system.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
/* non final assignments */
|
||||||
|
EncodeDecode assignEncodeDecode;
|
||||||
|
Object assignEncodeObject;
|
||||||
|
Method assignEncodeFunction;
|
||||||
|
Object assignDecodeObject;
|
||||||
|
Method assignDecodeFunction;
|
||||||
|
try {
|
||||||
|
/* Try java.util.base64 */
|
||||||
|
Class<?> jub64e = Class.forName("java.util.Base64.Encoder");
|
||||||
|
Class<?> jub64d = Class.forName("java.util.Base64.Decoder");
|
||||||
|
try {
|
||||||
|
assignEncodeDecode = EncodeDecode.JAVA_UTIL_BASE64;
|
||||||
|
assignEncodeObject = jub64e.getMethod("getEncoder").invoke(null);
|
||||||
|
assignEncodeFunction = jub64e.getMethod("encode", Class.forName("byte[]"));
|
||||||
|
assignDecodeObject = jub64d.getMethod("getDecoder").invoke(null);
|
||||||
|
assignDecodeFunction = jub64d.getMethod("decode", Class.forName("String"));
|
||||||
|
} catch (NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException e) {
|
||||||
|
throw new Error("Unable to link java.util.Base64.[Encode|Decode] methods", e);
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException java_util_base64_not_found) {
|
||||||
|
/* Try android.util.base64 */
|
||||||
|
try {
|
||||||
|
Class<?> aub64 = Class.forName("android.util.Base64");
|
||||||
|
try {
|
||||||
|
assignEncodeDecode = EncodeDecode.ANDROID_UTIL_BASE64;
|
||||||
|
assignEncodeObject = null;
|
||||||
|
assignEncodeFunction = aub64.getMethod("encodeToString", Class.forName("byte[]"), Class.forName("int"));
|
||||||
|
assignDecodeObject = null;
|
||||||
|
assignDecodeFunction = aub64.getMethod("decode", Class.forName("String"), Class.forName("int"));
|
||||||
|
} catch (NoSuchMethodException | SecurityException e) {
|
||||||
|
throw new Error("Unable to link android.util.Base64 methods", e);
|
||||||
|
}
|
||||||
|
} catch (ClassNotFoundException android_util_base64_not_found) {
|
||||||
|
/* No suitable base64 class found */
|
||||||
|
throw new LinkageError("Unable to find any base64 encoder/decoder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* final assignments */
|
||||||
|
encodeDecode = assignEncodeDecode;
|
||||||
|
encodeObject = assignEncodeObject;
|
||||||
|
encodeFunction = assignEncodeFunction;
|
||||||
|
decodeObject = assignDecodeObject;
|
||||||
|
decodeFunction = assignDecodeFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode binary data from a base64-encoded string.
|
||||||
|
* May throw IllegalArgumentException if the string is not base64-encoded.
|
||||||
|
* @param strData The base64 encoded string.
|
||||||
|
* @return The binary data.
|
||||||
|
*/
|
||||||
|
public static byte[] fromString(String strData) {
|
||||||
|
return encodeDecode.decode(strData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode binary data to a base64-encoded string.
|
||||||
|
* @param binData The binary data to encode.
|
||||||
|
* @return The base64-encoded string.
|
||||||
|
*/
|
||||||
|
public static String toString(byte[] binData) {
|
||||||
|
return encodeDecode.encode(binData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static utility-class, cannot be instantiated. */
|
||||||
|
private BinaryDTOFormat() { }
|
||||||
|
|
||||||
|
/** Enumeration of encode/decode signatures. */
|
||||||
|
private enum EncodeDecode
|
||||||
|
{
|
||||||
|
/** Signature from java.util.Base64. */
|
||||||
|
JAVA_UTIL_BASE64
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public byte[] decode(String strData) {
|
||||||
|
try {
|
||||||
|
return (byte[]) decodeFunction.invoke(decodeObject, strData);
|
||||||
|
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
throw new Error("Unable to call dynamically linked base64 decode", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encode(byte[] binData) {
|
||||||
|
try {
|
||||||
|
return (String) encodeFunction.invoke(encodeObject, binData);
|
||||||
|
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
throw new Error("Unable to call dynamically linked base64 encode", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** Signature from android.util.Base64. */
|
||||||
|
ANDROID_UTIL_BASE64
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public byte[] decode(String strData) {
|
||||||
|
try {
|
||||||
|
return (byte[]) decodeFunction.invoke(null, strData, 0 /*DEFAULT*/);
|
||||||
|
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
throw new Error("Unable to call dynamically linked base64 decode", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encode(byte[] binData) {
|
||||||
|
try {
|
||||||
|
return (String) encodeFunction.invoke(null, binData, 0 /* DEFAULT */);
|
||||||
|
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
|
||||||
|
throw new Error("Unable to call dynamically linked base64 encode", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The decoding function.
|
||||||
|
* @param strData The base64 string.
|
||||||
|
* @return The binary data.
|
||||||
|
*/
|
||||||
|
public abstract byte[] decode(String strData);
|
||||||
|
/**
|
||||||
|
* The encoding function.
|
||||||
|
* @param binData The binary data.
|
||||||
|
* @return The base64 string.
|
||||||
|
*/
|
||||||
|
public abstract String encode(byte[] binData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
package org.pasteque.coreutil.datatransfer.format;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import org.pasteque.coreutil.ParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Convert date data from/to string/number encoding.</p>
|
||||||
|
* <p>Dates are stored in local time, without timezone.
|
||||||
|
* Accepted date formats are:</p>
|
||||||
|
* <ul>
|
||||||
|
* <li>"YYYY-MM-DD" or "YYYY-MM-DD hh:mm:ss"</li>
|
||||||
|
* <li>Timestamp in seconds (not milliseconds)</li>
|
||||||
|
* <li>Null</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>The detection of a suitable class is done on startup (within static { }).
|
||||||
|
* If no suitable encoder/decoder is found, it will throw an Error.</p>
|
||||||
|
*/
|
||||||
|
// TODO: handle Date timezone
|
||||||
|
public class DateDTOFormat
|
||||||
|
{
|
||||||
|
/** Regex for the YYYY-MM-DD format. */
|
||||||
|
private static final String DATE_REGEX = "\\A\\d{4}-\\d{2}-\\d{2}\\z";
|
||||||
|
/** Regex for the YYYY-MM-DD hh:mm:ss format. */
|
||||||
|
private static final String DATETIME_REGEX = "\\A\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\z";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a YYYY-MM-DD string to a Date.
|
||||||
|
* @param strData The string to parse. It is trimmed before being parsed.
|
||||||
|
* @return The date. Null when strData is null or empty.
|
||||||
|
* @throws ParseException When strData does conform to a date format.
|
||||||
|
*/
|
||||||
|
public static Date parseDate(String strData) throws ParseException {
|
||||||
|
if (strData == null || strData.trim().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean hasTime = false;
|
||||||
|
String trimmed = strData.trim();
|
||||||
|
if (!trimmed.matches(DATE_REGEX)) {
|
||||||
|
throw new ParseException("Unacceptable date format, must conform to YYYY-MM-DD[ hh:mm:ss].");
|
||||||
|
}
|
||||||
|
if (trimmed.matches(DATETIME_REGEX)) {
|
||||||
|
hasTime = true;
|
||||||
|
}
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
int year = Integer.parseInt(trimmed.substring(0, 4));
|
||||||
|
int month = Integer.parseInt(trimmed.substring(5, 7));
|
||||||
|
int day = Integer.parseInt(trimmed.substring(8, 10));
|
||||||
|
int hour = 0;
|
||||||
|
int minute = 0;
|
||||||
|
int second = 0;
|
||||||
|
if (hasTime) {
|
||||||
|
hour = Integer.parseInt(trimmed.substring(11, 13));
|
||||||
|
minute = Integer.parseInt(trimmed.substring(14, 16));
|
||||||
|
second = Integer.parseInt(trimmed.substring(17, 19));
|
||||||
|
}
|
||||||
|
c.set(year, month - 1, day, hour, minute, second);
|
||||||
|
return c.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a timestamp in seconds to a Date.
|
||||||
|
* @param secTimestamp The timestamp in seconds, local time.
|
||||||
|
* @return The date.
|
||||||
|
*/
|
||||||
|
public static Date parseDate(long secTimestamp) {
|
||||||
|
return new Date(secTimestamp * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the date to the YYYY-MM-DD format.
|
||||||
|
* @param date The date to convert.
|
||||||
|
* @return The date in YYYY-MM-DD format. Null if date is null.
|
||||||
|
*/
|
||||||
|
public static String toDateString(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
c.setTime(date);
|
||||||
|
return String.format("%04d-%02d-%02d",
|
||||||
|
c.get(Calendar.YEAR),
|
||||||
|
c.get(Calendar.MONTH) + 1,
|
||||||
|
c.get(Calendar.DAY_OF_MONTH)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the date to the YYYY-MM-DD hh:mm:ss format.
|
||||||
|
* @param date The date to convert.
|
||||||
|
* @return The date in YYYY-MM-DD hh:mm:ss format. Null if date is null.
|
||||||
|
*/
|
||||||
|
public static String toDateTimeString(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
c.setTime(date);
|
||||||
|
return String.format("%04d-%02d-%02d %02d:%02d:%02d",
|
||||||
|
c.get(Calendar.YEAR),
|
||||||
|
c.get(Calendar.MONTH) + 1,
|
||||||
|
c.get(Calendar.DAY_OF_MONTH),
|
||||||
|
c.get(Calendar.HOUR_OF_DAY),
|
||||||
|
c.get(Calendar.MINUTE),
|
||||||
|
c.get(Calendar.SECOND)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the date to a timestamp in seconds.
|
||||||
|
* @param date The date to convert.
|
||||||
|
* @return The timestamp in seconds.
|
||||||
|
* @throws NullPointerException When date is null.
|
||||||
|
*/
|
||||||
|
public static long toSecTimestamp(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
throw new NullPointerException("Date cannot be null");
|
||||||
|
}
|
||||||
|
return date.getTime() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Static utility-class, cannot be instantiated. */
|
||||||
|
private DateDTOFormat() { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* <p>Common conversion of non-string nor number data from/to string.</p>
|
||||||
|
* <p>These conversion tools are only about data representation for DTO.
|
||||||
|
* For user friendly formats see {@link org.pasteque.common.view.Formatter}.</p>
|
||||||
|
*/
|
||||||
|
package org.pasteque.coreutil.datatransfer.format;
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue