commit 1c35b731fb475ac85cdb2a4c47802d51d647d85f Author: kelvinsuen Date: Fri Jun 13 17:13:22 2025 +0800 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..88d6b78 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +# 2Fi LIONER Backend Setup + +## 1. Create MySQL database +- Run the following command in MySQL + ``` + CREATE SCHEMA `lionerdb` DEFAULT CHARACTER SET utf8 COLLATE utf8_bin; + ``` + +## 2. Edit the config to match with the environment + - application-db-local.yml + - Update the MySQL database location & login info: + ``` + spring: + datasource: + jdbc-url: jdbc:mysql://127.0.0.1:3308/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + username: root + password: secret + ``` + +## 3. Configurations for VScode +- Build the **launch.json** & **settings.json** files, put them in **.vscode** folder and paste the following code: + - **launch.json** + ``` + { + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Launch Local", + "request": "launch", + "mainClass": "com.ffii.lioner.LionerApplication", + "console": "internalConsole", + "projectName": "LIONER", + "vmArgs": "-Xms2g -Xmx4g", + "args": "--spring.profiles.active=db-local,ldap-local,local" + } + ] + } + ``` + +- **settings.json** + - Modify the java sdk directory if necessary + ``` + { + "java.configuration.updateBuildConfiguration": "interactive", + "java.jdt.ls.java.home": "C:\\java\\jdk-17.0.8", + "java.jdt.ls.vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx4G -Xms100m -Xlog:disable" + } + ``` + +## 4. Run the application +- Run the application in VScode \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..0915992 --- /dev/null +++ b/build.gradle @@ -0,0 +1,69 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.1.9' + id 'io.spring.dependency-management' version '1.1.0' +} + +group = 'com.ffii' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-data-ldap' + implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-log4j2' + implementation 'org.springframework.security:spring-security-ldap' + implementation 'org.liquibase:liquibase-core' + + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0' + implementation group: 'org.apache.poi', name: 'poi', version: '5.2.3' + implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '5.2.3' + implementation group: 'org.apache.pdfbox', name: 'pdfbox', version: '3.0.0' + + implementation group: 'jakarta.persistence', name: 'jakarta.persistence-api', version: '3.1.0' + implementation group: 'jakarta.annotation', name: 'jakarta.annotation-api', version: '2.1.1' + implementation group: 'jakarta.validation', name: 'jakarta.validation-api', version: '3.0.2' + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.15.2' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.15.2' + + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + implementation group: 'org.bytedeco', name: 'ffmpeg-platform', version: '5.1.2-1.5.8' + + implementation group: 'org.bytedeco', name: 'javacv', version: '1.5.8' + + implementation group: 'org.freemarker', name: 'freemarker', version: '2.3.32' + compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.0.0' + + implementation group: 'org.apache.poi', name: 'poi', version: '5.2.2' + implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '5.2.2' + + implementation group: 'org.docx4j', name: 'docx4j-core', version: '11.4.11' + implementation group: 'org.docx4j', name: 'docx4j-JAXB-ReferenceImpl', version: '11.4.11' + + implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' + + runtimeOnly 'com.mysql:mysql-connector-j' + runtimeOnly 'com.unboundid:unboundid-ldapsdk:6.0.9' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' +} + +configurations { + all { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging' + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c1962a7 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..37aef8d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..a69d9cb --- /dev/null +++ b/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f127cfd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..40bb102 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'LIONER' diff --git a/src/main/java/com/ffii/core/entity/BaseEntity.java b/src/main/java/com/ffii/core/entity/BaseEntity.java new file mode 100644 index 0000000..f9ec189 --- /dev/null +++ b/src/main/java/com/ffii/core/entity/BaseEntity.java @@ -0,0 +1,124 @@ +package com.ffii.core.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Optional; + +import org.springframework.security.core.context.SecurityContextHolder; + +import com.ffii.lioner.modules.user.entity.User; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; +import jakarta.persistence.Version; +import jakarta.validation.constraints.NotNull; + +/** @author Terence */ +@MappedSuperclass +public abstract class BaseEntity extends IdEntity { + + @NotNull + @Version + @Column + private Integer version; + + @NotNull + @Column(updatable = false) + private LocalDateTime created; + + @Column(updatable = false) + private Long createdBy; + + @NotNull + @Column + private LocalDateTime modified; + + @Column + private Long modifiedBy; + + @NotNull + @Column + private Boolean deleted; + + @PrePersist + public void autoSetCreated() { + this.setCreated(LocalDateTime.now()); + this.setModified(LocalDateTime.now()); + this.setDeleted(Boolean.FALSE); + + Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) + .ifPresentOrElse( + authentication -> { + Long userId = ((User)authentication.getPrincipal()).getId(); + this.setCreatedBy(userId); + this.setModifiedBy(userId); + }, + () -> { + this.setCreatedBy(null); + this.setModifiedBy(null); + }); + } + + @PreUpdate + public void autoSetModified() { + this.setModified(LocalDateTime.now()); + Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()).ifPresentOrElse( + authentication -> this.setModifiedBy(((User)authentication.getPrincipal()).getId()), + () -> this.setModifiedBy(null)); + } + + public Integer getVersion() { + return this.version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public LocalDateTime getCreated() { + return this.created; + } + + public void setCreated(LocalDateTime created) { + this.created = created; + } + + public Long getCreatedBy() { + return this.createdBy; + } + + public void setCreatedBy(Long createdBy) { + this.createdBy = createdBy; + } + + public LocalDateTime getModified() { + return this.modified; + } + + public void setModified(LocalDateTime modified) { + this.modified = modified; + } + + public Long getModifiedBy() { + return this.modifiedBy; + } + + public void setModifiedBy(Long modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public Boolean isDeleted() { + return this.deleted; + } + + public Boolean getDeleted() { + return this.deleted; + } + + public void setDeleted(Boolean deleted) { + this.deleted = deleted; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/core/entity/IdEntity.java b/src/main/java/com/ffii/core/entity/IdEntity.java new file mode 100644 index 0000000..210fe41 --- /dev/null +++ b/src/main/java/com/ffii/core/entity/IdEntity.java @@ -0,0 +1,49 @@ +package com.ffii.core.entity; + +import java.io.Serializable; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PostLoad; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Transient; + +import org.springframework.data.domain.Persistable; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** @author Terence */ +@MappedSuperclass +public abstract class IdEntity implements Persistable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private ID id; + + @Transient + private boolean isNew = true; + + @JsonIgnore + @Override + public boolean isNew() { + return isNew; + } + + @PrePersist + @PostLoad + void markNotNew() { + this.isNew = false; + } + + // getter and setter + + public ID getId() { + return id; + } + + public void setId(ID id) { + this.id = id; + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/core/exception/BadRequestException.java b/src/main/java/com/ffii/core/exception/BadRequestException.java new file mode 100644 index 0000000..7ac98c0 --- /dev/null +++ b/src/main/java/com/ffii/core/exception/BadRequestException.java @@ -0,0 +1,15 @@ +package com.ffii.core.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class BadRequestException extends ResponseStatusException { + + public BadRequestException() { + super(HttpStatus.BAD_REQUEST); + } + + public BadRequestException(String reason) { + super(HttpStatus.BAD_REQUEST, reason); + } +} diff --git a/src/main/java/com/ffii/core/exception/ConflictException.java b/src/main/java/com/ffii/core/exception/ConflictException.java new file mode 100644 index 0000000..cc1f9c5 --- /dev/null +++ b/src/main/java/com/ffii/core/exception/ConflictException.java @@ -0,0 +1,16 @@ +package com.ffii.core.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +/* e.g. sub record not under record */ +public class ConflictException extends ResponseStatusException { + + public ConflictException() { + super(HttpStatus.CONFLICT); + } + + public ConflictException(String reason) { + super(HttpStatus.CONFLICT, reason); + } +} diff --git a/src/main/java/com/ffii/core/exception/InternalServerErrorException.java b/src/main/java/com/ffii/core/exception/InternalServerErrorException.java new file mode 100644 index 0000000..5587158 --- /dev/null +++ b/src/main/java/com/ffii/core/exception/InternalServerErrorException.java @@ -0,0 +1,19 @@ +package com.ffii.core.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public class InternalServerErrorException extends ResponseStatusException { + + public InternalServerErrorException() { + super(HttpStatus.INTERNAL_SERVER_ERROR); + } + + public InternalServerErrorException(String reason) { + super(HttpStatus.INTERNAL_SERVER_ERROR, reason); + } + + public InternalServerErrorException(String reason, Throwable e) { + super(HttpStatus.INTERNAL_SERVER_ERROR, reason, e); + } +} diff --git a/src/main/java/com/ffii/core/exception/NotFoundException.java b/src/main/java/com/ffii/core/exception/NotFoundException.java new file mode 100644 index 0000000..f41d0a3 --- /dev/null +++ b/src/main/java/com/ffii/core/exception/NotFoundException.java @@ -0,0 +1,13 @@ +package com.ffii.core.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +/* main record not found (e.g. item record) */ +public class NotFoundException extends ResponseStatusException{ + + public NotFoundException() { + super(HttpStatus.NOT_FOUND); + } + +} diff --git a/src/main/java/com/ffii/core/exception/UnprocessableEntityException.java b/src/main/java/com/ffii/core/exception/UnprocessableEntityException.java new file mode 100644 index 0000000..d099908 --- /dev/null +++ b/src/main/java/com/ffii/core/exception/UnprocessableEntityException.java @@ -0,0 +1,37 @@ +package com.ffii.core.exception; + +import java.util.Map; + +import jakarta.validation.constraints.NotNull; + +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +/* sub record not found (e.g. item_line record) */ +public class UnprocessableEntityException extends ResponseStatusException { + + public UnprocessableEntityException() { + super(HttpStatus.UNPROCESSABLE_ENTITY); + } + + public UnprocessableEntityException(@NotNull Map map) { + super(HttpStatus.UNPROCESSABLE_ENTITY, map2Str(map)); + } + + public UnprocessableEntityException(String reason) { + super(HttpStatus.UNPROCESSABLE_ENTITY, reason); + } + + private static String map2Str(@NotNull Map map) { + try { + return new ObjectMapper().writeValueAsString(map); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } + +} diff --git a/src/main/java/com/ffii/core/response/AuthRes.java b/src/main/java/com/ffii/core/response/AuthRes.java new file mode 100644 index 0000000..d1b96f6 --- /dev/null +++ b/src/main/java/com/ffii/core/response/AuthRes.java @@ -0,0 +1,32 @@ +package com.ffii.core.response; + +public class AuthRes { + private boolean success; + private String exception; + + public AuthRes(boolean success, String exception) { + this.success = success; + this.exception = exception; + } + + public boolean isSuccess() { + return this.success; + } + + public boolean getSuccess() { + return this.success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getException() { + return this.exception; + } + + public void setException(String exception) { + this.exception = exception; + } + +} diff --git a/src/main/java/com/ffii/core/response/DataRes.java b/src/main/java/com/ffii/core/response/DataRes.java new file mode 100644 index 0000000..d4edb54 --- /dev/null +++ b/src/main/java/com/ffii/core/response/DataRes.java @@ -0,0 +1,21 @@ +package com.ffii.core.response; + +public class DataRes { + private T data; + + public DataRes() { + } + + public DataRes(T data) { + this.data = data; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + +} diff --git a/src/main/java/com/ffii/core/response/ErrorRes.java b/src/main/java/com/ffii/core/response/ErrorRes.java new file mode 100644 index 0000000..40f2a66 --- /dev/null +++ b/src/main/java/com/ffii/core/response/ErrorRes.java @@ -0,0 +1,36 @@ +package com.ffii.core.response; + +import java.time.LocalDateTime; + +public class ErrorRes { + + private LocalDateTime timestamp; + + private String traceId; + + public ErrorRes() { + this.timestamp = LocalDateTime.now(); + } + + public ErrorRes(String traceId) { + this.timestamp = LocalDateTime.now(); + this.traceId = traceId; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public String getTraceId() { + return traceId; + } + + public void setTraceId(String traceId) { + this.traceId = traceId; + } + +} diff --git a/src/main/java/com/ffii/core/response/FailureRes.java b/src/main/java/com/ffii/core/response/FailureRes.java new file mode 100644 index 0000000..838b2b4 --- /dev/null +++ b/src/main/java/com/ffii/core/response/FailureRes.java @@ -0,0 +1,39 @@ +package com.ffii.core.response; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonInclude; + +public class FailureRes { + + private LocalDateTime timestamp; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private String error; + + public FailureRes() { + this.timestamp = LocalDateTime.now(); + } + + public FailureRes(String error) { + this.timestamp = LocalDateTime.now(); + this.error = error; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + +} diff --git a/src/main/java/com/ffii/core/response/IdRes.java b/src/main/java/com/ffii/core/response/IdRes.java new file mode 100644 index 0000000..95f72bd --- /dev/null +++ b/src/main/java/com/ffii/core/response/IdRes.java @@ -0,0 +1,21 @@ +package com.ffii.core.response; + +public class IdRes { + private long id; + + public IdRes() { + } + + public IdRes(long id) { + this.id = id; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + +} diff --git a/src/main/java/com/ffii/core/response/RecordsRes.java b/src/main/java/com/ffii/core/response/RecordsRes.java new file mode 100644 index 0000000..7798b9e --- /dev/null +++ b/src/main/java/com/ffii/core/response/RecordsRes.java @@ -0,0 +1,41 @@ +package com.ffii.core.response; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; + +public class RecordsRes { + private List records; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Integer total; + + public RecordsRes() { + } + + public RecordsRes(List records) { + this.records = records; + } + + public RecordsRes(List records, int total) { + this.records = records; + this.total = total; + } + + public List getRecords() { + return records; + } + + public void setRecords(List records) { + this.records = records; + } + + public Integer getTotal() { + return total; + } + + public void setTotal(Integer total) { + this.total = total; + } + +} diff --git a/src/main/java/com/ffii/core/support/AbstractBaseEntityService.java b/src/main/java/com/ffii/core/support/AbstractBaseEntityService.java new file mode 100644 index 0000000..1a75e6e --- /dev/null +++ b/src/main/java/com/ffii/core/support/AbstractBaseEntityService.java @@ -0,0 +1,42 @@ +package com.ffii.core.support; + +import java.io.Serializable; +import java.util.Optional; + +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import com.ffii.core.entity.BaseEntity; +import com.ffii.core.exception.ConflictException; + +/** @author Alex */ +public abstract class AbstractBaseEntityService, ID extends Serializable, R extends AbstractRepository> + extends AbstractIdEntityService { + + public AbstractBaseEntityService(JdbcDao jdbcDao, R repository) { + super(jdbcDao, repository); + } + + /** find and check versionId */ + public Optional find(ID id, int version) { + Assert.notNull(id, "id must not be null"); + return repository.findById(id) + .map(entity -> { + if (entity.getVersion() != version) throw new ConflictException("OPTIMISTIC_LOCK"); + return entity; + }); + } + + @Transactional(rollbackFor = Exception.class) + public void markDelete(ID id) { + Assert.notNull(id, "id must not be null"); + find(id).ifPresent(t -> markDelete(t)); + } + + @Transactional(rollbackFor = Exception.class) + public void markDelete(T entity) { + Assert.notNull(entity, "entity must not be null"); + entity.setDeleted(Boolean.TRUE); + save(entity); + } +} diff --git a/src/main/java/com/ffii/core/support/AbstractIdEntityService.java b/src/main/java/com/ffii/core/support/AbstractIdEntityService.java new file mode 100644 index 0000000..30612b1 --- /dev/null +++ b/src/main/java/com/ffii/core/support/AbstractIdEntityService.java @@ -0,0 +1,65 @@ +package com.ffii.core.support; + +import java.io.Serializable; +import java.util.List; +import java.util.Optional; + +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import com.ffii.core.entity.IdEntity; + +/** @author Alex */ +public abstract class AbstractIdEntityService, ID extends Serializable, R extends AbstractRepository> + extends AbstractService { + + protected R repository; + + public AbstractIdEntityService(JdbcDao jdbcDao, R repository) { + super(jdbcDao); + this.repository = repository; + } + + @Transactional(rollbackFor = Exception.class) + public T save(T entity) { + Assert.notNull(entity, "entity must not be null"); + return this.repository.save(entity); + } + + @Transactional(rollbackFor = Exception.class) + public T saveAndFlush(T entity) { + Assert.notNull(entity, "entity must not be null"); + return this.repository.saveAndFlush(entity); + } + + public List listAll() { + return this.repository.findAll(); + } + + public Optional find(ID id) { + Assert.notNull(id, "id must not be null"); + return this.repository.findById(id); + } + + public boolean existsById(ID id) { + Assert.notNull(id, "id must not be null"); + return this.repository.existsById(id); + } + + public List findAllByIds(List ids) { + Assert.notNull(ids, "ids must not be null"); + return this.repository.findAllById(ids); + } + + @Transactional(rollbackFor = Exception.class) + public void delete(ID id) { + Assert.notNull(id, "id must not be null"); + this.repository.deleteById(id); + } + + @Transactional(rollbackFor = Exception.class) + public void delete(T entity) { + Assert.notNull(entity, "entity must not be null"); + this.repository.delete(entity); + } +} diff --git a/src/main/java/com/ffii/core/support/AbstractRepository.java b/src/main/java/com/ffii/core/support/AbstractRepository.java new file mode 100644 index 0000000..3606539 --- /dev/null +++ b/src/main/java/com/ffii/core/support/AbstractRepository.java @@ -0,0 +1,16 @@ +package com.ffii.core.support; + +import java.io.Serializable; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.NoRepositoryBean; + +import com.ffii.core.entity.IdEntity; + +/** + * @author Alex + * @see https://docs.spring.io/spring-data/jpa/docs/2.7.0/reference/html/#jpa.query-methods.query-creation + */ +@NoRepositoryBean +public interface AbstractRepository, ID extends Serializable> extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/ffii/core/support/AbstractService.java b/src/main/java/com/ffii/core/support/AbstractService.java new file mode 100644 index 0000000..855e504 --- /dev/null +++ b/src/main/java/com/ffii/core/support/AbstractService.java @@ -0,0 +1,15 @@ +package com.ffii.core.support; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** @author Terence */ +public abstract class AbstractService { + protected final Log logger = LogFactory.getLog(getClass()); + + protected JdbcDao jdbcDao; + + public AbstractService(JdbcDao jdbcDao) { + this.jdbcDao = jdbcDao; + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/core/support/ErrorHandler.java b/src/main/java/com/ffii/core/support/ErrorHandler.java new file mode 100644 index 0000000..c8f5c10 --- /dev/null +++ b/src/main/java/com/ffii/core/support/ErrorHandler.java @@ -0,0 +1,44 @@ +package com.ffii.core.support; + +import java.util.UUID; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +import com.ffii.core.exception.ConflictException; +import com.ffii.core.exception.InternalServerErrorException; +import com.ffii.core.response.ErrorRes; +import com.ffii.core.response.FailureRes; + +@RestControllerAdvice +public class ErrorHandler extends ResponseEntityExceptionHandler { + private final Log logger = LogFactory.getLog(getClass()); + + @ExceptionHandler({ ConflictException.class, ResponseStatusException.class }) + public ResponseEntity error409422(final Exception ex) { + ResponseStatusException e = (ResponseStatusException) ex; + return new ResponseEntity<>(new FailureRes(e.getReason()), e.getStatusCode()); + } + + @ExceptionHandler(AccessDeniedException.class) + public ResponseEntity error403(final Exception ex) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); + } + + @ExceptionHandler({ InternalServerErrorException.class, Exception.class }) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ResponseEntity error500(final Exception ex) { + UUID traceId = UUID.randomUUID(); + logger.error("traceId: " + traceId, ex); + return new ResponseEntity<>(new ErrorRes(traceId.toString()), HttpStatus.INTERNAL_SERVER_ERROR); + } + +} diff --git a/src/main/java/com/ffii/core/support/JdbcDao.java b/src/main/java/com/ffii/core/support/JdbcDao.java new file mode 100644 index 0000000..15b1841 --- /dev/null +++ b/src/main/java/com/ffii/core/support/JdbcDao.java @@ -0,0 +1,463 @@ +package com.ffii.core.support; + +import java.math.BigDecimal; +import java.sql.Blob; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.sql.DataSource; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.dao.IncorrectResultSizeDataAccessException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.jdbc.IncorrectResultSetColumnCountException; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; + +/** @author Terence */ +public class JdbcDao { + + private NamedParameterJdbcTemplate template; + + public JdbcDao(DataSource dataSource) { + this.template = new NamedParameterJdbcTemplate(dataSource); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public String queryForString(String sql) { + return this.queryForString(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public String queryForString(String sql, Map paramMap) { + try { + return this.template.queryForObject(sql, paramMap, String.class); + } catch (EmptyResultDataAccessException e) { + return StringUtils.EMPTY; + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public String queryForString(String sql, Object paramObj) { + try { + return this.template.queryForObject(sql, new BeanPropertySqlParameterSource(paramObj), String.class); + } catch (EmptyResultDataAccessException e) { + return StringUtils.EMPTY; + } + } + + /** + * @return {@code true} if non-zero, {@code false} if zero + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public boolean queryForBoolean(String sql) { + return this.queryForBoolean(sql, (Map) null); + } + + /** + * @return {@code true} if non-zero, {@code false} if zero + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public boolean queryForBoolean(String sql, Map paramMap) { + try { + var rs = this.template.queryForObject(sql, paramMap, Boolean.class); + return rs == null ? false : rs; + } catch (EmptyResultDataAccessException e) { + return false; + } + } + + /** + * @return {@code true} if non-zero, {@code false} if zero + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public boolean queryForBoolean(String sql, Object paramObj) { + try { + var rs = this.template.queryForObject(sql, new BeanPropertySqlParameterSource(paramObj), Boolean.class); + return rs == null ? false : rs; + } catch (EmptyResultDataAccessException e) { + return false; + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public int queryForInt(String sql) { + return this.queryForInt(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public int queryForInt(String sql, Map paramMap) { + try { + var rs = this.template.queryForObject(sql, paramMap, Integer.class); + return rs == null ? 0 : rs; + } catch (EmptyResultDataAccessException e) { + return 0; + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public int queryForInt(String sql, Object paramObj) { + try { + var rs = this.template.queryForObject(sql, + new BeanPropertySqlParameterSource(paramObj), Integer.class); + return rs == null ? 0 : rs; + } catch (EmptyResultDataAccessException e) { + return 0; + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public BigDecimal queryForDecimal(String sql) { + return this.queryForDecimal(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public BigDecimal queryForDecimal(String sql, Map paramMap) { + try { + return this.template.queryForObject(sql, paramMap, BigDecimal.class); + } catch (EmptyResultDataAccessException e) { + return BigDecimal.ZERO; + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public BigDecimal queryForDecimal(String sql, Object paramObj) { + try { + return this.template.queryForObject(sql, + new BeanPropertySqlParameterSource(paramObj), BigDecimal.class); + } catch (EmptyResultDataAccessException e) { + return BigDecimal.ZERO; + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public Optional queryForEntity(String sql, Class entity) { + return this.queryForEntity(sql, (Map) null, entity); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public Optional queryForEntity(String sql, Map paramMap, Class entity) { + try { + return Optional.of(this.template.queryForObject(sql, paramMap, + new BeanPropertyRowMapper(entity))); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSizeDataAccessException: Incorrect result size + */ + public Optional queryForEntity(String sql, Object paramObj, Class entity) { + try { + return Optional.of(this.template.queryForObject(sql, + new BeanPropertySqlParameterSource(paramObj), new BeanPropertyRowMapper(entity))); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + /** + * @throws BadSqlGrammarException sql error + */ + public List queryForList(String sql, Class entity) { + return this.queryForList(sql, (Map) null, entity); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public List queryForList(String sql, Map paramMap, Class entity) { + return this.template.query(sql, paramMap, new BeanPropertyRowMapper(entity)); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public List queryForList(String sql, Object paramObj, Class entity) { + return this.template.query(sql, new BeanPropertySqlParameterSource(paramObj), + new BeanPropertyRowMapper(entity)); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForInts(String sql) { + return this.queryForInts(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForInts(String sql, Map paramMap) { + return this.template.queryForList(sql, paramMap, Integer.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForInts(String sql, Object paramObj) { + return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), Integer.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForDates(String sql) { + return this.queryForDates(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForDates(String sql, Map paramMap) { + return this.template.queryForList(sql, paramMap, LocalDate.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForDates(String sql, Object paramObj) { + return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), LocalDate.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForDatetimes(String sql) { + return this.queryForDatetimes(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForDatetimes(String sql, Map paramMap) { + return this.template.queryForList(sql, paramMap, LocalDateTime.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForDatetimes(String sql, Object paramObj) { + return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), LocalDateTime.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForStrings(String sql) { + return this.queryForStrings(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForStrings(String sql, Map paramMap) { + return this.template.queryForList(sql, paramMap, String.class); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + * @throws IncorrectResultSetColumnCountException Incorrect column count + */ + public List queryForStrings(String sql, Object paramObj) { + return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj), String.class); + } + + /** + * @throws BadSqlGrammarException sql error + */ + public List> queryForList(String sql) { + return this.queryForList(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public List> queryForList(String sql, Map paramMap) { + return this.template.queryForList(sql, paramMap); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public List> queryForList(String sql, Object paramObj) { + return this.template.queryForList(sql, new BeanPropertySqlParameterSource(paramObj)); + } + + /** + * @throws BadSqlGrammarException sql error + */ + public Optional> queryForMap(String sql) { + return this.queryForMap(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public Optional> queryForMap(String sql, Map paramMap) { + try { + return Optional.of(this.template.queryForMap(sql, paramMap)); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public Optional> queryForMap(String sql, Object paramObj) { + try { + return Optional.of(this.template.queryForMap(sql, new BeanPropertySqlParameterSource(paramObj))); + } catch (EmptyResultDataAccessException e) { + return Optional.empty(); + } + } + + /** + * @throws BadSqlGrammarException sql error + */ + public int executeUpdate(String sql) { + return this.executeUpdate(sql, (Map) null); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public int executeUpdate(String sql, Map paramMap) { + return this.template.update(sql, paramMap); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public long executeUpdateAndReturnId(String sql, Map paramMap) { + KeyHolder keyHolder = new GeneratedKeyHolder(); + this.template.update(sql, new MapSqlParameterSource(paramMap), keyHolder); + Number generatedId = keyHolder.getKey(); + if (generatedId != null) { + return generatedId.longValue(); + } + return 0; + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public int executeUpdate(String sql, Object paramObj) { + return this.template.update(sql, new BeanPropertySqlParameterSource(paramObj)); + } + + /** + * @throws BadSqlGrammarException sql error + * @throws InvalidDataAccessApiUsageException params missing when needed + */ + public int[] batchUpdate(String sql, List paramsMapOrObject) { + return this.template.batchUpdate(sql, SqlParameterSourceUtils.createBatch(paramsMapOrObject)); + } + + public Blob queryForBlob(String sql, Map paramMap) { + try { + return this.template.queryForObject(sql, paramMap, Blob.class); + } catch (EmptyResultDataAccessException e) { + return null; + } + } +} diff --git a/src/main/java/com/ffii/core/utils/AES.java b/src/main/java/com/ffii/core/utils/AES.java new file mode 100644 index 0000000..89830e7 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/AES.java @@ -0,0 +1,85 @@ +package com.ffii.core.utils; + +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class AES { + + protected final Log logger = LogFactory.getLog(getClass()); + + private static SecretKeySpec secretKey; + private static byte[] key; + + public static void setKey(String myKey) { + MessageDigest sha = null; + try { + key = myKey.getBytes("UTF-8"); + sha = MessageDigest.getInstance("SHA-1"); + key = sha.digest(key); + key = Arrays.copyOf(key, 16); + secretKey = new SecretKeySpec(key, "AES"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } + + public static String encrypt(String strToEncrypt, String secret) { + try { + setKey(secret); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return Base64.getEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))); + } catch (Exception e) { + System.out.println("Error while encrypting: " + e.toString()); + } + return null; + } + + public static String urlEncrypt(String strToEncrypt, String secret) { + try { + setKey(secret); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + return Base64.getUrlEncoder().encodeToString(cipher.doFinal(strToEncrypt.getBytes("UTF-8"))); + } catch (Exception e) { + System.out.println("Error while encrypting: " + e.toString()); + } + return null; + } + + public static String decrypt(String strToDecrypt, String secret) { + try { + setKey(secret); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt)), "UTF-8"); + } catch (Exception e) { + System.out.println("Error while decrypting: " + e.toString()); + } + return null; + } + + public static String urlDecrypt(String strToDecrypt, String secret) { + try { + setKey(secret); + System.out.println("strToDecrypt: " + strToDecrypt); + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING"); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + return new String(cipher.doFinal(Base64.getUrlDecoder().decode(strToDecrypt)), "UTF-8"); + } catch (Exception e) { + System.out.println("Error while decrypting: " + e.toString()); + } + return null; + } +} diff --git a/src/main/java/com/ffii/core/utils/BeanUtils.java b/src/main/java/com/ffii/core/utils/BeanUtils.java new file mode 100644 index 0000000..68f6dc9 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/BeanUtils.java @@ -0,0 +1,33 @@ +package com.ffii.core.utils; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import org.springframework.beans.BeansException; + +import com.ffii.core.entity.BaseEntity; + +public class BeanUtils extends org.springframework.beans.BeanUtils { + + @SuppressWarnings("unchecked") + public static void copyProperties(Object source, Object target) throws BeansException { + boolean validId = false; + Field idField = null; + Method getIdMethod = null; + + try { + idField = source.getClass().getDeclaredField("id"); + getIdMethod = source.getClass().getMethod("getId"); + + validId = idField != null && getIdMethod != null && (Long) getIdMethod.invoke(source) > 0L; + } catch (Exception e) { + + } + + if ((source instanceof BaseEntity && (((BaseEntity) source).getId() == null || ((BaseEntity) source).getId() <= 0L)) || !validId) { + org.springframework.beans.BeanUtils.copyProperties(source, target, "id"); + } else { + org.springframework.beans.BeanUtils.copyProperties(source, target); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java b/src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java new file mode 100644 index 0000000..c1c556f --- /dev/null +++ b/src/main/java/com/ffii/core/utils/CriteriaArgsBuilder.java @@ -0,0 +1,254 @@ +package com.ffii.core.utils; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.ServletRequestUtils; + +import jakarta.servlet.http.HttpServletRequest; + +/** @author Alex */ +public class CriteriaArgsBuilder { + + private HttpServletRequest request; + private Map args; + private final Log logger = LogFactory.getLog(getClass()); + + private CriteriaArgsBuilder(HttpServletRequest request, Map args) { + this.args = args; + this.request = request; + } + + public static CriteriaArgsBuilder withRequest(HttpServletRequest request) { + return new CriteriaArgsBuilder(request, new HashMap()); + } + + public static CriteriaArgsBuilder withRequestNMap(HttpServletRequest request, Map args) { + return new CriteriaArgsBuilder(request, args); + } + + public CriteriaArgsBuilder addStringExact(String paramName) throws ServletRequestBindingException { + String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); + if (value != null) + args.put(paramName, value); + return this; + } + + public CriteriaArgsBuilder addStringLike(String paramName) throws ServletRequestBindingException { + String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); + if (value != null) + args.put(paramName, "%" + value + "%"); + return this; + } + + public CriteriaArgsBuilder addString(String paramName) throws ServletRequestBindingException { + return this.addStringExact(paramName); + } + + public CriteriaArgsBuilder addStringStartsWith(String paramName) throws ServletRequestBindingException { + String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); + if (value != null) + args.put(paramName, value + "%"); + return this; + } + + public CriteriaArgsBuilder addStringEndsWith(String paramName) throws ServletRequestBindingException { + String value = StringUtils.trimToNull(ServletRequestUtils.getStringParameter(this.request, paramName)); + if (value != null) + args.put(paramName, "%" + value); + return this; + } + + public CriteriaArgsBuilder addStringList(String paramName) throws ServletRequestBindingException { + String[] params = ServletRequestUtils.getStringParameters(this.request, paramName); + if (params.length > 0) { + List value = new ArrayList(params.length); + for (String param : params) + if (StringUtils.isNotBlank(param)) + value.add(param); + if (value.size() > 0) + args.put(paramName, value); + } + return this; + } + + public CriteriaArgsBuilder addStringCsv(String paramName) throws ServletRequestBindingException { + String text = ServletRequestUtils.getStringParameter(this.request, paramName); + if (text != null && StringUtils.isNotEmpty(text)) + args.put(paramName, Arrays.asList(text.split(","))); + return this; + } + + public CriteriaArgsBuilder addInteger(String paramName) throws ServletRequestBindingException { + Integer value = StringUtils.isNotBlank(this.request.getParameter(paramName)) + ? ServletRequestUtils.getRequiredIntParameter(request, paramName) + : null; + if (value != null) + args.put(paramName, value); + return this; + } + + public CriteriaArgsBuilder addNonZeroInteger(String paramName) throws ServletRequestBindingException { + Integer value = StringUtils.isNotBlank(this.request.getParameter(paramName)) + ? ServletRequestUtils.getRequiredIntParameter(request, paramName) + : null; + if (value != null && value.intValue() != 0) + args.put(paramName, value); + return this; + } + + public CriteriaArgsBuilder addIntegerList(String paramName) throws ServletRequestBindingException { + int[] params = ServletRequestUtils.getIntParameters(request, paramName); + if (params.length > 0) { + List values = new ArrayList(); + for (int param : params) + values.add(param); + args.put(paramName, values); + } + return this; + } + + public CriteriaArgsBuilder addIntegerListString(String paramName) throws ServletRequestBindingException { + int[] params = ServletRequestUtils.getIntParameters(request, paramName); + if (params.length > 0) { + args.put(paramName, Arrays.toString(params)); + } + return this; + } + + public CriteriaArgsBuilder addNonZeroIntegerList(String paramName) throws ServletRequestBindingException { + int[] params = ServletRequestUtils.getIntParameters(request, paramName); + if (params.length > 0) { + List values = new ArrayList(); + for (int param : params) + if (param != 0) + values.add(param); + args.put(paramName, values); + } + return this; + } + + public CriteriaArgsBuilder addLong(String paramName) throws ServletRequestBindingException { + Long value = StringUtils.isNotBlank(this.request.getParameter(paramName)) + ? ServletRequestUtils.getRequiredLongParameter(request, paramName) + : null; + if (value != null) + args.put(paramName, value); + return this; + } + + public CriteriaArgsBuilder addNonZeroLong(String paramName) throws ServletRequestBindingException { + Long value = StringUtils.isNotBlank(this.request.getParameter(paramName)) + ? ServletRequestUtils.getRequiredLongParameter(request, paramName) + : null; + if (value != null && value.longValue() != 0L) + args.put(paramName, value); + return this; + } + + public CriteriaArgsBuilder addDatetime(String paramName) throws ServletRequestBindingException { + String value = ServletRequestUtils.getStringParameter(request, paramName); + if (StringUtils.isNotBlank(value)) { + try { + args.put(paramName, LocalDateTime.parse(value)); + } catch (DateTimeParseException e) { + throw new ServletRequestBindingException(paramName); + } + } + return this; + } + + public CriteriaArgsBuilder addDatetime(String paramName, DateTimeFormatter formatter) + throws ServletRequestBindingException { + String value = ServletRequestUtils.getStringParameter(request, paramName); + if (StringUtils.isNotBlank(value)) { + try { + args.put(paramName, LocalDateTime.parse(value, formatter)); + } catch (DateTimeParseException e) { + throw new ServletRequestBindingException(paramName); + } + } + return this; + } + + public CriteriaArgsBuilder addDate(String paramName) throws ServletRequestBindingException { + String value = ServletRequestUtils.getStringParameter(request, paramName); + if (StringUtils.isNotBlank(value)) { + try { + args.put(paramName, LocalDate.parse(value)); + } catch (DateTimeParseException e) { + throw new ServletRequestBindingException(paramName); + } + } + return this; + } + + public CriteriaArgsBuilder addDate(String paramName, DateTimeFormatter formatter) + throws ServletRequestBindingException { + String value = ServletRequestUtils.getStringParameter(request, paramName); + if (StringUtils.isNotBlank(value)) { + try { + args.put(paramName, LocalDate.parse(value, formatter)); + } catch (DateTimeParseException e) { + throw new ServletRequestBindingException(paramName); + } + } + return this; + } + + public CriteriaArgsBuilder addDateTo(String paramName) throws ServletRequestBindingException { + String value = ServletRequestUtils.getStringParameter(request, paramName); + if (StringUtils.isNotBlank(value)) { + try { + args.put(paramName, LocalDate.parse(value).plusDays(1)); + } catch (DateTimeParseException e) { + throw new ServletRequestBindingException(paramName); + } + } + return this; + } + + public CriteriaArgsBuilder addDateTo(String paramName, DateTimeFormatter formatter) + throws ServletRequestBindingException { + String value = ServletRequestUtils.getStringParameter(request, paramName); + if (StringUtils.isNotBlank(value)) { + try { + args.put(paramName, LocalDate.parse(value, formatter).plusDays(1)); + } catch (DateTimeParseException e) { + throw new ServletRequestBindingException(paramName); + } + } + return this; + } + + public CriteriaArgsBuilder addBoolean(String paramName) throws ServletRequestBindingException { + + if (request.getParameter(paramName) == null || request.getParameter(paramName).isEmpty()) { + return this; + } + Boolean value = ServletRequestUtils.getBooleanParameter(request, paramName); + args.put(paramName, value); + return this; + } + + public CriteriaArgsBuilder put(String key, Object value) { + args.put(key, value); + return this; + } + + public Map build() { + return this.args; + } +} diff --git a/src/main/java/com/ffii/core/utils/ExcelUtils.java b/src/main/java/com/ffii/core/utils/ExcelUtils.java new file mode 100644 index 0000000..b750b63 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/ExcelUtils.java @@ -0,0 +1,778 @@ +package com.ffii.core.utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.poi.openxml4j.exceptions.InvalidFormatException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.poifs.crypt.EncryptionInfo; +import org.apache.poi.poifs.crypt.EncryptionMode; +import org.apache.poi.poifs.crypt.Encryptor; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.RichTextString; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import jakarta.servlet.http.HttpServletResponse; + +public abstract class ExcelUtils { + + /** + * static A to Z char array + */ + private static final char[] A2Z = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z' }; + + private static final DataFormatter DATA_FORMATTER = new DataFormatter(); + + /** max rows limit of .xls **/ + public static final int MAX_ROWS = 65536; + /** max columns limit of .xls **/ + public static final int MAX_COLS = 256; + + /** + * Column reference to index (0-based) map, support up to 256 columns (compatible with .xls format) + */ + public static final Map COL_IDX = new HashMap(MAX_COLS, 1.0f); + + static { + for (int columnIndex = 0; columnIndex < MAX_COLS; columnIndex++) { + int tempColumnCount = columnIndex; + StringBuilder sb = new StringBuilder(2); + do { + sb.insert(0, A2Z[tempColumnCount % 26]); + tempColumnCount = (tempColumnCount / 26) - 1; + } while (tempColumnCount >= 0); + COL_IDX.put(sb.toString(), Integer.valueOf(columnIndex)); + } + } + + /** + * Load XSSF workbook (xlsx file) from template source. + * + * @param url + * the relative path to the template source, e.g. "WEB-INF/excel/exampleReportTemplate.xlsx" + * + * @return the workbook, or null if the template file cannot be loaded + */ + public static final Workbook loadXSSFWorkbookFromTemplateSource(ResourceLoader resourceLoader, String url) { + Resource resource = resourceLoader.getResource(url); + try { + return new XSSFWorkbook(resource.getInputStream()); + } catch (IOException e) { + return null; + } + } + + /** + * Write the workbook to byte array. + * + * @param workbook + * The Excel workbook (cannot be null) + * + * @return the byte[], or null if IO exception occurred + */ + public static final byte[] toByteArray(Workbook workbook) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + workbook.write(baos); + } catch (IOException e) { + return null; + } + return baos.toByteArray(); + } + + /** + * Check if the cell exists in the given sheet, row and column. + * + * @param sheet + * the Sheet (cannot be null) + * @param rowIndex + * 0-based row index + * @param colIndex + * 0-based column index + * + * @return {@code true} if cell exists, else {@code false} + */ + public static final boolean isCellExists(Sheet sheet, int rowIndex, int colIndex) { + Row row = sheet.getRow(rowIndex); + if (row != null) { + Cell cell = row.getCell(colIndex); + return cell != null; + } + return false; + } + + /** + * Convenient method to obtain the cell in the given sheet, row and column. + *

+ * Creates the row and the cell if not already exist. + * + * @param sheet + * the Sheet (cannot be null) + * @param rowIndex + * 0-based row index + * @param colIndex + * 0-based column index + * + * @return the Cell (never null) + */ + public static final Cell getCell(Sheet sheet, int rowIndex, int colIndex) { + Row row = sheet.getRow(rowIndex); + if (row == null) { + row = sheet.createRow(rowIndex); + } + Cell cell = row.getCell(colIndex); + if (cell == null) { + cell = row.createCell(colIndex); + } + return cell; + } + + /** + * Get column index by column reference (support up to 256 columns) + * + * @param columnRef + * column reference such as "A", "B", "AA", "AB"... + * + * @return the column index + * + * @throws NullPointerException + * if column reference is invalid or the index exceeds 256 + */ + public static final int getColumnIndex(String columnRef) { + return COL_IDX.get(columnRef); + } + + /** + * Get column reference by column index + * + * @param columnIndex + * 0-based column index + * + * @return the column reference such as "A", "B", "AA", "AB"... + */ + public static final String getColumnRef(int columnIndex) { + StringBuilder sb = new StringBuilder(); + int tempColumnCount = columnIndex; + do { + sb.insert(0, A2Z[tempColumnCount % 26]); + tempColumnCount = (tempColumnCount / 26) - 1; + } while (tempColumnCount >= 0); + return sb.toString(); + } + + /** + * Get the Excel Cell Ref String by columnIndex and rowIndex + * + * @param columnIndex + * 0-based column index + * @param rowIndex + * 0-based row index + */ + public static final String getCellRefString(int columnIndex, int rowIndex) { + StringBuilder sb = new StringBuilder(); + int tempColumnCount = columnIndex; + do { + sb.insert(0, A2Z[tempColumnCount % 26]); + tempColumnCount = (tempColumnCount / 26) - 1; + } while (tempColumnCount >= 0); + sb.append(rowIndex + 1); + return sb.toString(); + } + + /** + * Get Cell value as String + */ + public static String getStringValue(Cell cell) { + if (cell != null && cell.getCellType() == CellType.FORMULA) { + try { + return cell.getStringCellValue(); + } catch (Exception e) { + return ""; + } + } + return DATA_FORMATTER.formatCellValue(cell); + } + + /** + * Get Cell value as BigDecimal, with a fallback value + *

+ * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} + * + * @return the BigDecimal value, or the default value if cell is null or cell type is {@link CellType#BLANK} + */ + public static BigDecimal getDecimalValue(Cell cell, BigDecimal defaultValue) { + if (cell == null || cell.getCellType() == CellType.BLANK) return defaultValue; + if (cell.getCellType() == CellType.STRING) { + return new BigDecimal(cell.getStringCellValue()); + } else { + return BigDecimal.valueOf(cell.getNumericCellValue()); + } + } + + /** + * Get Cell value as BigDecimal + *

+ * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} + * + * @return the BigDecimal value, or BigDecimal.ZERO if cell is null or cell type is {@link CellType#BLANK} + */ + public static BigDecimal getDecimalValue(Cell cell) { + return getDecimalValue(cell, BigDecimal.ZERO); + } + + /** + * Get Cell value as double + *

+ * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} + */ + public static double getDoubleValue(Cell cell) { + if (cell == null) return 0.0; + if (cell.getCellType() == CellType.STRING) { + return NumberUtils.toDouble(cell.getStringCellValue()); + } else { + return cell.getNumericCellValue(); + } + } + + /** + * Get Cell value as int (rounded half-up to the nearest integer) + *

+ * Only support {@link CellType#NUMERIC} and {@link CellType#STRING} + */ + public static int getIntValue(Cell cell) { + return BigDecimal.valueOf(getDoubleValue(cell)).setScale(0, RoundingMode.HALF_UP).intValue(); + } + + /** + * Get Cell Integer value (truncated) + */ + public static Integer getIntValue(Cell cell, Integer defaultValue) { + if (cell == null) return defaultValue; + if (cell.getCellType() == CellType.STRING) { + return NumberUtils.toInt(cell.getStringCellValue(), defaultValue); + } else { + return (int) cell.getNumericCellValue(); + } + } + + public static LocalDate getDateValue(Cell cell, DateTimeFormatter formatter) { + if (cell == null) return null; + if (cell.getCellType() == CellType.STRING) { + try { + return LocalDate.parse(cell.getStringCellValue(), formatter); + } catch (DateTimeParseException e) { + return null; + } + } + if (DateUtil.isCellDateFormatted(cell)) { + try { + return DateUtil.getJavaDate(cell.getNumericCellValue()).toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate(); + } catch (NumberFormatException e) { + return null; + } + } else { + return null; + } + } + + public static LocalDateTime getDatetimeValue(Cell cell, DateTimeFormatter formatter) { + if (cell == null) return null; + if (cell.getCellType() == CellType.STRING) { + try { + return LocalDateTime.parse(cell.getStringCellValue(), formatter); + } catch (DateTimeParseException e) { + return null; + } + } + if (DateUtil.isCellDateFormatted(cell)) { + try { + return DateUtil.getJavaDate(cell.getNumericCellValue()).toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime(); + } catch (NumberFormatException e) { + return null; + } + } else { + return null; + } + } + + /** + * Convenient method to set Cell value + * + * @param cell + * the Cell (cannot be null) + * @param value + * the value to set + */ + public static void setCellValue(Cell cell, Object value) { + if (value instanceof String) + cell.setCellValue((String) value); + else if (value instanceof RichTextString) + cell.setCellValue((RichTextString) value); + else if (value instanceof Number) + cell.setCellValue(((Number) value).doubleValue()); + else if (value instanceof Boolean) + cell.setCellValue(((Boolean) value).booleanValue()); + else if (value instanceof Calendar) + cell.setCellValue((Calendar) value); + else if (value instanceof Date) + cell.setCellValue((Date) value); + else if (value instanceof LocalDate) + cell.setCellValue((LocalDate) value); + else if (value instanceof LocalTime) + cell.setCellValue(((LocalTime) value).toString()); + else if (value instanceof LocalDateTime) + cell.setCellValue((LocalDateTime) value); + else if (value == null) + cell.setCellValue(""); + else + throw new IllegalArgumentException(value.getClass().toString() + " is not supported"); + } + + /** + * Convenient method to set Cell value by Sheet, row index, and column index + * + * @param sheet + * the Sheet (cannot be null) + * @param rowIndex + * 0-based row index + * @param colIndex + * 0-based column index + * @param value + * the value to set + */ + public static void setCellValue(Sheet sheet, int rowIndex, int colIndex, Object value) { + setCellValue(getCell(sheet, rowIndex, colIndex), value); + } + + /** + * Increase Row Height (if necessary, but never decrease it) by counting the no. of lines in a String value + * + * @param sheet + * The Excel worksheet + * @param row + * The row index (0-based) + * @param value + * The (multi-line) String value to count for the no. of lines + * @param heightInPoints + * The height (in points) for 1 line of text + */ + public static void increaseRowHeight(Sheet sheet, int row, String value, int heightInPoints) { + int lines = StringUtils.countMatches(value, "\n") + 1; // count no. of lines + float newHeight = heightInPoints * lines; + + Row r = sheet.getRow(row); + if (r == null) r = sheet.createRow(row); + + // increase the row height if necessary, but never decrease it + if (r.getHeightInPoints() < newHeight) { + r.setHeightInPoints(newHeight); + } + } + + /** + * Add merged region (i.e. merge cells) + * + * @param sheet + * The Excel worksheet + * @param firstRowIdx + * The first row index (0-based) + * @param lastRowIdx + * The last row index (0-based) + * @param firstColIdx + * The first column index (0-based) + * @param lastColIdx + * The last column index (0-based) + */ + public static void addMergedRegion(Sheet sheet, int firstRowIdx, int lastRowIdx, int firstColIdx, int lastColIdx) { + CellRangeAddress cellRangeAddress = new CellRangeAddress(firstRowIdx, lastRowIdx, firstColIdx, lastColIdx); + sheet.addMergedRegion(cellRangeAddress); + } + + /** + * Copy and Insert Row + * + * @param workbook + * The Excel workbook + * @param sourceSheet + * The source Excel worksheet + * @param destinationSheet + * The destination Excel worksheet + * @param sourceRowNum + * The source row index (0-based) to copy from + * @param destinationRowNum + * The destination row index (0-based) to insert into (from the copied row) + */ + public static void copyAndInsertRow(Workbook workbook, Sheet sourceSheet, Sheet destinationSheet, int sourceRowNum, int destinationRowNum) { + // get the source / destination row + Row sourceRow = sourceSheet.getRow(sourceRowNum); + Row destRow = destinationSheet.getRow(destinationRowNum); + + // if the row exist in destination, push down all rows by 1 + if (destRow != null) { + destinationSheet.shiftRows(destinationRowNum, destinationSheet.getLastRowNum(), 1, true, false); + } + // create a new row + destRow = destinationSheet.createRow(destinationRowNum); + + // loop through source columns to add to new row + for (int i = 0; i < sourceRow.getLastCellNum(); i++) { + // grab a copy of the old cell + Cell oldCell = sourceRow.getCell(i); + + // if the old cell is null jump to next cell + if (oldCell == null) continue; + + // create a new cell in destination row + Cell newCell = destRow.createCell(i); + + // apply cell style to new cell from old cell + newCell.setCellStyle(oldCell.getCellStyle()); + + // if there is a cell comment, copy + if (oldCell.getCellComment() != null) { + newCell.setCellComment(oldCell.getCellComment()); + } + + // if there is a cell hyperlink, copy + if (oldCell.getHyperlink() != null) { + newCell.setHyperlink(oldCell.getHyperlink()); + } + + // copy the cell data value + switch (oldCell.getCellType()) { + case NUMERIC: + newCell.setCellValue(oldCell.getNumericCellValue()); + break; + case STRING: + newCell.setCellValue(oldCell.getRichStringCellValue()); + break; + case FORMULA: + newCell.setCellFormula(oldCell.getCellFormula()); + break; + case BLANK: + newCell.setCellValue(oldCell.getStringCellValue()); + break; + case BOOLEAN: + newCell.setCellValue(oldCell.getBooleanCellValue()); + break; + case ERROR: + newCell.setCellErrorValue(oldCell.getErrorCellValue()); + break; + default: + break; + } + } + + // if there are any merged regions in the source row, copy to new row + for (int i = 0; i < sourceSheet.getNumMergedRegions(); i++) { + CellRangeAddress cellRangeAddress = sourceSheet.getMergedRegion(i); + if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) { + addMergedRegion( + destinationSheet, + destRow.getRowNum(), + (destRow.getRowNum() + (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())), + cellRangeAddress.getFirstColumn(), + cellRangeAddress.getLastColumn()); + } + } + + // copy row height + destRow.setHeight(sourceRow.getHeight()); + } + + /** + * Copy and Insert Row + * + * @param workbook + * The Excel workbook + * @param sheet + * The Excel worksheet + * @param sourceRowNum + * The source row index (0-based) to copy from + * @param destinationRowNum + * The destination row index (0-based) to insert into (from the copied row) + */ + public static void copyAndInsertRow(Workbook workbook, Sheet sheet, int sourceRowNum, int destinationRowNum) { + copyAndInsertRow(workbook, sheet, sheet, sourceRowNum, destinationRowNum); + } + + public static void copyAndInsertRow(Workbook workbook, Sheet sourceSheet, int sourceRowNum, int destinationRowNum, int times) { + // get the source / destination row + Row sourceRow = sourceSheet.getRow(sourceRowNum); + + Row[] destRows = new Row[times]; + for (int j = 0; j < times; j++) { + Row destRow = sourceSheet.getRow(destinationRowNum + j); + // if the row exist in destination, push down all rows by 1 + if (destRow != null) { + sourceSheet.shiftRows(destinationRowNum + j, sourceSheet.getLastRowNum(), 1, true, false); + } + // create a new row + destRows[j] = sourceSheet.createRow(destinationRowNum + j); + // copy row height + destRows[j].setHeight(sourceRow.getHeight()); + } + + // loop through source columns to add to new row + for (int i = 0; i < sourceRow.getLastCellNum(); i++) { + // grab a copy of the old cell + Cell oldCell = sourceRow.getCell(i); + + // if the old cell is null jump to next cell + if (oldCell == null) continue; + + for (int k = 0; k < times; k++) { + // create a new cell in destination row + Cell newCell = destRows[k].createCell(i); + + // apply cell style to new cell from old cell + newCell.setCellStyle(oldCell.getCellStyle()); + } + } + } + + /** + * Copy Column + * + * @param workbook + * The Excel workbook + * @param sourceSheet + * The source Excel worksheet + * @param destinationSheet + * The destination Excel worksheet + * @param rowStart + * The source row start index (0-based) to copy from + * @param rowEnd + * The source row end index (0-based) to copy from + * @param sourceColumnNum + * The source column index (0-based) to copy from + * @param destinationColumnNum + * The destination column index (0-based) to copy into (from the copied row) + */ + public static void copyColumn(Workbook workbook, Sheet sourceSheet, Sheet destinationSheet, int rowStart, int rowEnd, int sourceColumnNum, + int destinationColumnNum) { + for (int i = rowStart; i <= rowEnd; i++) { + Row sourceRow = sourceSheet.getRow(i); + if (sourceRow == null) continue; + + Row destinationRow = destinationSheet.getRow(i); + if (destinationRow == null) destinationRow = destinationSheet.createRow(i); + + Cell oldCell = sourceRow.getCell(sourceColumnNum); + if (oldCell == null) continue; + + Cell newCell = destinationRow.createCell(destinationColumnNum); + + newCell.setCellStyle(oldCell.getCellStyle()); + + if (oldCell.getCellComment() != null) { + newCell.setCellComment(oldCell.getCellComment()); + } + + if (oldCell.getHyperlink() != null) { + newCell.setHyperlink(oldCell.getHyperlink()); + } + + switch (oldCell.getCellType()) { + case NUMERIC: + newCell.setCellValue(oldCell.getNumericCellValue()); + break; + case STRING: + newCell.setCellValue(oldCell.getRichStringCellValue()); + break; + case FORMULA: + newCell.setCellFormula(oldCell.getCellFormula()); + break; + case BLANK: + newCell.setCellValue(oldCell.getStringCellValue()); + break; + case BOOLEAN: + newCell.setCellValue(oldCell.getBooleanCellValue()); + break; + case ERROR: + newCell.setCellErrorValue(oldCell.getErrorCellValue()); + break; + default: + break; + } + + for (int ii = 0; ii < sourceSheet.getNumMergedRegions(); ii++) { + CellRangeAddress cellRangeAddress = sourceSheet.getMergedRegion(ii); + if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) { + addMergedRegion( + destinationSheet, + cellRangeAddress.getFirstRow(), + cellRangeAddress.getLastRow(), + destinationColumnNum, + (destinationColumnNum + (cellRangeAddress.getLastColumn() - cellRangeAddress.getFirstColumn()))); + } + } + } + + destinationSheet.setColumnWidth(destinationColumnNum, sourceSheet.getColumnWidth(sourceColumnNum)); + } + + /** + * Copy Column + * + * @param workbook + * The Excel workbook + * @param sheet + * The Excel worksheet + * @param rowStart + * The source row start index (0-based) to copy from + * @param rowEnd + * The source row end index (0-based) to copy from + * @param sourceColumnNum + * The source column index (0-based) to copy from + * @param destinationColumnNum + * The destination column index (0-based) to copy into (from the copied row) + */ + public static void copyColumn(Workbook workbook, Sheet sheet, int rowStart, int rowEnd, int sourceColumnNum, int destinationColumnNum) { + copyColumn(workbook, sheet, sheet, rowStart, rowEnd, sourceColumnNum, destinationColumnNum); + } + + public static void shiftColumns(Row row, int startingIndex, int shiftCount) { + for (int i = row.getPhysicalNumberOfCells() - 1; i >= startingIndex; i--) { + Cell oldCell = row.getCell(i); + Cell newCell = row.createCell(i + shiftCount); + + // apply cell style to new cell from old cell + newCell.setCellStyle(oldCell.getCellStyle()); + + // if there is a cell comment, copy + if (oldCell.getCellComment() != null) { + newCell.setCellComment(oldCell.getCellComment()); + } + + // if there is a cell hyperlink, copy + if (oldCell.getHyperlink() != null) { + newCell.setHyperlink(oldCell.getHyperlink()); + } + + // copy the cell data value + switch (oldCell.getCellType()) { + case NUMERIC: + newCell.setCellValue(oldCell.getNumericCellValue()); + break; + case STRING: + newCell.setCellValue(oldCell.getRichStringCellValue()); + break; + case FORMULA: + newCell.setCellFormula(oldCell.getCellFormula()); + break; + case BLANK: + newCell.setCellValue(oldCell.getStringCellValue()); + break; + case BOOLEAN: + newCell.setCellValue(oldCell.getBooleanCellValue()); + break; + case ERROR: + newCell.setCellErrorValue(oldCell.getErrorCellValue()); + break; + default: + break; + } + } + } + + /** handle some invalid char included ( /\*[]:? ) */ + public static void setSheetName(Workbook workbook, Sheet sheet, String name) { + if (workbook != null && sheet != null && StringUtils.isNotBlank(name)) + workbook.setSheetName(workbook.getSheetIndex(sheet), name.replaceAll("[/\\\\*\\[\\]:\\?]", "_")); + } + + /** delete row */ + public static void deleteRow(Sheet sheet, int rowIndex) { + if (sheet != null) { + sheet.removeRow(sheet.getRow(rowIndex)); + if (rowIndex < sheet.getLastRowNum()) + sheet.shiftRows(rowIndex, sheet.getLastRowNum(), -1); + } + } + + public static byte[] encrypt(Workbook workbook, String password) { + return encrypt(toByteArray(workbook), password); + } + + public static byte[] encrypt(byte[] bytes, String password) { + try { + POIFSFileSystem fs = new POIFSFileSystem(); + EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile); + // EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile, CipherAlgorithm.aes192, HashAlgorithm.sha384, -1, -1, null); + + Encryptor enc = info.getEncryptor(); + enc.confirmPassword(password); + + // Read in an existing OOXML file and write to encrypted output stream + // don't forget to close the output stream otherwise the padding bytes aren't added + OPCPackage opc = OPCPackage.open(new ByteArrayInputStream(bytes)); + OutputStream os = enc.getDataStream(fs); + opc.save(os); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + fs.writeFilesystem(bos); + + return bos.toByteArray(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Workbook loadTemplate(String templateClasspath) throws InvalidFormatException, IOException { + return loadTemplateFile(templateClasspath); + } + + public static Workbook loadTemplateFile(String templateClasspath) throws InvalidFormatException, IOException { + ClassPathResource r = new ClassPathResource(templateClasspath + "_" + ".xlsx"); + if (!r.exists()) r = new ClassPathResource(templateClasspath + ".xlsx"); + + try (InputStream in = r.getInputStream()) { + return new XSSFWorkbook(in); + } + } + + public static void send(HttpServletResponse response, Workbook workbook, String filename) throws IOException { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", + response.encodeURL(filename + ".xlsx"))); + try (OutputStream out = response.getOutputStream()) { + workbook.write(out); + } + } +} diff --git a/src/main/java/com/ffii/core/utils/FileUtils.java b/src/main/java/com/ffii/core/utils/FileUtils.java new file mode 100644 index 0000000..f5c89a6 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/FileUtils.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright 2013 2Fi Business Solutions Ltd. + * + * This code is part of the Core project. + * + * This code is copyrighted. Under no circumstances should any party, people, + * or organization should redistribute any portions of this code in any form, + * either verbatim or through electronic media, to any third parties, unless + * under explicit written permission by 2Fi Business Solutions Ltd. + ******************************************************************************/ +package com.ffii.core.utils; + +import java.util.HashMap; +import java.util.Map; + +/** + * File Utils + * + * @author Patrick + */ +public abstract class FileUtils { + + private static final Map MIMETYPES = new HashMap<>(); + + static { + MIMETYPES.put("pdf", "application/pdf"); + + MIMETYPES.put("doc", "application/msword"); + MIMETYPES.put("dot", "application/msword"); + MIMETYPES.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + + MIMETYPES.put("xls", "application/vnd.ms-excel"); + MIMETYPES.put("xlm", "application/vnd.ms-excel"); + MIMETYPES.put("xla", "application/vnd.ms-excel"); + MIMETYPES.put("xlc", "application/vnd.ms-excel"); + MIMETYPES.put("xlt", "application/vnd.ms-excel"); + MIMETYPES.put("xlw", "application/vnd.ms-excel"); + MIMETYPES.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + MIMETYPES.put("ppt", "application/vnd.ms-powerpoint"); + MIMETYPES.put("pps", "application/vnd.ms-powerpoint"); + MIMETYPES.put("pot", "application/vnd.ms-powerpoint"); + MIMETYPES.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); + + MIMETYPES.put("bat", "application/x-msdownload"); + MIMETYPES.put("com", "application/x-msdownload"); + MIMETYPES.put("dll", "application/x-msdownload"); + MIMETYPES.put("exe", "application/x-msdownload"); + MIMETYPES.put("msi", "application/x-msdownload"); + + MIMETYPES.put("swf", "application/x-shockwave-flash"); + + MIMETYPES.put("7z", "application/x-7z-compressed"); + MIMETYPES.put("rar", "application/x-rar-compressed"); + MIMETYPES.put("zip", "application/zip"); + + MIMETYPES.put("js", "application/javascript"); + MIMETYPES.put("json", "application/json"); + + MIMETYPES.put("mpga", "audio/mpeg"); + MIMETYPES.put("mp2", "audio/mpeg"); + MIMETYPES.put("mp2a", "audio/mpeg"); + MIMETYPES.put("mp3", "audio/mpeg"); + MIMETYPES.put("m2a", "audio/mpeg"); + MIMETYPES.put("m3a", "audio/mpeg"); + + MIMETYPES.put("bmp", "image/bmp"); + MIMETYPES.put("gif", "image/gif"); + MIMETYPES.put("jpeg", "image/jpeg"); + MIMETYPES.put("jpg", "image/jpeg"); + MIMETYPES.put("jpe", "image/jpeg"); + MIMETYPES.put("png", "image/png"); + MIMETYPES.put("tiff", "image/tiff"); + MIMETYPES.put("tif", "image/tiff"); + MIMETYPES.put("avif", "image/avif"); + + + MIMETYPES.put("css", "text/css"); + + MIMETYPES.put("csv", "text/csv"); + + MIMETYPES.put("html", "text/html"); + MIMETYPES.put("htm", "text/html"); + + MIMETYPES.put("txt", "text/plain"); + MIMETYPES.put("text", "text/plain"); + MIMETYPES.put("conf", "text/plain"); + MIMETYPES.put("log", "text/plain"); + + MIMETYPES.put("mp4", "video/mp4"); + MIMETYPES.put("mp4v", "video/mp4"); + MIMETYPES.put("mpg4", "video/mp4"); + + MIMETYPES.put("mkv", "video/x-matroska"); + + MIMETYPES.put("mpeg", "video/mpeg"); + MIMETYPES.put("mpg", "video/mpeg"); + MIMETYPES.put("mpe", "video/mpeg"); + MIMETYPES.put("m1v", "video/mpeg"); + MIMETYPES.put("m2v", "video/mpeg"); + + MIMETYPES.put("qt", "video/quicktime"); + MIMETYPES.put("mov", "video/quicktime"); + + MIMETYPES.put("wmv", "video/x-ms-wmv"); + MIMETYPES.put("wmx", "video/x-ms-wmx"); + MIMETYPES.put("wvx", "video/x-ms-wvx"); + MIMETYPES.put("avi", "video/x-msvideo"); + + // MIMETYPES.put("xxxxx", "xxxxx"); + } + + /** + * Guess the mimetype from the file name extension + * + * @return The mimetype guessed from the file name extension, or {@code null} if the mimetype cannot be determined + */ + public static String guessMimetype(String filename) { + String extension = StringUtils.substringAfterLast(filename, "."); + extension = extension.toLowerCase(); + String mimetype = MIMETYPES.get(extension); + return mimetype != null ? mimetype : "application/octet-stream"; + } + +} diff --git a/src/main/java/com/ffii/core/utils/JsonUtils.java b/src/main/java/com/ffii/core/utils/JsonUtils.java new file mode 100644 index 0000000..4efd35a --- /dev/null +++ b/src/main/java/com/ffii/core/utils/JsonUtils.java @@ -0,0 +1,47 @@ +package com.ffii.core.utils; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +/** + * JSON Utils + * + * @author Patrick + */ +public abstract class JsonUtils { + + // Default mapper instance + private static final ObjectMapper mapper = new ObjectMapper(); + + /** + * Method that can be used to serialize any Java value as a JSON String. + */ + public static String toJsonString(Object obj) { + try { + mapper.registerModule(new JavaTimeModule()); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + return mapper.writeValueAsString(obj); + } catch (JsonProcessingException e) { + return null; + } + } + + /** + * Read from JSON String. + * + * @param content + * JSON String content + * @param valueType + * the return type + */ + public static T fromJsonString(String content, Class valueType) throws JsonParseException, JsonMappingException, IOException { + return mapper.readValue(content, valueType); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/core/utils/JwtTokenUtil.java b/src/main/java/com/ffii/core/utils/JwtTokenUtil.java new file mode 100644 index 0000000..3e37c45 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/JwtTokenUtil.java @@ -0,0 +1,114 @@ +package com.ffii.core.utils; + +import java.io.Serializable; +import java.security.Key; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import com.ffii.lioner.model.RefreshToken; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; + +@Component +@Scope(value = ConfigurableBeanFactory. SCOPE_SINGLETON) +public class JwtTokenUtil implements Serializable { + + Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class); + + private static final long serialVersionUID = -2550185165626007488L; + + // * 60000 = 1 Min + public static final long JWT_REFRESH_TOKEN_EXPIRED_TIME = 60000 * 30; + public static final String AES_SECRET = "ffii"; + public static final String TOKEN_SEPARATOR = "@@"; + + // @Value("${jwt.secret}") + // private String secret; + + private static final Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512); + + // retrieve username from jwt token + public String getUsernameFromToken(String token) { + return getClaimFromToken(token, Claims::getSubject); + } + + // retrieve expiration date from jwt token + public Date getExpirationDateFromToken(String token) { + return getClaimFromToken(token, Claims::getExpiration); + } + + public T getClaimFromToken(String token, Function claimsResolver) { + final Claims claims = getAllClaimsFromToken(token); + return claimsResolver.apply(claims); + } + + // for retrieveing any information from token we will need the secret key + private Claims getAllClaimsFromToken(String token) { + return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody(); + } + + // check if the token has expired + private Boolean isTokenExpired(String token) { + final Date expiration = getExpirationDateFromToken(token); + return expiration.before(new Date()); + } + + // generate token for user + public String generateToken(UserDetails userDetails, long accessTokenExpiry) { + Map claims = new HashMap<>(); + return doGenerateToken(claims, userDetails.getUsername(), accessTokenExpiry); + } + + // while creating the token - + // 1. Define claims of the token, like Issuer, Expiration, Subject, and the ID + // 2. Sign the JWT using the HS512 algorithm and secret key. + // 3. According to JWS Compact + // Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1) + // compaction of the JWT to a URL-safe string + private String doGenerateToken(Map claims, String subject, long accessTokenExpiry) { + logger.info((new Date(System.currentTimeMillis() + accessTokenExpiry)).toString()); + return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + accessTokenExpiry)) + .signWith(secretKey).compact(); + } + + // validate token + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = getUsernameFromToken(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } + + public RefreshToken createRefreshToken(String username) { + RefreshToken refreshToken = new RefreshToken(); + refreshToken.setUserName(username); + refreshToken.setExpiryDate(Instant.now().plusMillis(JWT_REFRESH_TOKEN_EXPIRED_TIME)); + long instantNum = Instant.now().plusMillis(JWT_REFRESH_TOKEN_EXPIRED_TIME).toEpochMilli(); + refreshToken.setToken(AES.encrypt(username + TOKEN_SEPARATOR + instantNum, AES_SECRET)); + return refreshToken; + } + + public boolean verifyExpiration(RefreshToken token) throws Exception { + if (token.getExpiryDate().compareTo(Instant.now()) < 0) { + return false; + } + + return true; + } + + public String getUsernameFromRefreshToken(String refreshToken) { + return AES.decrypt(refreshToken, AES_SECRET); + } +} diff --git a/src/main/java/com/ffii/core/utils/LocaleUtils.java b/src/main/java/com/ffii/core/utils/LocaleUtils.java new file mode 100644 index 0000000..3964259 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/LocaleUtils.java @@ -0,0 +1,42 @@ +package com.ffii.core.utils; + +import java.util.Locale; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.i18n.LocaleContextHolder; + +/** this utils follow "-" standard ("zh-TW", no "zh_TW") */ +public abstract class LocaleUtils { + + public static Locale getLocale() { + return LocaleContextHolder.getLocale(); + } + + public static String getLocaleStr() { + return toLocaleStr(LocaleContextHolder.getLocale()); + } + + public static String toLocaleStr(Locale locale) { + String language = locale.getLanguage(); + String country = locale.getCountry(); + + if (StringUtils.isNotBlank(country)) { + return language + "-" + country; + } else { + return language; + } + } + + /** + * @param localeStr + * e.g. zh-TW + */ + public static Locale from(String localeStr) { + String[] localeArr = localeStr.split("-"); + if (localeArr.length == 1) { + return new Locale(localeArr[0]); + } else { + return new Locale(localeArr[0], localeArr[1]); + } + } +} diff --git a/src/main/java/com/ffii/core/utils/MapUtils.java b/src/main/java/com/ffii/core/utils/MapUtils.java new file mode 100644 index 0000000..733bda9 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/MapUtils.java @@ -0,0 +1,35 @@ +package com.ffii.core.utils; + +import java.util.HashMap; +import java.util.Map; + +/** + * MapUtils + * + * @author Patrick + */ +public class MapUtils { + + /** + * Convert key-value pairs to HashMap + * + * @param keyValuePairs + * Keys and values must be in pairs + * + * @return Map + */ + @SuppressWarnings("unchecked") + public static Map toHashMap(Object... keyValuePairs) { + if (keyValuePairs.length % 2 != 0) + throw new IllegalArgumentException("Keys and values must be in pairs"); + + Map map = new HashMap(keyValuePairs.length / 2); + + for (int i = 0; i < keyValuePairs.length; i += 2) { + map.put((K) keyValuePairs[i], (V) keyValuePairs[i + 1]); + } + + return map; + } + +} diff --git a/src/main/java/com/ffii/core/utils/NumberUtils.java b/src/main/java/com/ffii/core/utils/NumberUtils.java new file mode 100644 index 0000000..7027ff6 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/NumberUtils.java @@ -0,0 +1,45 @@ +package com.ffii.core.utils; + +public class NumberUtils extends org.apache.commons.lang3.math.NumberUtils{ + + private static final String[] units = { "", "one", "two", "three", "four", + "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", + "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", + "eighteen", "nineteen" }; + + private static final String[] tens = { + "", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", + "eighty", "ninety" }; + + public static String convertToWord(int number) { + if (number == 0) { + return "zero"; + } + + if (number < 0) { + return "minus " + convertToWord(-number); + } + + if (number < 20) { + return units[number]; + } + + if (number < 100) { + return tens[number / 10] + ((number % 10 != 0) ? " " : "") + units[number % 10]; + } + + if (number < 1000) { + return units[number / 100] + " hundred" + ((number % 100 != 0) ? " " : "") + convertToWord(number % 100); + } + + if (number < 1000000) { + return convertToWord(number / 1000) + " thousand" + ((number % 1000 != 0) ? " " : "") + convertToWord(number % 1000); + } + + if (number < 1000000000) { + return convertToWord(number / 1000000) + " million" + ((number % 1000000 != 0) ? " " : "") + convertToWord(number % 1000000); + } + + return convertToWord(number / 1000000000) + " billion" + ((number % 1000000000 != 0) ? " " : "") + convertToWord(number % 1000000000); + } +} diff --git a/src/main/java/com/ffii/core/utils/Params.java b/src/main/java/com/ffii/core/utils/Params.java new file mode 100644 index 0000000..5fa0b23 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/Params.java @@ -0,0 +1,42 @@ +package com.ffii.core.utils; + +/** @author Alex */ +public abstract class Params { + public static final String ERROR = "error"; + + public static final String SUCCESS = "success"; + public static final String DATA = "data"; + public static final String RECORDS = "records"; + public static final String TOTAL = "total"; + + public static final String ID = "id"; + public static final String CODE = "code"; + public static final String NAME = "name"; + public static final String TYPE = "type"; + public static final String MSG = "msg"; + public static final String MSG_CODE = "msgCode"; + public static final String MESSAGES = "messages"; + public static final String FROM = "from"; + public static final String TO = "to"; + + // sql + public static final String QUERY = "query"; + + // pagin + public static final String PAGE = "page"; + public static final String START = "start"; + public static final String LIMIT = "limit"; + + // filter + public static final String FILTER = "filter"; + public static final String OPERATOR = "operator"; + public static final String LIKE = "like"; + public static final String PROPERTY = "property"; + + // sort + public static final String SORT = "sort"; + public static final String DIRECTION = "direction"; + + public static final String VALUE = "value"; + +} diff --git a/src/main/java/com/ffii/core/utils/PasswordUtils.java b/src/main/java/com/ffii/core/utils/PasswordUtils.java new file mode 100644 index 0000000..f87ada9 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/PasswordUtils.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright 2Fi Business Solutions Ltd. + * + * This code is copyrighted. Under no circumstances should any party, people, + * or organization should redistribute any portions of this code in any form, + * either verbatim or through electronic media, to any third parties, unless + * under explicit written permission by 2Fi Business Solutions Ltd. + ******************************************************************************/ +package com.ffii.core.utils; + +import java.util.regex.Pattern; + +public abstract class PasswordUtils { + + private static final Pattern PATTERN_DIGITS = Pattern.compile("[0-9]"); + private static final Pattern PATTERN_A2Z_LOWER = Pattern.compile("[a-z]"); + private static final Pattern PATTERN_A2Z_UPPER = Pattern.compile("[A-Z]"); + private static final String A2Z_LOWER = "abcdefghijklmnopqrstuvwxyz"; + private static final String A2Z_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String DIGITS = "0123456789"; + private static final String SPECIAL_CHARS = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; + private static Pattern PATTERN_SPECIAL_CHARS = Pattern.compile("[!\"#$%&'()*+,-./:;<=>?@\\[\\\\\\]^_`{|}~]"); + + /* + * Ref: https://www.owasp.org/index.php/Password_special_characters + * without space character + */ + + public static final boolean checkPwd(String pwd, IPasswordRule rule, String username) { + if (pwd == null) return false; + if (pwd.length() < rule.getMin()) return false; + + if (rule.needSpecialCharacter() && !containsSpecialCharacters(pwd)) + return false; + if (rule.needNumberAndAlphabetic() && !containsAlphabeticAndNumericCharacters(pwd)) + return false; + if (rule.needNotContainUsername() && containsReferenceSubstring(pwd, username)) + return false; + if (rule.needNotContainThreeConsecutiveCharacters() && containsThreeConsecutiveCharacters(pwd)) + return false; + + return true; + } + + private static boolean containsSpecialCharacters(String input) { + return PATTERN_SPECIAL_CHARS.matcher(input).find(); + } + + private static boolean containsAlphabeticAndNumericCharacters(String input) { + boolean containsAlphabetic = PATTERN_A2Z_LOWER.matcher(input).find() || PATTERN_A2Z_UPPER.matcher(input).find(); + boolean containsNumeric = PATTERN_DIGITS.matcher(input).find(); + return containsAlphabetic && containsNumeric; + } + + private static boolean containsReferenceSubstring(String input, String username) { + return input.contains(username); + } + + private static boolean containsThreeConsecutiveCharacters(String input) { + for (int i = 0; i < input.length() - 2; i++) { + char currentChar = input.charAt(i); + char nextChar = input.charAt(i + 1); + char nextNextChar = input.charAt(i + 2); + if (currentChar == nextChar && nextChar == nextNextChar) { + return true; + } + } + return false; + } + + /*public static String genPwd(IPasswordRule rule, String username) { + int length = rule.getMin(); + + StringBuilder password = new StringBuilder(length); + Random random = new Random(System.nanoTime()); + + List charCategories = new ArrayList<>(4); + if (rule.needSpecialCharacter()) charCategories.add(SPECIAL_CHARS); + if (rule.needNumberAndAlphabetic()){ + charCategories.add(A2Z_UPPER); + charCategories.add(A2Z_LOWER); + charCategories.add(DIGITS); + } + + + if (rule.needSpecialChar()) charCategories.add(SPECIAL_CHARS); + + for (int i = 0; i < length; i++) { + String charCategory = charCategories.get(i % charCategories.size()); + char randomChar = charCategory.charAt(random.nextInt(charCategory.length())); + if (password.length() > 0) + password.insert(random.nextInt(password.length()), randomChar); + else + password.append(randomChar); + } + + return password.toString(); + }*/ + + public static interface IPasswordRule { + public Integer getMin(); + + public boolean needNumberAndAlphabetic(); + + public boolean needSpecialCharacter(); + + public boolean needNotContainUsername(); + + public boolean needNotContainThreeConsecutiveCharacters(); + } +} diff --git a/src/main/java/com/ffii/core/utils/RomanConverter.java b/src/main/java/com/ffii/core/utils/RomanConverter.java new file mode 100644 index 0000000..2eab689 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/RomanConverter.java @@ -0,0 +1,24 @@ +package com.ffii.core.utils; + +public class RomanConverter { + private static final int[] VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1 }; + private static final String[] CAP_SYMBOLS = { "M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I" }; + private static final String[] SYMBOLS = { "m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i" }; + + public static String convertToRoman(int number, boolean isCapitalLetter) { + if (number <= 0 || number > 3999) { + throw new IllegalArgumentException("Number out of range. Please provide a value between 1 and 3999."); + } + + StringBuilder roman = new StringBuilder(); + + for (int i = 0; i < VALUES.length; i++) { + while (number >= VALUES[i]) { + roman.append(isCapitalLetter ? CAP_SYMBOLS[i] : SYMBOLS[i]); + number -= VALUES[i]; + } + } + + return roman.toString(); + } +} diff --git a/src/main/java/com/ffii/core/utils/StringUtils.java b/src/main/java/com/ffii/core/utils/StringUtils.java new file mode 100644 index 0000000..c363b79 --- /dev/null +++ b/src/main/java/com/ffii/core/utils/StringUtils.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright 2017 2Fi Business Solutions Ltd. + * + * This code is part of the Core project. + * + * This code is copyrighted. Under no circumstances should any party, people, + * or organization should redistribute any portions of this code in any form, + * either verbatim or through electronic media, to any third parties, unless + * under explicit written permission by 2Fi Business Solutions Ltd. + ******************************************************************************/ +package com.ffii.core.utils; + +/** + * String Utils based on Apache Commons StringUtils. + * + * @author Patrick + */ +public abstract class StringUtils extends org.apache.commons.lang3.StringUtils { + + /** + * The String {@code "0"}. + */ + public static final String ZERO = "0"; + + /** + * The String {@code "1"}. + */ + public static final String ONE = "1"; + + /** + * The String {@code "%"}. + */ + public static final String PERCENT = "%"; + + /** + * The String {@code ","}. + */ + public static final String COMMA = ","; + + /** + * The String {@code "\r\n"} for line break on Windows + */ + public static final String LINE_BREAK_WINDOWS = "\r\n"; + + /** + * The String {@code "\n"} for line break on Unix/Linux + */ + public static final String LINE_BREAK_LINUX = "\n"; + + public static final String[] A2Z_LOWWER = { "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", + "o", "p", "q", "r", "s", "t", "u", "v", + "w", "x", "y", "z" }; + public static final String[] A2Z_UPPER = { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", + "O", "P", "Q", "R", "S", "T", "U", "V", + "W", "X", "Y", "Z" }; + + public static final String concat(String segregator, final String... chars) { + if (segregator == null) + segregator = ""; + String rs = ""; + for (String c : chars) { + if (c == null) + continue; + else { + if (StringUtils.isBlank(rs)) { + rs = c; + } else { + rs += segregator + c; + } + } + } + return rs; + } + + public static final String removeLineBreak(String str) { + if (str == null) + return str; + return str.replace("\r\n", " ") + .replace("\n", " ") + .replace("\r", " ") + .trim(); + } +} diff --git a/src/main/java/com/ffii/lioner/LionerApplication.java b/src/main/java/com/ffii/lioner/LionerApplication.java new file mode 100644 index 0000000..bc9b2e9 --- /dev/null +++ b/src/main/java/com/ffii/lioner/LionerApplication.java @@ -0,0 +1,13 @@ +package com.ffii.lioner; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class LionerApplication { + + public static void main(String[] args) { + SpringApplication.run(LionerApplication.class, args); + } + +} diff --git a/src/main/java/com/ffii/lioner/config/AppConfig.java b/src/main/java/com/ffii/lioner/config/AppConfig.java new file mode 100644 index 0000000..d3af4b5 --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/AppConfig.java @@ -0,0 +1,36 @@ +package com.ffii.lioner.config; + +import javax.sql.DataSource; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +import com.ffii.core.support.JdbcDao; + +/** @author Terence */ +@Configuration +// @EnableJpaRepositories("com.ffii.lioner.*") +// @ComponentScan(basePackages = { "com.ffii.core.*" }) +@ComponentScan(basePackages = { "com.ffii.core.*","com.ffii.lioner.*"}) +// @EntityScan("com.ffii.lioner.*") +@EnableScheduling +@EnableAsync +public class AppConfig { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource dataSource() { + return DataSourceBuilder.create().build(); + } + + @Bean + public JdbcDao jdbcDao(DataSource dataSource) { + return new JdbcDao(dataSource); + } + +} diff --git a/src/main/java/com/ffii/lioner/config/RestTemplateConfig.java b/src/main/java/com/ffii/lioner/config/RestTemplateConfig.java new file mode 100644 index 0000000..1ed822b --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/RestTemplateConfig.java @@ -0,0 +1,15 @@ +package com.ffii.lioner.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} diff --git a/src/main/java/com/ffii/lioner/config/WebConfig.java b/src/main/java/com/ffii/lioner/config/WebConfig.java new file mode 100644 index 0000000..a185eb6 --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/WebConfig.java @@ -0,0 +1,33 @@ +package com.ffii.lioner.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +@Configuration +@EnableWebMvc +public class WebConfig implements WebMvcConfigurer { + + @Value("${host.url}") + private String url; + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedHeaders("*") + .allowedOrigins(url) + .exposedHeaders("filename") + .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"); + + } + + @Bean + public InternalResourceViewResolver defaultViewResolver() { + return new InternalResourceViewResolver(); + } + +} diff --git a/src/main/java/com/ffii/lioner/config/security/SecurityConfig.java b/src/main/java/com/ffii/lioner/config/security/SecurityConfig.java new file mode 100644 index 0000000..142b47a --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/security/SecurityConfig.java @@ -0,0 +1,87 @@ +package com.ffii.lioner.config.security; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; +import org.springframework.ldap.core.LdapClient; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.core.support.BaseLdapPathContextSource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.config.ldap.LdapBindAuthenticationManagerFactory; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import com.ffii.lioner.config.security.jwt.JwtRequestFilter; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfig { + + public static final String INDEX_URL = "/"; + public static final String LOGIN_URL = "/login"; + public static final String REFRESH_TOKEN_URL = "/refresh-token"; + + public static final String[] URL_WHITELIST = { + INDEX_URL, + LOGIN_URL, + REFRESH_TOKEN_URL + }; + + @Lazy + @Autowired + private JwtRequestFilter jwtRequestFilter; + + @Bean + @Qualifier("AuthenticationManager") + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) + throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Bean + @Qualifier("LdapAuthenticationManager") + public AuthenticationManager ldapAuthenticationManager(BaseLdapPathContextSource contextSource) { + LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource); + factory.setUserSearchFilter("cn={0}"); + return factory.createAuthenticationManager(); + } + + @Bean + public LdapTemplate ldapTemplate(BaseLdapPathContextSource contextSource) { + return new LdapTemplate(contextSource); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @Order(1) + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .cors(Customizer.withDefaults()).csrf(csrf -> csrf.disable()) + .requestCache(requestCache -> requestCache.disable()) + .authorizeHttpRequests( + authRequest -> authRequest.requestMatchers(URL_WHITELIST).permitAll().anyRequest().authenticated()) + .httpBasic(httpBasic -> httpBasic.authenticationEntryPoint( + (request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value()))) + .sessionManagement( + sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } +} diff --git a/src/main/java/com/ffii/lioner/config/security/jwt/JwtRequestFilter.java b/src/main/java/com/ffii/lioner/config/security/jwt/JwtRequestFilter.java new file mode 100644 index 0000000..6ed993e --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/security/jwt/JwtRequestFilter.java @@ -0,0 +1,78 @@ +package com.ffii.lioner.config.security.jwt; + +import java.io.IOException; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import com.ffii.lioner.config.security.jwt.service.JwtUserDetailsService; +import com.ffii.core.utils.JwtTokenUtil; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.security.SignatureException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +@Component +public class JwtRequestFilter extends OncePerRequestFilter { + + @Autowired + private JwtUserDetailsService jwtUserDetailsService; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException { + + final String requestTokenHeader = request.getHeader("Authorization"); + + String username = null; + String jwtToken = null; + // JWT Token is in the form "Bearer token". Remove Bearer word and get + // only the Token + if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { + jwtToken = requestTokenHeader.substring(7).replaceAll("\"", ""); + try { + username = jwtTokenUtil.getUsernameFromToken(jwtToken); + } catch (IllegalArgumentException e) { + logger.error("Unable to get JWT Token"); + } catch (ExpiredJwtException e) { + logger.error("JWT Token has expired"); + } catch (SignatureException e) { + logger.error("JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trust."); + } + } else { + logger.warn("JWT Token does not begin with Bearer String"); + } + + // Once we get the token validate it. + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + + UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username); + + // if token is valid configure Spring Security to manually set + // authentication + if (jwtTokenUtil.validateToken(jwtToken, userDetails)) { + + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( + userDetails, null, userDetails.getAuthorities()); + usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + // After setting the Authentication in the context, we specify + // that the current user is authenticated. So it passes the + // Spring Security Configurations successfully. + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } + chain.doFilter(request, response); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/config/security/jwt/service/JwtUserDetailsService.java b/src/main/java/com/ffii/lioner/config/security/jwt/service/JwtUserDetailsService.java new file mode 100644 index 0000000..b898f8f --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/security/jwt/service/JwtUserDetailsService.java @@ -0,0 +1,31 @@ +package com.ffii.lioner.config.security.jwt.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.user.entity.User; +import com.ffii.lioner.modules.user.entity.UserRepository; +import com.ffii.lioner.modules.user.service.UserAuthorityService; +import com.ffii.lioner.modules.user.service.UserService; + +@Service +public class JwtUserDetailsService implements UserDetailsService { + + @Autowired + UserRepository userRepository; + + @Autowired + UserAuthorityService userAuthService; + + @Autowired + UserService userService; + + + @Override + public User loadUserByUsername(String username) throws UsernameNotFoundException { + return userService.loadUserOptByUsername(username).orElseThrow(() -> new UsernameNotFoundException(username)); + } + +} diff --git a/src/main/java/com/ffii/lioner/config/security/jwt/web/JwtAuthenticationController.java b/src/main/java/com/ffii/lioner/config/security/jwt/web/JwtAuthenticationController.java new file mode 100644 index 0000000..a052ac7 --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/security/jwt/web/JwtAuthenticationController.java @@ -0,0 +1,226 @@ +package com.ffii.lioner.config.security.jwt.web; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +import com.ffii.lioner.config.security.jwt.service.JwtUserDetailsService; +import com.ffii.lioner.config.security.service.LoginLogService; +import com.ffii.lioner.model.ExceptionResponse; +import com.ffii.lioner.model.JwtRequest; +import com.ffii.lioner.model.JwtResponse; +import com.ffii.lioner.model.RefreshToken; +import com.ffii.lioner.model.TokenRefreshRequest; +import com.ffii.lioner.model.TokenRefreshResponse; +import com.ffii.lioner.modules.common.SettingNames; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.lioner.modules.user.entity.User; +import com.ffii.lioner.modules.user.entity.UserRepository; +import com.ffii.lioner.modules.user.service.UserAuthorityService; +import com.ffii.core.response.AuthRes; +import com.ffii.core.utils.AES; +import com.ffii.core.utils.JwtTokenUtil; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +public class JwtAuthenticationController { + @Autowired + @Qualifier("AuthenticationManager") + private AuthenticationManager authenticationManager; + + @Autowired + @Qualifier("LdapAuthenticationManager") + private AuthenticationManager ldapAuthenticationManager; + + @Autowired + private JwtTokenUtil jwtTokenUtil; + + @Autowired + private JwtUserDetailsService userDetailsService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserAuthorityService userAuthorityService; + + @Autowired + private LoginLogService loginLogService; + + @Autowired + private SettingsService settingsService; + + private final Log logger = LogFactory.getLog(getClass()); + + private static final long EXPIRY_IN_MINTUE = 60000; + private static final long TOKEN_DURATION = 5; + + @PostMapping("/login") + public ResponseEntity login(@RequestBody JwtRequest authenticationRequest, HttpServletRequest request) + throws Exception { + String username = authenticationRequest.getUsername(); + HttpStatus httpStatus = HttpStatus.UNAUTHORIZED; + String message = "Invalid username and password."; + Integer maxAttempt = settingsService.getInt(SettingNames.SYS_LOGIN_ATTEMPT_LIMIT); + Integer penalityTime = settingsService.getInt(SettingNames.SYS_LOGIN_ATTEMPT_PENALITY_TIME); + String checkAddrr = request.getHeader("X-Forwarded-For"); + if (checkAddrr == null || "".equals(checkAddrr)) { + checkAddrr = request.getRemoteAddr(); + } + List> failAttempts = loginLogService.failChecking(username, checkAddrr, maxAttempt); + if (failAttempts.size() >= maxAttempt) { + // check if last failAttempts not contain sucess login then trigger penality + // time + if (!failAttempts.stream().map(x -> { + return x.get("success"); + }).collect(Collectors.toList()).contains(true)) { + LocalDateTime lastAttemptTime = (LocalDateTime) failAttempts.get(0).get("loginTime"); + if (LocalDateTime.now().isBefore(lastAttemptTime.plusMinutes(penalityTime))) { + return ResponseEntity.status(httpStatus) + .body(new ExceptionResponse( + "Login failure reach the limit.\n Please try again after " + // add 1 to round up + + (ChronoUnit.MINUTES.between(LocalDateTime.now(), lastAttemptTime.plusMinutes(penalityTime)) + 1) + + " minutes.", + null)); + } + } + } + + AuthRes authRes = ldapAuthenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); + + if (!authRes.isSuccess()) { + authRes = authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); + } + + UserDetails userDetails = null; + if (authRes.isSuccess()) { + try { + userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername()); + } catch (UsernameNotFoundException e) { + message = username + " not yet register in the system."; + authRes.setSuccess(false); + authRes.setException(ExceptionUtils.getStackTrace(e)); + } catch (Exception e) { + httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + message = "Internal Server Error"; + authRes.setSuccess(false); + authRes.setException(ExceptionUtils.getStackTrace(e)); + } + } + + String remoteAddrr = request.getHeader("X-Forwarded-For"); + if (remoteAddrr == null || "".equals(remoteAddrr)) { + remoteAddrr = request.getRemoteAddr(); + } + + loginLogService.createLoginLog(username, remoteAddrr, authRes.isSuccess()); + + if (userDetails == null) { + return ResponseEntity.status(httpStatus) + .body(new ExceptionResponse(message, authRes.getException())); + } else { + User user = userRepository.findByName(userDetails.getUsername()).get(0); + if (user.isLocked()) { + return ResponseEntity.status(httpStatus) + .body(new ExceptionResponse(username + " has been locked.", null)); + } + } + + return createAuthTokenResponse(userDetails); + } + + private AuthRes authenticate(String username, String password) throws Exception { + boolean success = false; + String exception = ""; + try { + authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); + success = true; + } catch (Exception e) { + exception = ExceptionUtils.getStackTrace(e); + } + return new AuthRes(success, exception); + } + + private AuthRes ldapAuthenticate(String username, String password) throws Exception { + boolean success = false; + String exception = ""; + try { + ldapAuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password)); + success = true; + } catch (Exception e) { + logger.info(ExceptionUtils.getStackTrace(e)); + exception = ExceptionUtils.getStackTrace(e); + } + return new AuthRes(success, exception); + } + + private ResponseEntity createAuthTokenResponse(UserDetails userDetails) { + long accessTokenExpiry = /* settingsService.getInt(SettingNames.SYS_IDLE_LOGOUT_TIME) */ TOKEN_DURATION + * EXPIRY_IN_MINTUE; + final String accessToken = jwtTokenUtil.generateToken(userDetails, accessTokenExpiry); + final String refreshToken = jwtTokenUtil.createRefreshToken(userDetails.getUsername()).getToken(); + + User user = userRepository.findByUsernameAndDeletedFalse(userDetails.getUsername()).get(); + + // Set abilities = new HashSet<>(); + // userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(new + // AbilityModel(auth.getAuthority()))); + List abilities = new ArrayList(); + userAuthorityService.getUserAuthority(user).forEach(auth -> abilities.add(auth.get("authority").toString())); + + return ResponseEntity.ok(new JwtResponse(accessToken, refreshToken, null, user, abilities)); + } + + @PostMapping("/refresh-token") + public ResponseEntity refreshtoken(@Valid @RequestBody TokenRefreshRequest request) + throws Exception { + long accessTokenExpiry = /* settingsService.getInt(SettingNames.SYS_IDLE_LOGOUT_TIME) */ TOKEN_DURATION + * EXPIRY_IN_MINTUE; + String requestRefreshToken = request.getRefreshToken(); + + requestRefreshToken = requestRefreshToken.replaceAll("\"", ""); + String[] decryptStringList = AES.decrypt(requestRefreshToken, JwtTokenUtil.AES_SECRET) + .split(JwtTokenUtil.TOKEN_SEPARATOR); + RefreshToken instance = new RefreshToken(); + String username = decryptStringList[0]; + instance.setExpiryDate(Instant.ofEpochMilli(Long.valueOf(decryptStringList[1]))); + instance.setToken(requestRefreshToken); + instance.setUserName(decryptStringList[0]); + + if (!jwtTokenUtil.verifyExpiration(instance)) { + throw new ResponseStatusException(HttpStatus.EXPECTATION_FAILED, + "Refresh token was expired. Please make a new signin request"); + } + + final UserDetails userDetails = userDetailsService.loadUserByUsername(username); + + String accessToken = jwtTokenUtil.generateToken(userDetails, accessTokenExpiry); + String refreshToken = jwtTokenUtil.createRefreshToken(username).getToken(); + return ResponseEntity.ok(new TokenRefreshResponse(accessToken, refreshToken)); + } + +} diff --git a/src/main/java/com/ffii/lioner/config/security/service/LoginLogService.java b/src/main/java/com/ffii/lioner/config/security/service/LoginLogService.java new file mode 100644 index 0000000..847a2b1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/config/security/service/LoginLogService.java @@ -0,0 +1,56 @@ +package com.ffii.lioner.config.security.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.core.support.AbstractService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.MapUtils; + +@Service +public class LoginLogService extends AbstractService { + + public LoginLogService(JdbcDao jdbcDao) { + super(jdbcDao); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = false) + public boolean createLoginLog(String username, String remoteAddr, boolean success) { + String sql = "INSERT INTO user_login_log (`username`, `loginTime`, `ipAddr`, `success`) " + +"VALUES (:username, :loginTime, :ipAddr, :success)"; + Map args = new HashMap<>(4); + args.put("username", username); + args.put("loginTime", new Date()); + args.put("ipAddr", remoteAddr); + args.put("success", success); + + return (jdbcDao.executeUpdate(sql, args) == 1); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public List> listLastLog(String username, int limit) { + return jdbcDao.queryForList("SELECT success FROM user_login_log where username = :username ORDER BY loginTime DESC LIMIT " + limit, + MapUtils.toHashMap("username", username)); + } + + public List> failChecking(String username,String remoteAddrr, Integer maxAttempt){ + StringBuilder sql = new StringBuilder("SELECT" + + " * " + + " FROM user_login_log ull " + //+ " WHERE ull.username = :username " + + " WHERE ull.ipAddr = :remoteAddrr " + //+ " AND ull.loginTime >= NOW() - INTERVAL 5 MINUTE " + + " ORDER BY ull.loginTime DESC " + + " LIMIT :maxAttempt " + ); + + return jdbcDao.queryForList(sql.toString(), Map.of("username", username, "remoteAddrr", remoteAddrr, "maxAttempt", maxAttempt)); + } + +} diff --git a/src/main/java/com/ffii/lioner/model/AbilityModel.java b/src/main/java/com/ffii/lioner/model/AbilityModel.java new file mode 100644 index 0000000..861c7e8 --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/AbilityModel.java @@ -0,0 +1,13 @@ +package com.ffii.lioner.model; + +public class AbilityModel { + + private final String actionSubjectCombo; + public AbilityModel(String actionSubjectCombo) { + this.actionSubjectCombo = actionSubjectCombo; + } + + public String getActionSubjectCombo() { + return actionSubjectCombo; + } +} diff --git a/src/main/java/com/ffii/lioner/model/ExceptionResponse.java b/src/main/java/com/ffii/lioner/model/ExceptionResponse.java new file mode 100644 index 0000000..83c93f2 --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/ExceptionResponse.java @@ -0,0 +1,28 @@ +package com.ffii.lioner.model; + +public class ExceptionResponse { + private String message; + private String exception; + + public ExceptionResponse(String message, String exception) { + this.message = message; + this.exception = exception; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getException() { + return exception; + } + + public void setException(String exception) { + this.exception = exception; + } + +} diff --git a/src/main/java/com/ffii/lioner/model/JwtRequest.java b/src/main/java/com/ffii/lioner/model/JwtRequest.java new file mode 100644 index 0000000..d0b575d --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/JwtRequest.java @@ -0,0 +1,45 @@ +package com.ffii.lioner.model; + + +import java.io.Serializable; + +import jakarta.validation.constraints.NotNull; + +public class JwtRequest implements Serializable { + + private static final long serialVersionUID = 5926468583005150707L; + + @NotNull + private String username; + + @NotNull + private String password; + + //need default constructor for JSON Parsing + public JwtRequest() + { + + } + + public JwtRequest(String username, String password) { + this.setUsername(username); + this.setPassword(password); + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/model/JwtResponse.java b/src/main/java/com/ffii/lioner/model/JwtResponse.java new file mode 100644 index 0000000..cd85a12 --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/JwtResponse.java @@ -0,0 +1,75 @@ +package com.ffii.lioner.model; + +import java.io.Serializable; +import java.util.List; +import java.util.Set; + +import com.ffii.lioner.modules.user.entity.User; + +public class JwtResponse implements Serializable { + + private static final long serialVersionUID = -8091879091924046844L; + private final Long id; + private final String name; + private final String email; + private final String accessToken; + private final String refreshToken; + private final String role; + //private final Set abilities; + private final Long subDivisionId; + private final Boolean lotusNotesUser; + private final List abilities; + + public JwtResponse(String accessToken, String refreshToken, String role, User user, /*Set*/List abilities) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.role = role; + this.id = user.getId(); + this.name = user.getName(); + this.email = user.getEmail(); + this.subDivisionId = user.getSubDivisionId(); + this.lotusNotesUser = user.getLotusNotesUser(); + this.abilities = abilities; + } + + public String getAccessToken() { + return this.accessToken; + } + + public String getRole() { + return role; + } + + public String getRefreshToken() { + return refreshToken; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + + public List/*Set*/ getAbilities() { + return abilities; + } + + public Long getSubDivisionId() { + return this.subDivisionId; + } + + public Boolean isLotusNotesUser() { + return this.lotusNotesUser; + } + + public Boolean getLotusNotesUser() { + return this.lotusNotesUser; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/model/RefreshToken.java b/src/main/java/com/ffii/lioner/model/RefreshToken.java new file mode 100644 index 0000000..2ea53d4 --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/RefreshToken.java @@ -0,0 +1,39 @@ +package com.ffii.lioner.model; + +import java.time.Instant; + +public class RefreshToken { + + private String userName; + + private String token; + + private Instant expiryDate; + + + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public Instant getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(Instant expiryDate) { + this.expiryDate = expiryDate; + } + +} diff --git a/src/main/java/com/ffii/lioner/model/TokenRefreshRequest.java b/src/main/java/com/ffii/lioner/model/TokenRefreshRequest.java new file mode 100644 index 0000000..64b9db1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/TokenRefreshRequest.java @@ -0,0 +1,16 @@ +package com.ffii.lioner.model; +import jakarta.validation.constraints.NotBlank; + +public class TokenRefreshRequest { + @NotBlank + private String refreshToken; + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + +} diff --git a/src/main/java/com/ffii/lioner/model/TokenRefreshResponse.java b/src/main/java/com/ffii/lioner/model/TokenRefreshResponse.java new file mode 100644 index 0000000..08a4f63 --- /dev/null +++ b/src/main/java/com/ffii/lioner/model/TokenRefreshResponse.java @@ -0,0 +1,37 @@ +package com.ffii.lioner.model; + +public class TokenRefreshResponse { + private String accessToken; + private String refreshToken; + private String tokenType = "Bearer"; + + public TokenRefreshResponse(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public String getTokenType() { + return tokenType; + } + + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/common/ErrorCodes.java b/src/main/java/com/ffii/lioner/modules/common/ErrorCodes.java new file mode 100644 index 0000000..1ec2228 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/ErrorCodes.java @@ -0,0 +1,17 @@ +package com.ffii.lioner.modules.common; + +public class ErrorCodes { + + public static final String FILE_UPLOAD_ERROR = "FILE_UPLOAD_ERROR"; + + public static final String STOCK_IN_WRONG_POST = "STOCK_IN_WRONG_POST"; + + public static final String USER_WRONG_NEW_PWD = "USER_WRONG_NEW_PWD"; + + public static final String SEND_EMAIL_ERROR = "SEND_EMAIL_ERROR"; + public static final String USERNAME_NOT_AVAILABLE = "USERNAME_NOT_AVAILABLE"; + + public static final String INIT_EXCEL_ERROR = "INIT_EXCEL_ERROR"; + + public static final String CHANGE_MAIN_CUSTOMER_ERROR = "CHANGE_MAIN_CUSTOMER_ERROR"; +} diff --git a/src/main/java/com/ffii/lioner/modules/common/LocalDateAdapter.java b/src/main/java/com/ffii/lioner/modules/common/LocalDateAdapter.java new file mode 100644 index 0000000..3f4852d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/LocalDateAdapter.java @@ -0,0 +1,23 @@ +package com.ffii.lioner.modules.common; + +import com.google.gson.*; +import java.lang.reflect.Type; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +public class LocalDateAdapter implements JsonSerializer, JsonDeserializer { + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Override + public JsonElement serialize(LocalDate date, Type type, JsonSerializationContext context) { + String dateString = date.format(DATE_FORMATTER); + return new JsonPrimitive(dateString); + } + + @Override + public LocalDate deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + String dateString = json.getAsString(); + return LocalDate.parse(dateString, DATE_FORMATTER); + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/common/MailSMTP.java b/src/main/java/com/ffii/lioner/modules/common/MailSMTP.java new file mode 100644 index 0000000..edb334b --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/MailSMTP.java @@ -0,0 +1,69 @@ +package com.ffii.lioner.modules.common; + +import org.apache.commons.lang3.StringUtils; + +import com.ffii.lioner.modules.settings.service.SettingsService; + +public class MailSMTP { + private String host; + private int port; + private String username; + private String password; + private boolean auth; + + public MailSMTP(SettingsService settingsService) { + if (settingsService == null) + throw new IllegalArgumentException("settingsService"); + + this.host = settingsService.getString(SettingNames.MAIL_SMTP_HOST); + this.port = settingsService.getInt(SettingNames.MAIL_SMTP_PORT); + this.username = settingsService.getString(SettingNames.MAIL_SMTP_USERNAME); + this.password = settingsService.getString(SettingNames.MAIL_SMTP_PASSWORD); + this.auth = settingsService.getBoolean(SettingNames.MAIL_SMTP_AUTH); + } + + public String getHost() { + return host; + } + + public int getPort() { + return port; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public boolean isAuth() { + return this.auth; + } + + public boolean getAuth() { + return this.auth; + } + + public void setAuth(boolean auth) { + this.auth = auth; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof MailSMTP)) + return false; + + MailSMTP o = (MailSMTP) obj; + if (StringUtils.equals(this.getHost(), o.getHost()) && + this.getPort() == o.getPort() && + StringUtils.equals(this.getUsername(), o.getUsername()) && + StringUtils.equals(this.getPassword(), o.getPassword())) { + return true; + } + + return false; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/common/PasswordRule.java b/src/main/java/com/ffii/lioner/modules/common/PasswordRule.java new file mode 100644 index 0000000..572e7de --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/PasswordRule.java @@ -0,0 +1,119 @@ +package com.ffii.lioner.modules.common; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.core.utils.PasswordUtils.IPasswordRule; + + +public class PasswordRule implements IPasswordRule { + private Integer min; + + private Boolean numberAndAlphabetic; + private Boolean specialCharacter; + private Boolean notContainUsername; + private Boolean notContainThreeConsecutiveCharacters; + +public PasswordRule(SettingsService settingsService) { + if (settingsService == null){ + throw new IllegalArgumentException("settingsService"); + } + + this.min = settingsService.getInt(SettingNames.SYS_PASSWORD_RULE_MIN); + this.numberAndAlphabetic = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_NUMBER_AND_ALPHABETIC); + this.specialCharacter = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_SPECIAL_CHAR); + this.notContainUsername = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_NOT_CONTAIN_USERNAME); + this.notContainThreeConsecutiveCharacters = settingsService.getBoolean(SettingNames.SYS_PASSWORD_RULE_NOT_CONTAIN_THREE_CONSECUTIVE_CHAR); + } + + public Integer getMin() { + return this.min; + } + + public void setMin(Integer min) { + this.min = min; + } + + public Boolean isNumberAndAlphabetic() { + return this.numberAndAlphabetic; + } + + public Boolean getNumberAndAlphabetic() { + return this.numberAndAlphabetic; + } + + public void setNumberAndAlphabetic(Boolean numberAndAlphabetic) { + this.numberAndAlphabetic = numberAndAlphabetic; + } + + public Boolean isSpecialCharacter() { + return this.specialCharacter; + } + + public Boolean getSpecialCharacter() { + return this.specialCharacter; + } + + public void setSpecialCharacter(Boolean specialCharacter) { + this.specialCharacter = specialCharacter; + } + + public Boolean isNotContainUsername() { + return this.notContainUsername; + } + + public Boolean getNotContainUsername() { + return this.notContainUsername; + } + + public void setNotContainUsername(Boolean notContainUsername) { + this.notContainUsername = notContainUsername; + } + + public Boolean isNotContainThreeConsecutiveCharacters() { + return this.notContainThreeConsecutiveCharacters; + } + + public Boolean getNotContainThreeConsecutiveCharacters() { + return this.notContainThreeConsecutiveCharacters; + } + + public void setNotContainThreeConsecutiveCharacters(Boolean notContainThreeConsecutiveCharacters) { + this.notContainThreeConsecutiveCharacters = notContainThreeConsecutiveCharacters; + } + + @JsonIgnore + public String getWrongMsg() { + StringBuilder msg = new StringBuilder("Please Following Password Rule:\n"); + msg.append("- Minimum " + getMin() + " Characters.\n"); + if (needNumberAndAlphabetic()) + msg.append("- Need numbers and alphabetic characters.\n"); + if (needSpecialCharacter()) + msg.append("- Need at least one special characters.\n"); + if (needNotContainUsername()) + msg.append("- Cannot contain username.\n"); + if (needNotContainThreeConsecutiveCharacters()) + msg.append("- Cannot contain three consecutive characters.\n"); + return msg.toString(); + } + + @Override + public boolean needNumberAndAlphabetic() { + return numberAndAlphabetic; + } + + @Override + public boolean needSpecialCharacter() { + return specialCharacter; + } + + @Override + public boolean needNotContainUsername() { + return notContainUsername; + } + + @Override + public boolean needNotContainThreeConsecutiveCharacters() { + return notContainThreeConsecutiveCharacters; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/common/SecurityUtils.java b/src/main/java/com/ffii/lioner/modules/common/SecurityUtils.java new file mode 100644 index 0000000..47c06b3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/SecurityUtils.java @@ -0,0 +1,146 @@ +package com.ffii.lioner.modules.common; + +import java.util.Optional; + +import org.springframework.dao.DataAccessException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import com.ffii.lioner.modules.user.entity.User; + +/** + * Security Utils - for Spring Security + * + * @author Patrick + */ +public class SecurityUtils { + + /** + * Obtains the current {@code SecurityContext}. + * + * @return the security context (never {@code null}) + */ + public static final SecurityContext getSecurityContext() { + return SecurityContextHolder.getContext(); + } + + /** + * @return the authenticated {@code Principal}) + * @see Authentication#getPrincipal() + */ + public static final Optional getUser() { + try { + return Optional.of((User) getSecurityContext().getAuthentication().getPrincipal()); + } catch (ClassCastException e) { + // no authenticated principal + return Optional.empty(); + } catch (NullPointerException e) { + // no authentication information is available + return Optional.empty(); + } + } + + /** + * Updates the Authentication Token with the user (e.g. user changed the password) + * + * @see SecurityContext#setAuthentication(Authentication) + */ + public static final void updateUserAuthentication(final UserDetails user) { + getSecurityContext().setAuthentication(new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities())); + } + + /** + * Checks if the current user is GRANTED the {@code role} + * + * @param role + * the {@code role} to check for + * @return {@code true} if the current user is GRANTED the {@code role}, else {@code false} + */ + public static final boolean isGranted(String role) { + Authentication authentication = getSecurityContext().getAuthentication(); + if (authentication == null) return false; + for (GrantedAuthority auth : authentication.getAuthorities()) { + if (role.equals(auth.getAuthority())) return true; + } + return false; + } + + /** + * Checks if the current user is NOT GRANTED the {@code role} + * + * @param role + * the {@code role} to check for + * @return {@code true} if the current user is NOT GRANTED the {@code role}, else {@code false} + */ + public static final boolean isNotGranted(String role) { + return !isGranted(role); + } + + /** + * Checks if the current user is GRANTED ANY of the {@code role}s + * + * @param roles + * the {@code role}s to check for + * @return {@code true} if the current user is GRANTED ANY of the {@code role}s, else {@code false} + */ + public static final boolean isGrantedAny(String... roles) { + for (int i = 0; i < roles.length; i++) { + if (isGranted(roles[i])) return true; + } + return false; + } + + /** + * Checks if the current user is NOT GRANTED ANY of the {@code role}s + * + * @param roles + * the {@code role}s to check for + * @return {@code true} if the current user is NOT GRANTED ANY of the {@code role}s, else {@code false} + */ + public static final boolean isNotGrantedAny(String... roles) { + return !isGrantedAny(roles); + } + + /** + * Checks if the current user is GRANTED ALL of the {@code role}s + * + * @param roles + * the {@code role}s to check for + * @return {@code true} if the current user is GRANTED ALL of the {@code role}s, else {@code false} + */ + public static final boolean isGrantedAll(String... roles) { + for (int i = 0; i < roles.length; i++) { + if (isNotGranted(roles[i])) return false; + } + return true; + } + + /** + * Login a user non-interactively + * + * @param userService + * any implementation of {@link UserDetailsService} + * @param username + * the username + * + * @throws UsernameNotFoundException + * if the user could not be found or the user has no GrantedAuthority + * @throws DataAccessException + * if user could not be found for a repository-specific reason + */ + public static final void loginUser(UserDetailsService userService, String username) { + /* load the user, throw exception if user not found */ + UserDetails userDetails = userService.loadUserByUsername(username); + + /* create authentication token for the specified user */ + Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + getSecurityContext().setAuthentication(authentication); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/common/SettingNames.java b/src/main/java/com/ffii/lioner/modules/common/SettingNames.java new file mode 100644 index 0000000..73e456d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/SettingNames.java @@ -0,0 +1,53 @@ +package com.ffii.lioner.modules.common; + +public abstract class SettingNames { + /* + * System-wide settings + */ + + /** System Idle Logout Time*/ + public static final String SYS_IDLE_LOGOUT_TIME = "SYS.idleLogoutTime"; + + /* + * Mail settings + */ + + /** Mail - SMTP host */ + public static final String MAIL_SMTP_HOST = "MAIL.smtp.host"; + + /** Mail - SMTP port */ + public static final String MAIL_SMTP_PORT = "MAIL.smtp.port"; + + /** Mail - SMTP username */ + public static final String MAIL_SMTP_USERNAME = "MAIL.smtp.username"; + + /** Mail - SMTP password */ + public static final String MAIL_SMTP_PASSWORD = "MAIL.smtp.password"; + + /** Mail - SMTP auth */ + public static final String MAIL_SMTP_AUTH = "MAIL.smtp.auth"; + + public static final String JS_VERSION = "JS.version"; + + public static final String SYS_PASSWORD_RULE_MIN = "SYS.password.rule.length.min"; + public static final String SYS_PASSWORD_RULE_DURATION = "SYS.password.rule.duration"; + public static final String SYS_PASSWORD_RULE_HISTORY = "SYS.password.rule.history"; + public static final String SYS_LOGIN_ATTEMPT_LIMIT = "SYS.loginAttempt.limit"; + public static final String SYS_LOGIN_ATTEMPT_PENALITY_TIME = "SYS.loginAttempt.penalityTime"; + + + public static final String SYS_REMINDER_BEFORE = "SYS.reminder.before"; + public static final String SYS_REMINDER_INTERVAL = "SYS.reminder.interval"; + public static final String SYS_REMINDER_LIMIT = "SYS.reminder.limit"; + public static final String SYS_REMINDER_LIMIT_MAX = "SYS.reminder.limit.maxValue"; + public static final String SYS_REMINDER_WITHIN = "SYS.reminder.eventWithin"; + + public static final String SYS_PASSWORD_RULE_NUMBER_AND_ALPHABETIC = "SYS.password.rule.numberAndAlphabetic"; + public static final String SYS_PASSWORD_RULE_SPECIAL_CHAR = "SYS.password.rule.specialCharacter"; + public static final String SYS_PASSWORD_RULE_NOT_CONTAIN_USERNAME = "SYS.password.rule.notContainUsername"; + public static final String SYS_PASSWORD_RULE_NOT_CONTAIN_THREE_CONSECUTIVE_CHAR = "SYS.password.rule.notContainThreeConsecutiveCharacters"; + + public static final String SYS_EMAIL_TEMPLATE_ANNOUNCEMENT = "SYS.email.template.announcement"; + public static final String SYS_EMAIL_TEMPLATE_REMINDER_NEWEVENT = "SYS.email.template.application.reminder.newEvent"; + public static final String SYS_EMAIL_TEMPLATE_REMINDER_OLDEVENT = "SYS.email.template.application.reminder.oldEvent"; +} diff --git a/src/main/java/com/ffii/lioner/modules/common/TempConst.java b/src/main/java/com/ffii/lioner/modules/common/TempConst.java new file mode 100644 index 0000000..f9d217d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/TempConst.java @@ -0,0 +1,122 @@ +package com.ffii.lioner.modules.common; + +public abstract class TempConst { + public static final String LOTUS_TEMP_CONST = "\n" + + "\n" + + "資訊科技經理/企業傳訊/1\n" + + "NULL\n" + + "10001001->10002101->10008587->10009567->10009595->10009660\n" + + "NULL\n" + + "CSD\n" + + "NULL\n" + + "16/6/2023 5:45:13\n" + + "NULL\n" + + "YAT_SING_LEUNG@EMSD.HKSARG\n" + + "yat_sing_leung\n" + + "Electrical and Mechanical Services Dept->Trading Services->Engineering Services Branch 3->Corporate Services Division->Corporate Communications Sub-division->Corporate Communications 2\n" + + "機電工程署->營運服務->工程服務科3->企業服務部->企業傳訊分部->企業傳訊2\n" + + "SE/CC\n" + + "NULL\n" + + "Customer Partnership 2\n" + + "NULL\n" + + "Mr\n" + + "NULL\n" + + "ITM/CC/1\n" + + "39120600 OR 63717182\n" + + "ITM\n" + + "NULL\n" + + "NULL\n" + + "NCSC\n" + + "10009660\n" + + "MALE\n" + + "NULL\n" + + "Ho Sing\n" + + "00011627\n" + + "LEUNGYS@EMSD.GOV.HK\n" + + "89C0\n" + + "00018114\n" + + "EMSDPerson\n" + + "NULL\n" + + "NULL\n" + + "00019383\n" + + "NULL\n" + + "NULL\n" + + "Information Technology Manager/Corporate Communications/1\n" + + "E4\n" + + "Information Technology Manager\n" + + "00019383\n" + + "梁日昇\n" + + "Active\n" + + "NULL\n" + + "NULL\n" + + "ITMCC1\n" + + "NULL\n" + + "NULL\n" + + "NULL\n" + + "NULL\n" + + "NULL\n" + + "LEUNG\n" + + "\n" + + "\n" + + "資訊科技經理/企業傳訊123/1\n" + + "NULL\n" + + "10001001->10002101->10008587->10009567->10009595->10009660\n" + + "NULL\n" + + "CSD\n" + + "NULL\n" + + "16/6/2023 5:45:13\n" + + "NULL\n" + + "YAT_SING_LEUNG@EMSD.HKSARG\n" + + "yat_sing_leung\n" + + "Electrical and Mechanical Services Dept->Trading Services->Engineering Services Branch 3->Corporate Services Division->Corporate Communications Sub-division->Corporate Communications 2\n" + + "機電工程署->營運服務->工程服務科3->企業服務部->企業傳訊分部->企業傳訊2\n" + + "SE/CC\n" + + "NULL\n" + + "Customer Partnership 2\n" + + "NULL\n" + + "Mr\n" + + "NULL\n" + + "ITM/CC/1\n" + + "39120600 OR 63717182\n" + + "ITM\n" + + "NULL\n" + + "NULL\n" + + "NCSC\n" + + "10009660\n" + + "MALE\n" + + "NULL\n" + + "Yat Sing\n" + + "00011627\n" + + "LEUNGYS@EMSD.GOV.HK\n" + + "89C0\n" + + "00018114\n" + + "EMSDPerson\n" + + "NULL\n" + + "NULL\n" + + "00019383\n" + + "NULL\n" + + "NULL\n" + + "Information Technology Manager/Corporate Communications/1\n" + + "E4\n" + + "Information Technology Manager\n" + + "00019383\n" + + "梁日昇2.0\n" + + "Active\n" + + "NULL\n" + + "NULL\n" + + "ITMCC1\n" + + "NULL\n" + + "NULL\n" + + "NULL\n" + + "NULL\n" + + "NULL\n" + + "LEUNG\n" + + "\n" + + ""; + + public static final String LOTUS_TEMP_NONE_CONST = "\n" + + "\n" + + "None" + + "\n" + + ""; +} diff --git a/src/main/java/com/ffii/lioner/modules/common/mail/pojo/MailRequest.java b/src/main/java/com/ffii/lioner/modules/common/mail/pojo/MailRequest.java new file mode 100644 index 0000000..401918f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/mail/pojo/MailRequest.java @@ -0,0 +1,372 @@ +package com.ffii.lioner.modules.common.mail.pojo; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.InternetAddress; + +public class MailRequest { + public final static int PRIORITY_HIGHEST = 1; + public final static int PRIORITY_HIGH = 2; + public final static int PRIORITY_NORMAL = 3; + public final static int PRIORITY_LOW = 4; + public final static int PRIORITY_LOWEST = 5; + + private static final String MAIL_SUFFIX = "@emsd.gov.hk"; + //private static final String MAIL_SUFFIX = "@2fi-solutions.com.hk"; + + private InternetAddress from; + private List to; + + private String subject; + + private String template; + private String templateContent; + private Map args; + + private Integer priority; + + private InternetAddress replyTo; + private List cc; + private List bcc; + + private Map attachments; + + public MailRequest() { + } + + public static Builder builder() { + return new Builder(); + } + + public void addAttachment(String attachmentFilename, byte[] byteArray) { + if (this.attachments == null) this.attachments = new HashMap<>(); + this.attachments.put(attachmentFilename, byteArray); + } + + public void addTo(InternetAddress to) { + if (this.to == null) this.to = new ArrayList<>(); + this.to.add(to); + } + + public void addCc(InternetAddress cc) { + if (this.cc == null) this.cc = new ArrayList<>(); + this.cc.add(cc); + } + + public void addBcc(InternetAddress bcc) { + if (this.bcc == null) this.bcc = new ArrayList<>(); + this.bcc.add(bcc); + } + + // getter setter + + public InternetAddress getFrom() { + return from; + } + + public void setFrom(InternetAddress from) { + this.from = from; + } + + public List getTo() { + return to; + } + + public void setTo(List to) { + this.to = to; + } + + public void setTo(String[] to) throws AddressException { + if (to == null) { + this.to = null; + } else { + for (String a : to) { + this.addTo(new InternetAddress(a)); + } + } + } + + public void setStringListTo(List to) throws AddressException { + if (to == null) { + this.to = null; + } else { + for (String a : to) { + this.addTo(new InternetAddress(a)); + } + } + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + + public String getTemplateContent() { + return this.templateContent; + } + + public void setTemplateContent(String templateContent) { + this.templateContent = templateContent; + } + + public Map getArgs() { + return args; + } + + public void setArgs(Map args) { + this.args = args; + } + + public InternetAddress getReplyTo() { + return replyTo; + } + + public void setReplyTo(InternetAddress replyTo) { + this.replyTo = replyTo; + } + + public List getCc() { + return cc; + } + + public void setCc(List cc) { + this.cc = cc; + } + + public void setCc(String[] cc) throws AddressException { + if (cc == null) { + this.cc = null; + } else { + for (String a : cc) { + this.addCc(new InternetAddress(a)); + } + } + } + + public void setStringListCc(List cc) throws AddressException { + if (cc == null) { + this.cc = null; + } else { + for (String a : cc) { + this.addCc(new InternetAddress(a)); + } + } + } + + public List getBcc() { + return bcc; + } + + public void setBcc(List bcc) { + this.bcc = bcc; + } + + public void setBcc(String[] bcc) throws AddressException { + if (bcc == null) { + this.bcc = null; + } else { + for (String a : bcc) { + this.addBcc(new InternetAddress(a)); + } + } + } + + public void setStringListBcc(List bcc) throws AddressException { + if (bcc == null) { + this.bcc = null; + } else { + for (String a : bcc) { + this.addBcc(new InternetAddress(a)); + } + } + } + + public Map getAttachments() { + return attachments; + } + + public void setAttachments(Map attachments) { + this.attachments = attachments; + } + + public Integer getPriority() { + return priority; + } + + public void setPriority(Integer priority) { + this.priority = priority; + } + + // classes + + public static class Builder { + private MailRequest mailRequest; + + private Builder() { + this.mailRequest = new MailRequest(); + } + + public MailRequest build() { + return this.mailRequest; + } + + public Builder addAttachment(String attachmentFilename, byte[] byteArray) { + this.mailRequest.addAttachment(attachmentFilename, byteArray); + return this; + } + + public Builder addTo(InternetAddress to) { + if (!to.getAddress().contains("@")) { + to.setAddress(to.getAddress() + MAIL_SUFFIX); + } + this.mailRequest.addTo(to); + return this; + } + + public Builder addCc(InternetAddress cc) { + this.mailRequest.addCc(cc); + return this; + } + + public Builder addBcc(InternetAddress bcc) { + this.mailRequest.addBcc(bcc); + return this; + } + + public Builder from(InternetAddress from) { + this.mailRequest.setFrom(from); + return this; + } + + public Builder to(List to) { + this.mailRequest.setTo(to); + return this; + } + + public Builder to(String[] to) throws AddressException { + if (to == null) { + this.mailRequest.setTo((List) null); + } else { + for (String a : to) { + this.addTo(new InternetAddress(a)); + } + } + return this; + } + + public Builder stringListTo(List to) throws AddressException { + if (to == null) { + this.mailRequest.setTo((List) null); + } else { + for (String a : to) { + this.addTo(new InternetAddress(a)); + } + } + return this; + } + + public Builder subject(String subject) { + this.mailRequest.setSubject(subject); + return this; + } + + public Builder template(String template) { + this.mailRequest.setTemplate(template); + return this; + } + + public Builder templateContent(String templateContent) { + this.mailRequest.setTemplateContent(templateContent); + return this; + } + + public Builder args(Map args) { + this.mailRequest.setArgs(args); + return this; + } + + public Builder replyTo(InternetAddress replyTo) { + this.mailRequest.setReplyTo(replyTo); + return this; + } + + public Builder cc(List cc) { + this.mailRequest.setCc(cc); + return this; + } + + public Builder cc(String[] cc) throws AddressException { + if (cc == null) { + this.mailRequest.setCc((List) null); + } else { + for (String a : cc) { + this.addCc(new InternetAddress(a)); + } + } + return this; + } + + public Builder stringListCc(List cc) throws AddressException { + if (cc == null) { + this.mailRequest.setCc((List) null); + } else { + for (String a : cc) { + this.addCc(new InternetAddress(a)); + } + } + return this; + } + + public Builder bcc(List bcc) { + this.mailRequest.setBcc(bcc); + return this; + } + + public Builder bcc(String[] bcc) throws AddressException { + if (bcc == null) { + this.mailRequest.setBcc((List) null); + } else { + for (String a : bcc) { + this.addCc(new InternetAddress(a)); + } + } + return this; + } + + public Builder stringListBcc(List bcc) throws AddressException { + if (bcc == null) { + this.mailRequest.setBcc((List) null); + } else { + for (String a : bcc) { + this.addCc(new InternetAddress(a)); + } + } + return this; + } + + public Builder attachments(Map attachments) { + this.mailRequest.setAttachments(attachments); + return this; + } + + public Builder priority(Integer priority) { + this.mailRequest.setPriority(priority); + return this; + } + } +} diff --git a/src/main/java/com/ffii/lioner/modules/common/mail/service/MailSenderService.java b/src/main/java/com/ffii/lioner/modules/common/mail/service/MailSenderService.java new file mode 100644 index 0000000..4071803 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/mail/service/MailSenderService.java @@ -0,0 +1,57 @@ +package com.ffii.lioner.modules.common.mail.service; + +import java.util.Properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.MailSMTP; +import com.ffii.lioner.modules.settings.service.SettingsService; + +/** caching mail sender if config no changed */ +@Service +public class MailSenderService { + + private SettingsService settingsService; + + private JavaMailSender sender; + private MailSMTP mailConfigCachs; + + @Value("${emsd.webservice.required}") + private Boolean ldapWebServiceRequired; + + public MailSenderService(SettingsService settingsService) { + this.settingsService = settingsService; + } + + public JavaMailSender get() { + MailSMTP config = new MailSMTP(settingsService); + if (this.sender == null || mailConfigCachs == null || !config.equals(this.mailConfigCachs)) { + this.mailConfigCachs = config; + JavaMailSenderImpl sender = new JavaMailSenderImpl(); + + Properties props = new Properties(); + if(!ldapWebServiceRequired){ + props.put("mail.smtp.timeout", "20000"); + props.put("mail.smtp.connectiontimeout", "10000"); + } + props.put("mail.smtp.auth", config.getAuth()); + props.put("mail.smtp.starttls.enable", "true"); + + + sender.setHost(config.getHost()); + sender.setPort(config.getPort()); + if(!ldapWebServiceRequired){ + sender.setUsername(config.getUsername()); + sender.setPassword(config.getPassword()); + } + sender.setJavaMailProperties(props); + + this.sender = sender; + } + + return this.sender; + } +} diff --git a/src/main/java/com/ffii/lioner/modules/common/mail/service/MailService.java b/src/main/java/com/ffii/lioner/modules/common/mail/service/MailService.java new file mode 100644 index 0000000..bb8c0b7 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/mail/service/MailService.java @@ -0,0 +1,228 @@ +package com.ffii.lioner.modules.common.mail.service; + +import java.io.IOException; +import java.text.ParseException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.ui.freemarker.FreeMarkerTemplateUtils; + +import com.ffii.lioner.modules.common.ErrorCodes; +import com.ffii.lioner.modules.common.LocalDateAdapter; +import com.ffii.lioner.modules.common.SettingNames; +import com.ffii.lioner.modules.common.mail.pojo.MailRequest; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.core.exception.InternalServerErrorException; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.LocaleUtils; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import freemarker.cache.StringTemplateLoader; +import freemarker.template.Configuration; +import freemarker.template.MalformedTemplateNameException; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import freemarker.template.TemplateNotFoundException; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; + +@Service +public class MailService { + protected final Log logger = LogFactory.getLog(getClass()); + + private JdbcDao jdbcDao; + private MailSenderService mailSenderService; + private Configuration freemarkerConfig; + private SettingsService settingsService; + + public MailService(JdbcDao jdbcDao, MailSenderService mailSenderService, Configuration freemarkerConfig, + SettingsService settingsService) { + this.jdbcDao = jdbcDao; + this.mailSenderService = mailSenderService; + this.freemarkerConfig = freemarkerConfig; + this.settingsService = settingsService; + } + + private void doSend(List mailRequests, Locale locale) + throws MessagingException, TemplateNotFoundException, MalformedTemplateNameException, ParseException, IOException, + TemplateException { + JavaMailSender sender = mailSenderService.get(); + for (MailRequest mailRequest : mailRequests) { + MimeMessage mimeMessage = sender.createMimeMessage(); + + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); + helper.setSubject(mailRequest.getSubject()); + + Template template = null; + if (mailRequest.getTemplate() != null) { + try { + template = freemarkerConfig + .getTemplate(mailRequest.getTemplate() + "_" + LocaleUtils.toLocaleStr(locale) + ".ftl"); + } catch (TemplateNotFoundException e) { + template = freemarkerConfig.getTemplate(mailRequest.getTemplate() + ".ftl"); + } + } else { + // Create a FreeMarker configuration + Configuration configuration = new Configuration(Configuration.VERSION_2_3_32); + + // Create a template loader and add the template content + StringTemplateLoader templateLoader = new StringTemplateLoader(); + templateLoader.putTemplate("myTemplate", mailRequest.getTemplateContent()); + configuration.setTemplateLoader(templateLoader); + + try { + // Get the template by name + template = configuration.getTemplate("myTemplate"); + } catch (IOException e) { + // Handle any IO exceptions + } + } + + helper.setText( + FreeMarkerTemplateUtils.processTemplateIntoString(template, mailRequest.getArgs()), + true); + if (mailRequest.getFrom() != null) { + helper.setFrom(mailRequest.getFrom()); + } else { + helper.setFrom(settingsService.getString(SettingNames.MAIL_SMTP_USERNAME)); + } + + if (mailRequest.getPriority() != null) + helper.setPriority(mailRequest.getPriority()); + if (mailRequest.getReplyTo() != null) + helper.setReplyTo(mailRequest.getReplyTo()); + if (mailRequest.getTo() != null) + helper.setTo(mailRequest.getTo().toArray(new InternetAddress[mailRequest.getTo().size()])); + if (mailRequest.getCc() != null) + helper.setCc(mailRequest.getCc().toArray(new InternetAddress[mailRequest.getCc().size()])); + if (mailRequest.getBcc() != null) + helper.setBcc(mailRequest.getBcc().toArray(new InternetAddress[mailRequest.getBcc().size()])); + + if (mailRequest.getAttachments() != null) { + for (Map.Entry entry : mailRequest.getAttachments().entrySet()) { + helper.addAttachment(entry.getKey(), new ByteArrayResource(entry.getValue())); + } + } + sender.send(mimeMessage); + } + } + + public void send(List mailRequests, Locale locale) throws ParseException { + try { + doSend(mailRequests, locale); + } catch (MessagingException | IOException | TemplateException e) { + throw new InternalServerErrorException(ErrorCodes.SEND_EMAIL_ERROR, e); + } + } + + public void sendARS(List mailRequests, Locale locale, Long eventId, Long subDivisionId, Long userId, + String reminderType) throws ParseException { + // Create an instance of the GsonBuilder + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter()); + Gson gson = gsonBuilder.create(); + + String jsonString = gson.toJson(mailRequests.get(0)); + try { + doSend(mailRequests, locale); + } catch (MessagingException | IOException | TemplateException e) { + Map value = Map.of( + "eventId", eventId, + "subDivisionId", subDivisionId, + "reminderType", reminderType, + "userId", userId, + "sendDate", LocalDateTime.now(), + "success", false, + "response", e.getMessage(), + "content", jsonString, + "resendSuccess", false); + jdbcDao.executeUpdate( + "INSERT IGNORE INTO todo_reminder_email_log (eventId, subDivisionId, reminderType, userId, sendDate, success, response, content, resendSuccess)" + + " VALUE (:eventId, :subDivisionId, :reminderType, :userId, :sendDate, :success, :response, :content, :resendSuccess)", + value); + } + Map value = Map.of( + "eventId", eventId, + "subDivisionId", subDivisionId, + "reminderType", reminderType, + "userId", userId, + "sendDate", LocalDateTime.now(), + "success", true, + "content", jsonString, + "resendSuccess", false); + jdbcDao.executeUpdate( + "INSERT IGNORE INTO todo_reminder_email_log (eventId, subDivisionId, reminderType, userId, sendDate, success, content, resendSuccess)" + + " VALUE (:eventId, :subDivisionId, :reminderType, :userId, :sendDate, :success ,:content, :resendSuccess)", + value); + } + + public void resendARS(MailRequest mailRequestObj, Long emailLogId) throws ParseException { + List mailRequests = new ArrayList(); + mailRequests.add(mailRequestObj); + Locale locale = Locale.ENGLISH; + + // Convert the object to JSON string + try { + doSend(mailRequests, locale); + } catch (MessagingException | IOException | TemplateException e) { + //error still occur + } + jdbcDao.executeUpdate( + " UPDATE todo_reminder_email_log " + + " SET resendSuccess=1 " + + " WHERE id = :emailLogId", + Map.of("emailLogId", emailLogId)); + } + + @Async + public void asyncSend(List mailRequests, Locale locale) throws ParseException { + try { + doSend(mailRequests, locale); + } catch (MessagingException | IOException | TemplateException e) { + logger.error("send email error", e); + } + } + + public void send(List mailRequests) throws ParseException { + send(mailRequests, LocaleUtils.getLocale()); + } + + @Async + public void asyncSend(List mailRequests) throws ParseException { + asyncSend(mailRequests, LocaleUtils.getLocale()); + } + + public void send(MailRequest mailRequest) throws ParseException { + send(Arrays.asList(mailRequest)); + } + + @Async + public void asyncSend(MailRequest mailRequest) throws ParseException { + asyncSend(Arrays.asList(mailRequest)); + } + + public void send(MailRequest mailRequest, Locale locale) throws ParseException { + send(Arrays.asList(mailRequest), locale); + } + + @Async + public void asyncSend(MailRequest mailRequest, Locale locale) throws ParseException { + asyncSend(Arrays.asList(mailRequest), locale); + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/common/service/AuditLogService.java b/src/main/java/com/ffii/lioner/modules/common/service/AuditLogService.java new file mode 100644 index 0000000..e03b019 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/service/AuditLogService.java @@ -0,0 +1,108 @@ +package com.ffii.lioner.modules.common.service; + +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.core.support.AbstractService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.MapUtils; +import com.ffii.core.utils.Params; + +import jakarta.annotation.Nullable; + +@Service +public class AuditLogService extends AbstractService { + + public AuditLogService(JdbcDao jdbcDao) { + super(jdbcDao); + } + + private static final String SQL_INSERT_AUDIT_LOG = "INSERT INTO audit_log (`tableName`, `recordId`, `recordName`, `modifiedBy`, `modified`, `oldData`, `newData`) " + + "VALUES (:tableName, :recordId, :recordName, :modifiedBy, :modified, :oldData, :newData)"; + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + public int save(String tableName, Long recordId, String recordName, Long modifiedBy, Date modified, @Nullable String oldData, String newData) { + return jdbcDao.executeUpdate(SQL_INSERT_AUDIT_LOG,MapUtils.toHashMap("tableName", tableName, "recordId", recordId, + "recordName",recordName, "modifiedBy", modifiedBy, "modified", modified, + "oldData", oldData, "newData", newData)); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public List> search(String tableName, Integer recordId) { + + String sql = "SELECT * FROM audit_log WHERE tableName = :tableName AND recordId = :recordId ORDER BY modified"; + + return jdbcDao.queryForList(sql, Map.of("tableName", tableName, "recordId", recordId)); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public List> search(String tableName, String recordId) { + + String sql = "SELECT * FROM audit_log WHERE tableName = :tableName AND recordId = :recordId ORDER BY modified"; + + return jdbcDao.queryForList(sql, Map.of("tableName", tableName, "recordId", recordId)); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public List> arsSearch(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " *, " + + " u.username " + + " FROM audit_log al" + + " LEFT JOIN user u on u.id = al.modifiedBy " + + " WHERE al.tableName is not NULL " + ); + + if (args != null) { + if (args.containsKey("tableName")) sql.append(" AND al.tableName = :tableName "); + if (args.containsKey("recordName")) sql.append(" AND al.recordName LIKE :recordName"); + if (args.containsKey("fromDate"))sql.append(" AND DATE(al.modified) >= :fromDate"); + if (args.containsKey("toDate"))sql.append(" AND DATE(al.modified) < :toDate"); + if (args.containsKey("modifiedBy")) sql.append(" AND al.modifiedBy = :modifiedBy"); + } + sql.append(" ORDER BY al.modified desc "); + return jdbcDao.queryForList(sql.toString(), args); + } + + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public List> getTables() { + String sql = "SELECT DISTINCT tableName FROM audit_log"; + + return jdbcDao.queryForList(sql, ""); + } + + public Map compareMaps(Map map1, Map map2) { + Map diffMap = new HashMap<>(); + + // Iterate over the entries of map2 + for (Map.Entry entry : map2.entrySet()) { + String key = entry.getKey(); + Object value2 = entry.getValue(); + Object value1 = map1.get(key); + + // Compare the values + if (value1 != null) { + if (value1.getClass().isArray() && value2.getClass().isArray()) { + if (!Arrays.deepEquals((Object[]) value1, (Object[]) value2)) { + diffMap.put(key, value2); + } + } else if (!value1.equals(value2)) { + diffMap.put(key, value2); + } + } + else if(value2 != null){ + diffMap.put(key, value2); + } + } + + return diffMap; + } +} diff --git a/src/main/java/com/ffii/lioner/modules/common/service/ExcelReportService.java b/src/main/java/com/ffii/lioner/modules/common/service/ExcelReportService.java new file mode 100644 index 0000000..7afed47 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/service/ExcelReportService.java @@ -0,0 +1,546 @@ +package com.ffii.lioner.modules.common.service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.reportDao.ImportErrorRecord; + +@Service +public class ExcelReportService { + private static final String DASHBOARD_SUMMARY_FILE_PATH = "templates/report/awardSummaryByDivision.xlsx"; + private static final String IMPORT_TEMPLATE_PATH = "templates/report/Appreciation_Records_Import_Template_v1.0.xlsx"; + private static final String AWARD_IMPORT_TEMPLATE_PATH = "templates/report/Award_Record_Import_Template_v1.0.xlsx"; + private final Log logger = LogFactory.getLog(getClass()); + + public byte[] generateDashboardExcelReport(List> queryResults, List> detailResult, Integer year) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createDashboardExcelReport(queryResults, detailResult, DASHBOARD_SUMMARY_FILE_PATH,year); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createDashboardExcelReport(List> queryResults, List> detailResult, String templatePath, Integer year) throws IOException { + ClassPathResource resource = new ClassPathResource(templatePath); + InputStream templateInputStream = resource.getInputStream(); + Workbook workbook = new XSSFWorkbook(templateInputStream); + + // Page 1 + Sheet sheet = workbook.getSheetAt(0); + + int rowIndex = 1; // Assuming the location is in the first row + int columnIndex = 0; + Row tempRow = sheet.getRow(rowIndex); + + LocalDate currentDate = LocalDate.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); + DateTimeFormatter displayFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + String formattedDate = currentDate.format(formatter); + sheet.getHeader().setCenter(year.toString() + " Award Summary by Division" ); + sheet.getHeader().setRight("Printed Date: " + formattedDate ); + Cell tempCell = tempRow.getCell(columnIndex); + CellStyle cellStyle = tempCell.getCellStyle(); + + int rowNum = 1; + for (Map result : queryResults) { + Row row = sheet.createRow(rowNum++); + Cell nameCell = row.createCell(0); + nameCell.setCellValue(result.get("name").toString()); + nameCell.setCellStyle(cellStyle); + + Cell countCell = row.createCell(1); + countCell.setCellValue( Integer.parseInt(result.get("count").toString())); + countCell.setCellStyle(cellStyle); + // Add more cells/columns as needed + } + + // Auto-size columns + for (int i = 0; i < queryResults.size(); i++) { + sheet.autoSizeColumn(i); + } + + // Page 2 + sheet = workbook.getSheetAt(1); + + rowIndex = 1; // Assuming the location is in the first row + columnIndex = 0; + tempRow = sheet.getRow(rowIndex); + tempCell = tempRow.getCell(columnIndex); + cellStyle = tempCell.getCellStyle(); + + currentDate = LocalDate.now(); + formatter = DateTimeFormatter.ofPattern("dd MMM yyyy"); + formattedDate = currentDate.format(formatter); + sheet.getHeader().setCenter(year.toString() + " Award Summary by Division" ); + sheet.getHeader().setRight("Printed Date: " + formattedDate ); + + rowNum = 1; + for (Map result : detailResult) { + Row row = sheet.createRow(rowNum++); + Cell nameCell = row.createCell(0); + nameCell.setCellValue(result.get("name").toString()); + nameCell.setCellStyle(cellStyle); + + Cell countCell = row.createCell(1); + countCell.setCellValue( result.get("subDivision").toString()); + countCell.setCellStyle(cellStyle); + CellStyle temp = countCell.getCellStyle(); + temp.setWrapText(true); + countCell.setCellStyle(temp); + + Cell eventNameCell = row.createCell(2); + eventNameCell.setCellValue( result.get("eventName").toString()); + eventNameCell.setCellStyle(cellStyle); + + Cell applicationNameCell = row.createCell(3); + applicationNameCell.setCellValue( result.get("applicationName").toString()); + applicationNameCell.setCellStyle(cellStyle); + + Cell awardNameCell = row.createCell(4); + awardNameCell.setCellValue( result.get("awardName").toString()); + awardNameCell.setCellStyle(cellStyle); + + Cell receiveDateCell = row.createCell(5); + receiveDateCell.setCellValue( result.get("receiveDate") == null ? "" : ((LocalDateTime) result.get("receiveDate")).format(displayFormatter)); + receiveDateCell.setCellStyle(cellStyle); + + Cell categoryCell = row.createCell(6); + categoryCell.setCellValue( result.get("category").toString()); + categoryCell.setCellStyle(cellStyle); + + Cell responsibleOfficerCell = row.createCell(7); + responsibleOfficerCell.setCellValue(result.get("responsibleOfficer").toString()); + responsibleOfficerCell.setCellStyle(cellStyle); + } + + // Auto-size columns + for (int i = 0; i < queryResults.size(); i++) { + if(i!= 1){ + sheet.autoSizeColumn(i); + } + } + + return workbook; + } + + public byte[] generateEventExcelReport(List> queryResults) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createEventExcelReport(queryResults); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createEventExcelReport(List> queryResults) throws IOException { + Workbook workbook = new XSSFWorkbook(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + // Page 1 + Sheet sheet = workbook.createSheet("Event Data"); + + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("id"); + headerRow.createCell(1).setCellValue("Created By"); + headerRow.createCell(2).setCellValue("Created"); + headerRow.createCell(3).setCellValue("Name"); + headerRow.createCell(4).setCellValue("Chinese Name"); + headerRow.createCell(5).setCellValue("Description"); + headerRow.createCell(6).setCellValue("Region"); + headerRow.createCell(7).setCellValue("Division"); + headerRow.createCell(8).setCellValue("Organization"); + headerRow.createCell(9).setCellValue("Event Type"); + headerRow.createCell(10).setCellValue("Frequency"); + headerRow.createCell(11).setCellValue("Series"); + headerRow.createCell(12).setCellValue("Application Date"); + headerRow.createCell(13).setCellValue("Application Deadline"); + headerRow.createCell(14).setCellValue("Next Application Date"); + headerRow.createCell(15).setCellValue("Announcement Date"); + headerRow.createCell(16).setCellValue("Award Date"); + headerRow.createCell(17).setCellValue("Reminder Flag"); + headerRow.createCell(18).setCellValue("Reminder Threshold"); + headerRow.createCell(19).setCellValue("Reminder Interval"); + headerRow.createCell(20).setCellValue("Reminder Limit"); + + int rowNum = 1; + for (Map result : queryResults) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); + row.createCell(1).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); + row.createCell(2).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); + row.createCell(3).setCellValue(result.get("name").toString()); + row.createCell(4).setCellValue(result.get("nameCht") == null ? "" : result.get("nameCht").toString()); + row.createCell(5).setCellValue(result.get("description") == null ? "" : result.get("description").toString()); + row.createCell(6).setCellValue(result.get("region") == null ? "" : result.get("region").toString()); + row.createCell(7).setCellValue(result.get("divisionList") == null ? "" : result.get("divisionList").toString()); + row.createCell(8).setCellValue(result.get("organization") == null ? "" : result.get("organization").toString()); + row.createCell(9).setCellValue(result.get("eventType") == null ? "" : result.get("eventType").toString()); + row.createCell(10).setCellValue(result.get("frequency") == null ? "" : result.get("frequency").toString()); + row.createCell(11).setCellValue(result.get("series") == null ? "" : result.get("series").toString()); + row.createCell(12).setCellValue(((LocalDateTime)result.get("startDate")).format(formatter)); + row.createCell(13).setCellValue(((LocalDateTime)result.get("applicationDeadline")).format(formatter)); + row.createCell(14).setCellValue(result.get("nextApplicationDate") == null ? "" :((LocalDateTime)result.get("nextApplicationDate")).format(formatter)); + row.createCell(15).setCellValue(result.get("announcementDate") == null ? "" : ((LocalDateTime)result.get("announcementDate")).format(formatter)); + row.createCell(16).setCellValue(result.get("awardDate") == null ? "" : ((LocalDateTime )result.get("awardDate")).format(formatter)); + row.createCell(17).setCellValue(result.get("reminderFlag").toString()); + row.createCell(18).setCellValue(result.get("reminderThreshold") == null ? 0 : Integer.parseInt(result.get("reminderThreshold").toString())); + row.createCell(19).setCellValue(result.get("reminderInterval") == null ? 0 : Integer.parseInt(result.get("reminderInterval").toString())); + row.createCell(20).setCellValue(result.get("reminderLimit") == null ? 0 : Integer.parseInt(result.get("reminderLimit").toString())); + // Add more cells/columns as needed + } + + // Auto-size columns + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + sheet.autoSizeColumn(i); + } + + return workbook; + } + + public byte[] generateApplicationExcelReport(List> queryResults) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createApplicationExcelReport(queryResults); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createApplicationExcelReport(List> queryResults) throws IOException { + Workbook workbook = new XSSFWorkbook(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + // Page 1 + Sheet sheet = workbook.createSheet("Application Data"); + + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("id"); + headerRow.createCell(1).setCellValue("Created By"); + headerRow.createCell(2).setCellValue("Created"); + headerRow.createCell(3).setCellValue("Event Name"); + headerRow.createCell(4).setCellValue("Application Name"); + headerRow.createCell(5).setCellValue("Description"); + headerRow.createCell(6).setCellValue("Division"); + headerRow.createCell(7).setCellValue("Responsible Officer"); + headerRow.createCell(8).setCellValue("Status"); + + int rowNum = 1; + for (Map result : queryResults) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); + row.createCell(1).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); + row.createCell(2).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); + + row.createCell(3).setCellValue(result.get("eventName").toString()); + row.createCell(4).setCellValue(result.get("name").toString()); + row.createCell(5).setCellValue(result.get("description") == null ? "" : result.get("description").toString()); + row.createCell(6).setCellValue(result.get("divisionList") == null ? "" : result.get("divisionList").toString()); + row.createCell(7).setCellValue(result.get("responsibleOfficer").toString()); + row.createCell(8).setCellValue(result.get("status").toString()); + // Add more cells/columns as needed + } + + // Auto-size columns + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + sheet.autoSizeColumn(i); + + } + + return workbook; + } + + public byte[] generateAwardExcelReport(List> queryResults) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createAwardExcelReport(queryResults); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createAwardExcelReport(List> queryResults) throws IOException { + Workbook workbook = new XSSFWorkbook(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + // Page 1 + Sheet sheet = workbook.createSheet("Award Data"); + + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("id"); + headerRow.createCell(1).setCellValue("Created By"); + headerRow.createCell(2).setCellValue("Created"); + headerRow.createCell(3).setCellValue("Event Name"); + headerRow.createCell(4).setCellValue("Application Name"); + headerRow.createCell(5).setCellValue("Award Name"); + headerRow.createCell(6).setCellValue("Award Chinese Name"); + headerRow.createCell(7).setCellValue("Remarks"); + headerRow.createCell(8).setCellValue("Division"); + headerRow.createCell(9).setCellValue("Category"); + headerRow.createCell(10).setCellValue("Receive Date"); + headerRow.createCell(11).setCellValue("Storage Location"); + headerRow.createCell(12).setCellValue("Physical Award"); + headerRow.createCell(13).setCellValue("Responsible Officer"); + + int rowNum = 1; + for (Map result : queryResults) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); + row.createCell(1).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); + row.createCell(2).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); + row.createCell(3).setCellValue(result.get("eventName").toString()); + row.createCell(4).setCellValue(result.get("applicationName").toString()); + row.createCell(5).setCellValue(result.get("name").toString()); + row.createCell(6).setCellValue(result.get("nameCht") == null ? "" : result.get("nameCht").toString()); + row.createCell(7).setCellValue(result.get("remarks") == null ? "" : result.get("remarks").toString()); + row.createCell(8).setCellValue(result.get("divisionList") == null ? "" : result.get("divisionList").toString()); + row.createCell(9).setCellValue(result.get("categoryName").toString()); + row.createCell(10).setCellValue(result.get("receiveDate") == null ? "" : ((LocalDateTime)result.get("receiveDate")).format(formatter)); + row.createCell(11).setCellValue(result.get("storageLocation") == null ? "" : result.get("storageLocation").toString()); + row.createCell(12).setCellValue(result.get("physicalAward") == null ? "" : result.get("physicalAward").toString()); + row.createCell(13).setCellValue(result.get("responsibleOfficer") == null ? "" : result.get("responsibleOfficer").toString()); + // Add more cells/columns as needed + } + + // Auto-size columns + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + sheet.autoSizeColumn(i); + + } + + return workbook; + } + + public byte[] exportAppreciationTemplate() throws IOException { + ClassPathResource resource = new ClassPathResource(IMPORT_TEMPLATE_PATH); + InputStream templateInputStream = resource.getInputStream(); + Workbook workbook = new XSSFWorkbook(templateInputStream); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + public byte[] exportAwardImportTemplate(Map>> masterData) throws IOException { + ClassPathResource resource = new ClassPathResource(AWARD_IMPORT_TEMPLATE_PATH); + InputStream templateInputStream = resource.getInputStream(); + Workbook workbook = new XSSFWorkbook(templateInputStream); + + // Iterate over the map entries + for (Map.Entry>> entry : masterData.entrySet()) { + String key = entry.getKey(); + List> values = entry.getValue(); + // Find the sheet by the key + Sheet sheet = workbook.getSheet(key); + + if (sheet != null) { + + // Write the list values to the sheet + int rowNum = 0; + for (Mapvalue : values) { + Row row = sheet.createRow(rowNum++); + Cell cell = row.createCell(0); + cell.setCellValue(value.get("name").toString()); + } + } else { + // Handle case where sheet with the key doesn't exist + System.out.println("Sheet with key " + key + " not found."); + } + } + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + public byte[] generateAppreciationExcelReport(List> queryResults) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createAppreciationExcelReport(queryResults); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createAppreciationExcelReport(List> queryResults) throws IOException { + Workbook workbook = new XSSFWorkbook(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + // Page 1 + Sheet sheet = workbook.createSheet("Award Data"); + + Row headerRow = sheet.createRow(0); + //headerRow.createCell(0).setCellValue("id"); + headerRow.createCell(0).setCellValue("Created By"); + headerRow.createCell(1).setCellValue("Created"); + headerRow.createCell(2).setCellValue("SBU / Division"); + headerRow.createCell(3).setCellValue("Receipt Date"); + headerRow.createCell(4).setCellValue("Brief Description"); + headerRow.createCell(5).setCellValue("Venue Involved"); + headerRow.createCell(6).setCellValue("Client Department / Organization"); + headerRow.createCell(7).setCellValue("Client Full Name"); + headerRow.createCell(8).setCellValue("Client Post"); + headerRow.createCell(9).setCellValue("Client Reply Date"); + headerRow.createCell(10).setCellValue("Staff Reply Date"); + headerRow.createCell(11).setCellValue("No. of Staff"); + headerRow.createCell(12).setCellValue("LN Receipt Date"); + headerRow.createCell(13).setCellValue("Category"); + headerRow.createCell(14).setCellValue("Remarks"); + + + int rowNum = 1; + for (Map result : queryResults) { + Row row = sheet.createRow(rowNum++); + //row.createCell(0).setCellValue(Integer.parseInt(result.get("id").toString())); + row.createCell(0).setCellValue(result.get("username") == null ? "" : result.get("username").toString()); + row.createCell(1).setCellValue(((LocalDateTime)result.get("created")).format(formatter)); + row.createCell(2).setCellValue(result.get("divisionString").toString()); + row.createCell(3).setCellValue(result.get("receiptDate") == null ? "" : ((LocalDateTime)result.get("receiptDate")).format(formatter)); + row.createCell(4).setCellValue(result.get("description").toString()); + row.createCell(5).setCellValue(result.get("venue") == null ? "" : result.get("venue").toString()); + row.createCell(6).setCellValue(result.get("clientDepartment") == null ? + result.get("clientOrganization").toString().length() == 0 ? "" : result.get("clientOrganization").toString() + : result.get("clientDepartment").toString()); + row.createCell(7).setCellValue(result.get("clientFullname") == null ? "" : result.get("clientFullname").toString()); + row.createCell(8).setCellValue(result.get("clientPost") == null ? "" : result.get("clientPost").toString()); + row.createCell(9).setCellValue(result.get("clientReplyDate") == null ? "" : ((LocalDateTime)result.get("clientReplyDate")).format(formatter)); + row.createCell(10).setCellValue(result.get("staffReplyDate") == null ? "" : ((LocalDateTime)result.get("staffReplyDate")).format(formatter)); + row.createCell(11).setCellValue(result.get("noOfStaff") == null ? "" : result.get("noOfStaff").toString()); + row.createCell(12).setCellValue(((LocalDateTime)result.get("lnReceiptDate")).format(formatter)); + row.createCell(13).setCellValue(result.get("category").toString()); + row.createCell(14).setCellValue(result.get("remarks") == null ? "" : result.get("remarks").toString()); + // Add more cells/columns as needed + } + + // Auto-size columns + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + sheet.autoSizeColumn(i); + + } + + return workbook; + } + + public byte[] generateAppreciationImportErrorReport(List record) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createAppreciationImportErrorExcelReport(record); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createAppreciationImportErrorExcelReport(List record) throws IOException { + Workbook workbook = new XSSFWorkbook(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + // Page 1 + Sheet sheet = workbook.createSheet("Error Record"); + + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("Row"); + headerRow.createCell(1).setCellValue("Column"); + headerRow.createCell(2).setCellValue("Error Message"); + + + int rowNum = 1; + for (ImportErrorRecord result : record) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(result.getRowId().toString()); + row.createCell(1).setCellValue(result.getColumn()); + row.createCell(2).setCellValue(result.getRemarks()); + } + + // Auto-size columns + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + sheet.autoSizeColumn(i); + } + + return workbook; + } + + public byte[] generateAwardImportErrorReport(List record) throws IOException { + // Generate the Excel report with query results + Workbook workbook = createAwardImportErrorExcelReport(record); + + // Write the workbook to a ByteArrayOutputStream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + workbook.close(); + + return outputStream.toByteArray(); + } + + private Workbook createAwardImportErrorExcelReport(List record) throws IOException { + Workbook workbook = new XSSFWorkbook(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + // Page 1 + Sheet sheet = workbook.createSheet("Error Record"); + + Row headerRow = sheet.createRow(0); + headerRow.createCell(0).setCellValue("Row"); + headerRow.createCell(1).setCellValue("Page Name"); + headerRow.createCell(2).setCellValue("Column"); + headerRow.createCell(3).setCellValue("Error Message"); + + + int rowNum = 1; + for (ImportErrorRecord result : record) { + Row row = sheet.createRow(rowNum++); + row.createCell(0).setCellValue(result.getRowId().toString()); + row.createCell(1).setCellValue(result.getPageName()); + row.createCell(2).setCellValue(result.getColumn()); + row.createCell(3).setCellValue(result.getRemarks()); + } + + // Auto-size columns + for (int i = 0; i < headerRow.getLastCellNum(); i++) { + sheet.autoSizeColumn(i); + } + + return workbook; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/common/service/WordReportService.java b/src/main/java/com/ffii/lioner/modules/common/service/WordReportService.java new file mode 100644 index 0000000..0204ddb --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/common/service/WordReportService.java @@ -0,0 +1,308 @@ +package com.ffii.lioner.modules.common.service; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.reportDao.AppreciationCaseReportDao; +import com.ffii.lioner.modules.lioner.reportDao.AppreciationChartDataDao; +import com.ffii.core.utils.NumberUtils; +import com.ffii.core.utils.RomanConverter; + +import freemarker.template.Configuration; +import freemarker.template.Template; +import io.micrometer.common.util.StringUtils; + +@Service +public class WordReportService { + + private static final String XML_TEST_TEMPLATE_PATH = "templates/report/Report 1 - Appreciations Case (Sample) - test.ftl"; + private static final String APPRECIATION_SUMMARY_TEMPLATE_PATH = "templates/report/Report 2 - Annex III – Summary of Appreciation Cases.ftl"; + private static final String[] chartColors = new String[] { "7FC817", "FF4F90", "FFD453", "FFF2CB", "41D3F3", + "795AEE", "FFF547", "F9CB27", "EA871E", "FC001D", "4A2E95", "C8CBCD" }; + + public byte[] generateTestXMLFile() throws IOException { + ClassPathResource resource = new ClassPathResource(XML_TEST_TEMPLATE_PATH); + InputStream templateInputStream = resource.getInputStream(); + + try { + // Load the FTL template + Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); + cfg.setClassForTemplateLoading(WordReportService.class, "/"); + Template template = cfg.getTemplate(XML_TEST_TEMPLATE_PATH); + + // Create a data model + Map dataModel = new HashMap<>(); + dataModel.put("reportDate", "June 2023"); + dataModel.put("monthTotal", String.format("%s (%d)", NumberUtils.convertToWord(14), 14)); + + List> catBreakDowns = new ArrayList<>(); + catBreakDowns.add(Map.of("catTotal", String.format("%s (%d)", NumberUtils.convertToWord(6), 6), "catName", + "project")); + catBreakDowns.add(Map.of("catTotal", String.format("%s (%d)", NumberUtils.convertToWord(4), 4), "catName", + "maintenance")); + catBreakDowns.add( + Map.of("catTotal", String.format("%s (%d)", NumberUtils.convertToWord(4), 4), "catName", "AA&I")); + + dataModel.put("catBreakDowns", catBreakDowns); + + dataModel.put("compareStr1", "more than"); + dataModel.put("compareStr2", "by five"); + + dataModel.put("finYear", "2023/24"); + dataModel.put("yearTotal", 50); + + LinkedHashMap sbuYearTotal = new LinkedHashMap<>(); + sbuYearTotal.put("BTSD", 1); + sbuYearTotal.put("CSD", 2); + sbuYearTotal.put("GESD", 3); + sbuYearTotal.put("HSD", 4); + sbuYearTotal.put("MunSD", 5); + sbuYearTotal.put("SVSD", 6); + sbuYearTotal.put("DTD", 7); + + dataModel.put("sbuYearTotal", sbuYearTotal); + + int idx = 0; + + List> series = new ArrayList<>(); + List sbuList = Arrays.asList("BTSD", "CSD", "GESD", "HSD", "MunSD", "SVSD", "DTD"); + + for (String sbu : sbuList) { + Map serie = new HashMap<>(); + serie.put("serieName", sbu); + serie.put("color", chartColors[idx % (chartColors.length - 1)]); + serie.put("data", + Arrays.asList((int) (Math.random() * 1), (int) (Math.random() * 2), (int) (Math.random() * 3), + (int) (Math.random() * 4), (int) (Math.random() * 5), (int) (Math.random() * 6), + (int) (Math.random() * 7), (int) (Math.random() * 8), (int) (Math.random() * 9), + (int) (Math.random() * 10), (int) (Math.random() * 11), (int) (Math.random() * 12))); + series.add(serie); + + idx++; + } + + dataModel.put("series", series); + + dataModel.put("lnReceiptDateRange", "17 Jul 2023 – 31 Jul 2023"); + + List> aprRecords = new ArrayList<>(); + + aprRecords.add(Map.of("sbu", "BTSD", "receiptDate", "17 Jul 2023", "description", + "An appreciation received on 28 July 2023 via 1823 from a citizen expressing appreciation to EMSD colleagues for their prompt action to rectify damaged traffic signal equipment located at Chatham Road North, Hung Hom.", + "clientReplyDate", "03 Jul 2023", "staffReplyDate", "04 Jul 2023", "numOfStaff", 4, "clientDeptOrg", + "Transport Department")); + + aprRecords.add(Map.of("sbu", "BTSD", "receiptDate", "22 Jul 2023", "description", + "Mr. Patrick K P CHENG, Chief Engineer/Traffic Survey & Support of TD, expressed the appreciation to BTSD’s colleagues for all the efforts and continuous support in commissioning of Journey Time Indicators (JTIs) to facilitate the opening of Tseung Kwan O – Lam Tin Tunnel (TKO-LTT) within very tight schedule.", + "clientReplyDate", "03 Jul 2023", "staffReplyDate", "04 Jul 2023", "numOfStaff", 7, "clientDeptOrg", + "Transport Department")); + + dataModel.put("aprRecords", aprRecords); + + /* + * dataModel.put("reportTitle", "New Title"); + * + * List headers = Arrays.asList("Header 0", "Header 1", "Header 2", + * "Header 3"); + * dataModel.put("headers", headers); + * + * List rows1 = Arrays.asList("R1-1", "R1-2", "R1-3", "R1-4"); + * dataModel.put("rows1", rows1); + * + * List rows2 = Arrays.asList("R2-1", "R2-2", "R2-3", "R2-4"); + * dataModel.put("rows2", rows2); + * + * Map chartData = new HashMap<>(); + * + * List categories = Arrays.asList("Category 1", "Category 2", + * "Category 3", "Category 4", + * "Category 5"); + * chartData.put("categories", categories); + * + * LinkedHashMap series = new LinkedHashMap<>(); + * + * List series1 = Arrays.asList(4.33, 2.55, 3.55, 4.55, 14.0); + * Map serie1 = new HashMap<>(); + * serie1.put("col", "B"); + * serie1.put("data", series1); + * serie1.put("size", 5); + * series.put("Series 1", serie1); + * + * List series2 = Arrays.asList(44.33, 22.55, 33.55, 44.55, 13.0); + * Map serie2 = new HashMap<>(); + * serie2.put("col", "C"); + * serie2.put("data", series2); + * serie2.put("size", 5); + * series.put("Series 2", serie2); + * + * List series3 = Arrays.asList(4.333, 2.555, 3.555, 4.555, 12.0); + * Map serie3 = new HashMap<>(); + * serie3.put("col", "D"); + * serie3.put("data", series3); + * serie3.put("size", 5); + * series.put("Series 3", serie3); + * + * chartData.put("series", series); + * + * dataModel.put("chartData", chartData); + */ + + // Render the template with the data model + StringWriter writer = new StringWriter(); + template.process(dataModel, writer); + String output = writer.toString(); + + byte[] byteArray = output.getBytes(); + + return byteArray; + + } catch (Exception e) { + e.printStackTrace(); + return null; // or handle the exception according to your requirements + } finally { + templateInputStream.close(); + } + } + + public byte[] generateAppreciationCasesXMLFile(AppreciationCaseReportDao reportDao) throws IOException { + try { + // Load the FTL template + Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); + cfg.setClassForTemplateLoading(WordReportService.class, "/"); + Template template = cfg.getTemplate(XML_TEST_TEMPLATE_PATH); + + // Create a data model + Map dataModel = new HashMap<>(); + dataModel.put("reportDate", reportDao.getFormattedDateFrom()); + dataModel.put("monthTotal", String.format("%s (%d)", NumberUtils.convertToWord(reportDao.getCount().intValue()), reportDao.getCount().intValue())); + + dataModel.put("catBreakDowns", reportDao.getCatBreakDowns()); + + dataModel.put("compareStr1", reportDao.getCompareStr1()); + dataModel.put("compareStr2", reportDao.getCompareStr2()); + + dataModel.put("finYear", reportDao.getFinYear()); + dataModel.put("yearTotal", reportDao.getYearlyCount()); + + LinkedHashMap sbuYearTotal = new LinkedHashMap<>(); + for (Map record : reportDao.getYearlyRecordCount()) { + sbuYearTotal.put( + (String) record.get("name"), + (Long) record.get("count") + ); + } + + dataModel.put("sbuYearTotal", sbuYearTotal); + + int idx = 0; + + List> series = new ArrayList<>(); + + for (AppreciationChartDataDao record : reportDao.getChartList()) { + Map serie = new HashMap<>(); + serie.put("serieName", record.getSbuName()); + serie.put("color", chartColors[idx % (chartColors.length - 1)]); + serie.put("data",record.getRecordByMonth()); + + series.add(serie); + + idx++; + } + + dataModel.put("series", series); + + dataModel.put("lnReceiptDateRange", reportDao.getLnReceiptDateRange()); + + List> aprRecords = new ArrayList<>(); + + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMM YYYY"); + + for (Map record : reportDao.getAppreciationRecordDetail()) { + aprRecords.add(Map.of( + "sbu", record.get("sbuNames") == null ? "" : record.get("sbuNames"), + "receiptDate", record.get("receiptDate") == null ? "" : ((LocalDateTime)record.get("receiptDate")).format(dateFormat), + "description", record.get("description") == null ? "" : record.get("description"), + "clientReplyDate", record.get("clientReplyDate") == null ? "" : ((LocalDateTime)record.get("clientReplyDate")).format(dateFormat), + "staffReplyDate", record.get("staffReplyDate") == null ? "" : ((LocalDateTime)record.get("staffReplyDate")).format(dateFormat), + "numOfStaff", record.get("noOfStaff") == null ? "" : record.get("noOfStaff").toString(), + "clientDeptOrg", !StringUtils.isBlank((String)record.get("clientDepartment")) ? record.get("clientDepartment") : record.get("clientOrganization") + )); + + } + + dataModel.put("aprRecords", aprRecords); + + // Render the template with the data model + StringWriter writer = new StringWriter(); + template.process(dataModel, writer); + String output = writer.toString(); + + byte[] byteArray = output.getBytes(); + + return byteArray; + + } catch (Exception e) { + e.printStackTrace(); + return null; // or handle the exception according to your requirements + } + } + + public byte[] generateAppreciationSummaryXMLFile(String reportHeading, List> reportRecord) throws IOException { + ClassPathResource resource = new ClassPathResource(APPRECIATION_SUMMARY_TEMPLATE_PATH); + InputStream templateInputStream = resource.getInputStream(); + + try { + // Load the FTL template + Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); + cfg.setClassForTemplateLoading(WordReportService.class, "/"); + Template template = cfg.getTemplate(APPRECIATION_SUMMARY_TEMPLATE_PATH); + + // Create a data model + Map dataModel = new HashMap<>(); + dataModel.put("reportDate", reportHeading); + + List> aprRecords = new ArrayList<>(); + + Integer count = 1; + for (Map record : reportRecord) { + aprRecords.add(Map.of( + "index", "(" + RomanConverter.convertToRoman(count, false) + ")", + "description", record.get("description"), + "category", record.get("category") + )); + count++; + } + + dataModel.put("aprRecords", aprRecords); + + // Render the template with the data model + StringWriter writer = new StringWriter(); + template.process(dataModel, writer); + String output = writer.toString(); + + byte[] byteArray = output.getBytes(); + + return byteArray; + + } catch (Exception e) { + e.printStackTrace(); + return null; // or handle the exception according to your requirements + } finally { + templateInputStream.close(); + } + } + + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/entity/Client.java b/src/main/java/com/ffii/lioner/modules/lioner/client/entity/Client.java new file mode 100644 index 0000000..c1af636 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/entity/Client.java @@ -0,0 +1,123 @@ +package com.ffii.lioner.modules.lioner.client.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "client") +@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) +public class Client extends BaseEntity{ + + @NotBlank + @Column + private String fullname; + + @Column + private String lastname; + + @Column + private String firstname; + + @Column + private String title; + + @Column + private String email; + + @Column + private String phone1; + + @Column + private String phone2; + + @Column + private String remarks; + + @Column + private LocalDateTime joinDate; + + public String getFullname() { + return fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone1() { + return phone1; + } + + public void setPhone1(String phone1) { + this.phone1 = phone1; + } + + public String getPhone2() { + return phone2; + } + + public void setPhone2(String phone2) { + this.phone2 = phone2; + } + + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public LocalDateTime getJoinDate() { + return joinDate; + } + + public void setJoinDate(LocalDateTime joinDate) { + this.joinDate = joinDate; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/entity/ClientRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/client/entity/ClientRepository.java new file mode 100644 index 0000000..2e5dc8c --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/entity/ClientRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.client.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface ClientRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/req/UpdateClientReq.java b/src/main/java/com/ffii/lioner/modules/lioner/client/req/UpdateClientReq.java new file mode 100644 index 0000000..76c96e4 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/req/UpdateClientReq.java @@ -0,0 +1,108 @@ +package com.ffii.lioner.modules.lioner.client.req; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +import java.time.LocalDate; + +public class UpdateClientReq { + + private Long id; + + @Size(max = 500) + String fullname; + + @Size(max = 255) + String lastname; + + @Size(max = 255) + String firstname; + + @Size(max = 50) + String title; + + @Size(max = 500) + String email; + + @Size(max = 50) + String phone1; + + @Size(max = 50) + String phone2; + + String remarks; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFullname() { + return fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone1() { + return phone1; + } + + public void setPhone1(String phone1) { + this.phone1 = phone1; + } + + public String getPhone2() { + return phone2; + } + + public void setPhone2(String phone2) { + this.phone2 = phone2; + } + + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java b/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java new file mode 100644 index 0000000..a14ae9c --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/service/ClientService.java @@ -0,0 +1,1037 @@ +package com.ffii.lioner.modules.lioner.client.service; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.client.entity.Client; +import com.ffii.lioner.modules.lioner.client.entity.ClientRepository; +import com.ffii.lioner.modules.lioner.entity.ImpEvent; +import com.ffii.lioner.modules.lioner.client.req.UpdateClientReq; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.SubDivision; +import com.ffii.core.exception.UnprocessableEntityException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.core.utils.JsonUtils; + +import jakarta.persistence.Table; + +@Service +public class ClientService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + public ClientService(JdbcDao jdbcDao, ClientRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " e.id, " + + " e.created, " + + " e.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " e.version, " + + " e.modified, " + + " e.modifiedBy AS modifiedById, " + + " e.deleted, " + + " u2.name AS modifiedBy, " + + " e.name, " + + " e.nameCht, " + + " e.description, " + + " e.region, " + + " e.organization, " + + " e.eventType, " + + " e.frequency, " + + " e.series, " + + " e.startDate, " + + " e.eventFrom, " + + " e.eventTo, " + + " e.applicationDeadline, " + + " e.nextApplicationDate, " + + " e.announcementDate, " + + " e.awardDate, " + + " e.reminderFlag, " + + " e.reminderThreshold, " + + " e.reminderInterval, " + + " e.reminderLimit " + + " FROM event e " + + " LEFT JOIN `user` u1 ON u1.id = e.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = e.modifiedBy " + + " WHERE e.id = :id " + ); + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + // public Client saveImportRecord(ImpEvent req) { + // Client instance; + // if (req.getId() > 0) { + // instance = find(req.getId()).get(); + // } else { + // instance = new Client(); + // } + // // =====GET OLD AUDIT LOG=====// + // String tableName = instance.getClass().getAnnotation(Table.class).name(); + // Map oldValueObject = new HashMap(); + // Map newValueObject = new HashMap(); + + // if (instance != null && instance.getId() != null && instance.getId() > 0) { + // Map input = Map.of("id", instance.getId()); + // Map logData = getAuditLogObject(input); + // logData.put("subDivision", this.getEventDivisionName(input)); + // oldValueObject = logData; + // } + // // =====GET OLD AUDIT LOG=====// + + // BeanUtils.copyProperties(req, instance); + // logger.info(instance.toString()); + // instance = save(instance); + // Long eventId = instance.getId(); + // updateSubDivision(req.getTempIdList(),new ArrayList<>(),eventId); + + // // =====GET NEW AUDIT LOG=====// + // if (instance != null && instance.getId() != null && instance.getId() > 0) { + // Map input = Map.of("id", instance.getId()); + // Map logData = getAuditLogObject(input); + // logData.put("subDivision", this.getEventDivisionName(input)); + // newValueObject = logData; + // } + + // if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + // auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + // auditLogService.save( + // tableName, + // instance.getId(), + // instance.getFullname(), + // SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + // new Date(), + // JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + // JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + // } + // // =====GET NEW AUDIT LOG=====// + + // return instance; + // } + + public Map saveOrUpdate(UpdateClientReq req) { + Client instance; + // List onUsingIdList = new ArrayList(); + if(req.getId()>0){ + instance = find(req.getId()).get(); + // onUsingIdList = this.getSelectedSubDivisionList(req.getId()); + } + else{ + instance = new Client(); + } + + // List subDivisionNameList = new ArrayList(); + // for (SubDivision subDivision : onUsingIdList) { + // for (Long deleteId: req.getSubDivisionRemoveIds()) { + // if(deleteId == subDivision.getId()){ + // subDivisionNameList.add(subDivision.getName()); + // } + // } + // } + + // if(subDivisionNameList.size() > 0){ + // String temp = ""; + // for (String subDivisionName : subDivisionNameList) { + // temp = temp + subDivisionName + "\n"; + // } + // throw new UnprocessableEntityException( + // "Below Sub-Division already exist in application, cannot be deleted: \n" + // + temp + // ); + // } + + // BeanUtils.copyProperties(req,instance); + // if(!instance.getReminderFlag()){ + // instance.setReminderThreshold((long) 0); + // instance.setReminderInterval((long) 0); + // instance.setReminderLimit((long) 0); + // } + + //=====GET OLD AUDIT LOG=====// + // String tableName = instance.getClass().getAnnotation(Table.class).name(); + // Map oldValueObject = new HashMap(); + // Map newValueObject = new HashMap(); + + // if (instance != null && instance.getId() != null && instance.getId() > 0) { + // Map input = Map.of("id", instance.getId()); + // Map logData= getAuditLogObject(input); + // logData.put("subDivision", this.getEventDivisionName(input)); + // oldValueObject = logData; + // } + // //=====GET OLD AUDIT LOG=====// + + // instance = save(instance); + // Long eventId = instance.getId(); + // updateSubDivision(req.getSubDivisionIds(),req.getSubDivisionRemoveIds(),eventId); + + //=====GET NEW AUDIT LOG=====// + // if (instance != null && instance.getId() != null && instance.getId() > 0) { + // Map input = Map.of("id", instance.getId()); + // Map logData= getAuditLogObject(input); + // logData.put("subDivision", this.getEventDivisionName(input)); + // newValueObject = logData; + // } + + + // if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + // auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + // ){ + // auditLogService.save( + // tableName, + // instance.getId(), + // instance.getName(), + // SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + // new Date(), + // JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + // JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + // } + //=====GET NEW AUDIT LOG=====// + + + return Map.of( + "id", instance.getId() + ); + } + + // public void markDeleteWithAuditLog(Client instance){ + // //=====GET OLD AUDIT LOG=====// + // String tableName = instance.getClass().getAnnotation(Table.class).name(); + // StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + // Map oldValueObject = new HashMap(); + // Map newValueObject = new HashMap(); + + // if (instance != null && instance.getId() != null && instance.getId() > 0) { + // Map input = Map.of("id", instance.getId()); + // Map logData= getAuditLogObject(input); + // logData.put("subDivision", this.getEventDivisionName(input)); + // oldValueObject = logData; + // } + // //=====GET OLD AUDIT LOG=====// + // this.markDelete(instance.getId()); + // this.deleteEventDivision(instance.getId().intValue()); + // //=====GET NEW AUDIT LOG=====// + // if (instance != null && instance.getId() != null && instance.getId() > 0) { + // Map input = Map.of("id", instance.getId()); + // Map logData= getAuditLogObject(input); + // logData.put("subDivision", this.getEventDivisionName(input)); + // newValueObject = logData; + // } + + + // if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + // auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + // ){ + // auditLogService.save( + // tableName, + // instance.getId(), + // instance.getName(), + // SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + // new Date(), + // JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + // JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + // } + // //=====GET NEW AUDIT LOG=====// + // } + + // public void deleteEventDivision(Integer eventId) { + // List subDivisionIdList = this.getEventDivision(Map.of("id", eventId)); + // List> deleteDivisionIds = subDivisionIdList.stream() + // .map(subDivisionId -> Map.of("eventId", eventId, "subDivisionId", subDivisionId)) + // .collect(Collectors.toList()); + + // if (!deleteDivisionIds.isEmpty()) { + // jdbcDao.batchUpdate( + // "UPDATE event_sub_division esd" + // + " SET esd.deleted = TRUE" + // + " WHERE eventId = :eventId AND subDivisionId = :subDivisionId", + // deleteDivisionIds); + // } + // } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " id " + + " , fullname " + + " , lastname " + + " , firstname " + + " , title " + + " , email " + + " , phone1 " + + " , phone2 " + + " , remarks " + + " , joinDate " + + " , caseManagerId " + + " , consultantId " + + " FROM client " + + " WHERE deleted = FALSE " + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT e.id, " + // + " e.name AS name, " + // + " e.applicationDeadline, " + // + " e.startDate, " + // + " e.eventFrom " + // + " FROM event e " + // + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + // + " LEFT JOIN sub_division sd ON esd.subDivisionId = sd.id " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " LEFT JOIN `user` u ON u.id = e.createdBy " + // + " WHERE e.deleted = FALSE " + // + " AND esd.deleted = FALSE " + + ); + + if (args != null) { + if (args.containsKey("fullname")) + sql.append(" AND fullname LIKE :fullname "); + if (args.containsKey("lastname")) + sql.append(" AND lastname LIKE :lastname "); + if (args.containsKey("firstname")) + sql.append(" AND firstname LIKE :firstname "); + if (args.containsKey("phone1")) + sql.append(" AND phone1 LIKE :phone1 "); + if (args.containsKey("phone2")) + sql.append(" AND phone2 LIKE :phone2 "); + if (args.containsKey("email")) + sql.append(" AND email LIKE :email "); + if (args.containsKey("remarks")) + sql.append(" AND remarks LIKE :remarks "); + if (args.containsKey("joinDateFrom")) + sql.append(" AND joinDate >= :joinDateFrom "); + if (args.containsKey("joinDateTo")) + sql.append(" AND joinDate <= :joinDateTo "); + // if (args.containsKey("type")) + // sql.append(" AND eventType LIKE :type "); + // if (args.containsKey("divisionIdList")) + // sql.append(" AND sd.divisionId IN (:divisionIdList) "); + // if (args.containsKey("subDivisionIdList")) + // sql.append(" AND sd.id IN (:subDivisionIdList) "); + // if (args.containsKey("keyword")){ + // sql.append(" AND ( createdBy LIKE :keyword "); + // sql.append(" OR u.username LIKE :keyword "); + // sql.append(" OR u.fullname LIKE :keyword "); + // sql.append(" OR u.email LIKE :keyword "); + // sql.append(" OR sd.name LIKE :keyword "); + // sql.append(" OR d.name LIKE :keyword "); + // sql.append(" OR e.name LIKE :keyword) "); + } + + // } + // sql.append(" ORDER BY id desc "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + // public List> listReport(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT e.*, " + // + " (SELECT " + // + " GROUP_CONCAT( DISTINCT d2.name) " + // + " FROM event e2 " + // + " LEFT JOIN event_sub_division esd2 ON e2.id = esd2.eventId " + // + " LEFT JOIN sub_division sd2 ON esd2.subDivisionId = sd2.id " + // + " LEFT JOIN division d2 ON d2.id = sd2.divisionId " + // + " WHERE e2.id = e.id " + // + " AND esd2.deleted = false " + // + " GROUP BY e2.id " + // + " ) AS divisionList, " + // + " u.name as username " + // + " FROM event e " + // + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + // + " LEFT JOIN sub_division sd ON esd.subDivisionId = sd.id " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " LEFT JOIN `user` u ON u.id = e.createdBy " + // + " WHERE e.deleted = FALSE " + // ); + + // if (args != null) { + // if (args.containsKey("eventName")) + // sql.append(" AND e.name LIKE :eventName "); + // if (args.containsKey("description")) + // sql.append(" AND e.description LIKE :description "); + // if (args.containsKey("organization")) + // sql.append(" AND e.organization LIKE :organization "); + // if (args.containsKey("fromDate")) + // sql.append(" AND DATE(e.startDate) >= :fromDate"); + // if (args.containsKey("toDate")) + // sql.append(" AND DATE(e.startDate) < :toDate"); + // if (args.containsKey("region")) + // sql.append(" AND e.region LIKE :region "); + // if (args.containsKey("type")) + // sql.append(" AND e.eventType LIKE :type "); + // if (args.containsKey("divisionIdList")) + // sql.append(" AND sd.divisionId IN (:divisionIdList) "); + // if (args.containsKey("subDivisionIdList")) + // sql.append(" AND sd.id IN (:subDivisionIdList) "); + // if (args.containsKey("keyword")){ + // sql.append(" AND ( e.createdBy LIKE :keyword "); + // sql.append(" OR u.username LIKE :keyword "); + // sql.append(" OR u.fullname LIKE :keyword "); + // sql.append(" OR u.email LIKE :keyword "); + // sql.append(" OR sd.name LIKE :keyword "); + // sql.append(" OR d.name LIKE :keyword "); + // sql.append(" OR e.name LIKE :keyword) "); + // } + + // } + // sql.append(" ORDER BY e.id"); + + // return jdbcDao.queryForList(sql.toString(), args); + // } + + // public List> listByEvent(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT a.id, " + // + " a.name AS applicationName, " + // + " a.responsibleOfficer, " + // + " (SELECT " + // + " GROUP_CONCAT(t.name SEPARATOR ', ') " + // + " FROM application_tag at2 " + // + " LEFT JOIN tag t ON at2.tagId = t.id " + // + " WHERE at2.deleted = FALSE " + // + " AND at2.applicationId = a.id) AS tags, " + // + " (SELECT " + // + " GROUP_CONCAT(sd.name SEPARATOR ', ') " + // + " FROM application_sub_division asd2 " + // + " LEFT JOIN sub_division sd ON sd.id = asd2.subDivisionId " + // + " WHERE asd2.deleted = FALSE " + // + " AND asd2.applicationId = a.id) AS subDivisions " + // + " FROM application a " + // + " JOIN application_sub_division asd ON asd.applicationId = a.id " + // + " LEFT JOIN sub_division sd3 ON asd.subDivisionId = sd3.id" + // + " LEFT JOIN division d ON d.id = sd3.divisionId " + // + " WHERE a.deleted = FALSE " + // + " AND a.eventId = :eventId " + // ); + // if (args.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + // sql.append(" ORDER BY a.id"); + + // return jdbcDao.queryForList(sql.toString(), args); + // } + + // public List getEventByWithinYear(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT e.* " + // + " FROM event e " + // + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + // + " WHERE e.deleted = FALSE " + // + " AND e.reminderFlag = TRUE " + // + " AND e.startDate >= :yearRange " + // ); + + // if (args != null) { + // if (args.containsKey("announcementDate")) { + // sql.append(" AND e.announcementDate = :announcementDate"); + // } + // } + + // sql.append(" ORDER BY e.id"); + + // return jdbcDao.queryForList(sql.toString(), args, Client.class); + // } + + // public List getEventDivision(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " esd.subDivisionId " + // + " FROM event_sub_division esd " + // + " LEFT JOIN sub_division sd ON sd.id = esd.subDivisionId " + // + " WHERE esd.deleted = FALSE " + // + " AND sd.deleted = FALSE " + // + " AND esd.eventId = :id " + // ); + + // sql.append(" ORDER BY esd.id"); + + // return jdbcDao.queryForInts(sql.toString(), args); + // } + + // public List getEventDivisionName(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " sd.name " + // + " FROM event_sub_division esd " + // + " JOIN sub_division sd ON esd.subDivisionId = sd.id " + // + " WHERE esd.deleted = FALSE " + // + " AND esd.eventId = :id " + // ); + + // sql.append(" ORDER BY esd.id"); + + // return jdbcDao.queryForStrings(sql.toString(), args); + // } + + // public List> getEventDivisionAllData(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " esd.* " + // + " FROM event_sub_division esd " + // + " WHERE esd.deleted = FALSE " + // + " AND esd.eventId = :id " + // ); + + // sql.append(" ORDER BY esd.id"); + + // return jdbcDao.queryForList(sql.toString(), args); + // } + + // public List> listCombo(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT " + // + " e.id, " + // + " e.id as `key`, " + // + " e.name as label " + // + " FROM event e " + // + " LEFT JOIN event_sub_division esd ON esd.eventId = e.id " + // + " WHERE e.deleted = FALSE " + // ); + // if (args != null) { + // if (args.containsKey("userSubDivisionId")) + // sql.append(" AND esd.subDivisionId = :userSubDivisionId "); + + // } + + // sql.append(" ORDER BY e.name asc"); + + // return jdbcDao.queryForList(sql.toString(), args); + // } + + // private void updateSubDivision(List newIds, List removeIds, Long eventId){ + // List> newDivisionIds = newIds.stream() + // .map(subDivisionId -> Map.of("eventId", eventId, "subDivisionId", subDivisionId)) + // .collect(Collectors.toList()); + // List> deleteDivisionIds = removeIds.stream() + // .map(subDivisionId -> Map.of("eventId", eventId, "subDivisionId", subDivisionId)) + // .collect(Collectors.toList()); + // if (!newDivisionIds.isEmpty()) { + // jdbcDao.batchUpdate( + // "INSERT IGNORE INTO event_sub_division (eventId, subDivisionId, newReminderCount, suppressedNewReminder, oldReminderCount, suppressedOldReminder )" + // + " VALUES (:eventId, :subDivisionId, 0, 0, 0, 0)", + // newDivisionIds); + // } + // if (!deleteDivisionIds.isEmpty()) { + // jdbcDao.batchUpdate( + // "UPDATE event_sub_division esd" + // + " SET esd.deleted = TRUE" + // + " WHERE eventId = :eventId AND subDivisionId = :subDivisionId", + // deleteDivisionIds); + // } + + // } + + // public boolean isNameTaken(String name, Long id) { + // StringBuilder sql = new StringBuilder("SELECT" + // + " Count(e.id) " + // + " FROM event e " + // + " WHERE e.deleted =FALSE " + // + " AND e.name = :name " + // + " AND e.id != :id " + // ); + + // return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + // } + + // public Boolean hasApplicationRecord(Map args){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " COUNT(*) " + // + " FROM event e " + // + " JOIN application a ON e.id = a.eventId " + // + " WHERE e.deleted = FALSE " + // + " AND e.id = :id " + // ); + + // sql.append(" ORDER BY e.id"); + + // return jdbcDao.queryForBoolean(sql.toString(), args); + // } + + // public Map isReminderOvertime(Long id){ + // Client event = find(id).get(); + + // if(event.getReminderFlag() == false){ + // return Map.of("warning",false); + // } + // else{ + // LocalDate firstIssueDate = null; + // Boolean hasApplicationRecord = hasApplicationRecord(Map.of("id",event.getId())); + + // if(!hasApplicationRecord){ + // firstIssueDate = event.getStartDate().minusDays(event.getReminderThreshold()); + // } + // else{ + // if(event.getNextApplicationDate() != null){ + // firstIssueDate = event.getNextApplicationDate().minusDays(event.getReminderThreshold()); + // } + // else{ + // return Map.of( + // "warning", false + // ); + // } + // } + // Integer limit = event.getReminderLimit().intValue(); + // Integer interval = event.getReminderInterval().intValue(); + // List reminderDateSchedule = new ArrayList(); + // reminderDateSchedule.add(firstIssueDate); + // for(Integer i=1; i< limit+1 ; i++){ + // reminderDateSchedule.add(firstIssueDate.plusDays(i*interval)); + // } + // logger.info(reminderDateSchedule); + // LocalDate lastLocalDate = null; + // for (LocalDate localDate : reminderDateSchedule) { + // if(localDate.compareTo(event.getApplicationDeadline()) > 0){ + // //reminder overflow + // return Map.of( + // "warning", true, + // "lastNotiDate", lastLocalDate + // ); + // } + // else{ + // lastLocalDate = localDate; + // } + // } + // return Map.of( + // "warning", false + // ); + // } + // } + + // public List> getTop6(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " ROW_NUMBER() OVER (ORDER BY SUM(IF(temp.awardId IS NOT NULL, 1, 0)) DESC) as `rank`, " + // + " temp.eventId, " + // + " temp.eventName, " + // + " SUM(IF(temp.awardId IS NOT NULL, 1, 0)) as awardCount " + // + " FROM ( " + // + " (SELECT " + // + " e.id as eventId, " + // + " e.name as eventName, " + // + " a2.id as awardId " + // + " FROM event e " + // + " LEFT JOIN application a ON a.eventId = e.id " + // + " LEFT JOIN award a2 ON a2.applicationId = a.id " + // + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId AND asd.deleted = false " + // + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " WHERE e.deleted = FALSE " + // + " AND a.deleted = FALSE " + // + " AND a2.deleted = FALSE " + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + // sql.append( + // " AND YEAR(e.startDate) = :year " + // + " GROUP BY e.id, e.name, a2.id) " + // + " UNION " + // + " (SELECT " + // + " e.id as eventId, " + // + " e.name as eventName, " + // + " null " + // + " FROM event e " + // + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId AND esd.deleted = false " + // + " LEFT JOIN sub_division sd ON sd.id = esd.subDivisionId " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " WHERE e.deleted = FALSE " + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + // sql.append( + // " AND YEAR(e.startDate) = :year " + // + " GROUP BY e.id, e.name) " + // + " ) temp " + // + " GROUP BY temp.eventId, temp.eventName " + // + " ORDER BY SUM(IF(temp.awardId IS NOT NULL, 1, 0)) DESC " + // + " LIMIT 6; " + // ); + + // return jdbcDao.queryForList(sql.toString(), req); + // } + + // public List> getDashboardCombo(){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT year(e.startDate) AS id, " + // + " year(e.startDate) AS label " + // + " FROM event e " + // + " WHERE e.deleted = FALSE " + // + " ORDER BY id DESC " + // ); + + // return jdbcDao.queryForList(sql.toString()); + // } + + // public Map getYearEventCount(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " COUNT(temp.eventId) AS count " + // + " FROM( " + // + " SELECT " + // + " e.id AS eventId " + // + " FROM event e " + // + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + // + " LEFT JOIN sub_division sd ON sd.id =esd.subDivisionId " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " WHERE e.deleted = FALSE " + // + " AND esd.deleted = false " + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + // sql.append(" AND YEAR(e.startDate) = :year"); + // sql.append(" GROUP BY e.id "); + // sql.append(" ) temp; "); + + // return jdbcDao.queryForMap(sql.toString(),req).get(); + // } + + // public Map getYearAwardCount(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " COUNT(temp.awardId) AS count " + // + " FROM( " + // + " SELECT " + // + " a2.id AS awardId " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId " + // + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId AND asd.deleted = false " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " WHERE e.deleted = FALSE " + // + " AND a.deleted = FALSE " + // + " AND a2.deleted = FALSE " + + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + // sql.append(" AND YEAR(e.awardDate) = :year"); + // sql.append(" GROUP BY a2.id, a2.id "); + // sql.append(" ) temp; "); + + // return jdbcDao.queryForMap(sql.toString(),req).get(); + // } + + // public List> getDummyDivisionAwardSummary(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " ROW_NUMBER() OVER (ORDER BY awardCount DESC) AS `rank`, " + // + " name, " + // + " awardCount " + // + " FROM ( " + // + " SELECT " + // + " queryResult.name, " + // + " COUNT(queryResult.awardName) AS awardCount " + // + " FROM ( " + // + " SELECT " + // + " DISTINCT " + // + " d.id, " + // + " d.name, " + // + " e.name AS eventName," + // + " a.name AS application," + // + " a2.name AS awardName " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " JOIN category c ON a2.categoryId = c.id " + // + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + // + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + // + " LEFT JOIN division d ON sd.divisionId = d.id " + // + " WHERE d.deleted = FALSE " + // + " AND YEAR(e.startDate) = :year " + // + " AND asd.deleted = false " + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND sd.id = :userSubDivisionId "); + // if (req.containsKey("branch")) sql.append(" AND d.branch LIKE :branch "); + + // sql.append( + // " AND sd.deleted = FALSE " + // + " ) AS queryResult " + // + " GROUP BY queryResult.id " + // + " UNION ALL " + // + " SELECT " + // + " dummy.name, " + // + " 0 AS awardCount " + // + " FROM ( " + // + " SELECT 1 AS id, 'z' AS name " + // + " UNION ALL SELECT 2, 'z' " + // + " ) AS dummy " + // + " ) AS rankedData " + // + " ORDER BY name ASC " + // + " LIMIT 10; " + // ); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + + // public List> getDivisionAwardSummaryUntil10(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " ROW_NUMBER() OVER (ORDER BY awardCount DESC) AS `rank`, " + // + " name, " + // + " awardCount " + // + " FROM ( " + // + " SELECT " + // + " queryResult.name, " + // + " COUNT(queryResult.awardName) AS awardCount " + // + " FROM ( " + // + " SELECT " + // + " DISTINCT " + // + " d.id, " + // + " d.name, " + // + " e.name AS eventName," + // + " a.name AS application," + // + " a2.name AS awardName " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " JOIN category c ON a2.categoryId = c.id " + // + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + // + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + // + " LEFT JOIN division d ON sd.divisionId = d.id " + // + " WHERE d.deleted = FALSE " + // + " AND YEAR(e.startDate) = :year " + // + " AND sd.deleted = FALSE " + // + " AND d.branch LIKE :branch " + // + " AND asd.deleted = false " + // + " ) AS queryResult " + // + " GROUP BY queryResult.id " + // + " UNION ALL " + // + " SELECT " + // + " dummy.name, " + // + " 0 AS awardCount " + // + " FROM ( " + // + " SELECT 1 AS id, 'z' AS name " + // + " UNION ALL SELECT 2, 'z' " + // + " UNION ALL SELECT 3, 'z' " + // + " UNION ALL SELECT 4, 'z' " + // + " UNION ALL SELECT 5, 'z' " + // + " UNION ALL SELECT 6, 'z' " + // + " UNION ALL SELECT 7, 'z' " + // + " UNION ALL SELECT 8, 'z' " + // + " UNION ALL SELECT 9, 'z' " + // + " UNION ALL SELECT 10, 'z' " + // + " ) AS dummy " + // + " ) AS rankedData " + // + " ORDER BY name ASC " + // + " LIMIT 10; " + // ); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + // public List> getDivisionAwardSummary(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " ROW_NUMBER() OVER (ORDER BY awardCount DESC) AS `rank`, " + // + " name, " + // + " awardCount " + // + " FROM ( " + // + " SELECT " + // + " queryResult.name, " + // + " COUNT(queryResult.awardName) AS awardCount " + // + " FROM ( " + // + " SELECT " + // + " DISTINCT " + // + " d.id, " + // + " d.name, " + // + " e.name AS eventName, " + // + " a.name AS application, " + // + " a2.name AS awardName " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " JOIN category c ON a2.categoryId = c.id " + // + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + // + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + // + " LEFT JOIN division d ON sd.divisionId = d.id " + // + " WHERE d.deleted = FALSE " + // + " AND YEAR(e.startDate) = :year " + // + " AND sd.deleted = FALSE " + // + " AND d.branch LIKE :branch " + // + " AND asd.deleted = false " + // + " ) AS queryResult " + // + " GROUP BY queryResult.id " + // + " ) AS rankedData " + // + " ORDER BY name ASC " + // ); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + // public List> getTagAwardSummary(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " temp.id, " + // + " temp.name, " + // + " COUNT(temp.awardId) AS awardCount " + // + " FROM( " + // + " SELECT " + // + " t.id, " + // + " t.name, " + // + " a2.id AS awardId " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN application_tag at2 ON at2.applicationId = a.id " + // + " JOIN tag t ON t.id = at2.tagId " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId AND asd.deleted = FALSE " + // + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " WHERE e.deleted = FALSE " + // + " AND a.deleted = FALSE " + // + " AND a2.deleted = FALSE " + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + // sql.append(" AND YEAR(e.startDate) = :year "); + // sql.append(" GROUP BY t.id, a2.id "); + // sql.append(" ) temp "); + // sql.append(" GROUP BY temp.id "); + // sql.append(" ORDER BY COUNT(temp.id) DESC"); + // sql.append(" LIMIT 6 ;"); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + // public List> getCategoryAwardSummary(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " temp.id, " + // + " temp.name, " + // + " COUNT(temp.awardId) AS awardCount " + // + " FROM( " + // + " SELECT " + // + " c.id, " + // + " c.name, " + // + " a2.id AS awardId " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId " + // + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + // + " LEFT JOIN division d ON d.id = sd.divisionId " + // + " LEFT JOIN category c ON a2.categoryId = c.id " + // + " WHERE e.deleted = FALSE " + // + " AND a.deleted = FALSE " + // + " AND a2.deleted = FALSE " + // + " AND asd.deleted = false " + + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + // sql.append(" AND YEAR(e.startDate) = :year "); + // sql.append(" GROUP BY c.id, a2.id "); + // sql.append(" ) temp "); + // sql.append(" GROUP BY temp.id "); + // sql.append(" ORDER BY COUNT(temp.id) DESC"); + // sql.append(" LIMIT 6 ;"); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + // public List> getAwardSummaryReportPage1(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " name, " + // + " count " + // + " FROM ( " + // + " SELECT " + // + " queryResult.name, " + // + " COUNT(queryResult.awardName) AS count " + // + " FROM ( " + // + " SELECT " + // + " DISTINCT " + // + " d.id, " + // + " d.name, " + // + " e.name AS eventName," + // + " a.name AS application," + // + " a2.name AS awardName " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " JOIN category c ON a2.categoryId = c.id " + // + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + // + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + // + " LEFT JOIN division d ON sd.divisionId = d.id " + // + " WHERE d.deleted = FALSE " + // + " AND YEAR(e.startDate) = :year " + // + " AND asd.deleted = false " + // ); + + // if (req.containsKey("userSubDivisionId")) sql.append(" AND sd.id = :userSubDivisionId "); + // if (req.containsKey("branch")) sql.append(" AND d.branch LIKE :branch "); + + // sql.append( + // " AND sd.deleted = FALSE " + // + " ) AS queryResult " + // + " GROUP BY queryResult.id " + // + " ) AS rankedData " + // + " ORDER BY count DESC " + // ); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + // public List> getAwardSummaryReportPage2(Map req){ + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT " + // + " d.name, " + // + " (SELECT GROUP_CONCAT( DISTINCT sd.name SEPARATOR ', ') " + // + " FROM award_sub_division asd2 " + // + " LEFT JOIN sub_division sd ON sd.id = asd2.subDivisionId " + // + " LEFT JOIN division d2 ON sd.divisionId = d2.id " + // + " WHERE asd2.deleted = FALSE " + // + " AND d2.id = d.id " + // + " ) AS subDivision, " + // + " e.name AS eventName, " + // + " a.name AS applicationName, " + // + " a2.name AS awardName, " + // + " a2.receiveDate, " + // + " c.name AS category , " + // + " a2.responsibleOfficer " + // + " FROM event e " + // + " JOIN application a ON a.eventId = e.id " + // + " JOIN award a2 ON a2.applicationId = a.id " + // + " JOIN category c ON a2.categoryId = c.id " + // + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id AND asd.deleted = FALSE " + // + " LEFT JOIN sub_division sd ON asd.subDivisionId =sd.id " + // + " LEFT JOIN division d ON sd.divisionId = d.id " + // + " WHERE a.deleted = FALSE " + // + " AND a.deleted = FALSE " + // + " AND a2.deleted = FALSE " + // + " AND YEAR(e.startDate) = :year " + // ); + // if (req.containsKey("userSubDivisionId")) sql.append(" AND sd.id = :userSubDivisionId "); + // if (req.containsKey("branch")) sql.append(" AND d.branch LIKE :branch "); + + // return jdbcDao.queryForList(sql.toString(),req); + // } + + // public List getSelectedSubDivisionList(Long eventId) { + // StringBuilder sql = new StringBuilder("SELECT" + // + " DISTINCT " + // + " sd.* " + // + " FROM application a " + // + " JOIN application_sub_division asd ON a.id = asd.applicationId " + // + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + // + " WHERE a.eventId = :eventId " + // ); + + // return jdbcDao.queryForList(sql.toString(), Map.of("eventId", eventId), SubDivision.class); + // } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " e.id " + + " FROM event e " + + " WHERE e.deleted = FALSE" + + " AND e.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java b/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java new file mode 100644 index 0000000..decb5b6 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/client/web/ClientController.java @@ -0,0 +1,252 @@ +package com.ffii.lioner.modules.lioner.client.web; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.lioner.client.req.UpdateClientReq; +import com.ffii.lioner.modules.lioner.client.service.ClientService; + +import aj.org.objectweb.asm.Type; + +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.DataRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/client") +public class ClientController{ + + private final Log logger = LogFactory.getLog(getClass()); + private ClientService clientService; + private ExcelReportService excelReportService; + + public ClientController(ClientService clientService, ExcelReportService excelReportService) { + this.clientService = clientService; + this.excelReportService = excelReportService; + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, clientService.find(id).orElseThrow(NotFoundException::new) + // "eventDivision", clientService.getEventDivision(Map.of("id",id)) + ); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping("/save") + public Map saveOrUpdate(@RequestBody @Valid UpdateClientReq req) { + return Map.of( + Params.DATA,clientService.saveOrUpdate(req) + ); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + clientService.markDelete(clientService.find(id).get()); + // clientService.markDeleteWithAuditLog(clientService.find(id).get()); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(clientService.list( + CriteriaArgsBuilder.withRequest(request) + .addStringLike("fullname") + .addStringLike("lastname") + .addStringLike("firstname") + // .addStringLike("title") + .addStringLike("email") + .addStringLike("phone1") + .addStringLike("phone2") + .addDate("joinDateFrom") + .addDate("joinDateTo") + .addStringLike("remarks") + .build())); + } + + // @GetMapping("/export") + // public ResponseEntity getEventExport(HttpServletRequest request) throws ServletRequestBindingException, IOException { + // List> record = clientService.listReport(CriteriaArgsBuilder.withRequest(request) + // .addStringLike("eventName") + // .addStringLike("description") + // .addStringLike("organization") + // .addDate("fromDate") + // .addDateTo("toDate") + // .addStringLike("region") + // .addStringLike("type") + // .addIntegerList("divisionIdList") + // .addIntegerList("subDivisionIdList") + // .addStringLike("keyword") + // .build()); + + // byte[] reportResult = excelReportService.generateEventExcelReport(record); + // return ResponseEntity.ok() + // .header("filename", "event_export_" + LocalDate.now()) + // .body(new ByteArrayResource(reportResult)); + // } + + // @GetMapping("/application") + // public RecordsRes> listByEvent(HttpServletRequest request) + // throws ServletRequestBindingException { + // return new RecordsRes<>(clientService.listByEvent( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("eventId") + // .addInteger("userSubDivisionId") + // .build() + // )); + // } + + // @GetMapping("/combo") + // public RecordsRes> getEventCombo(HttpServletRequest request) throws ServletRequestBindingException { + // System.out.println(request); + // return new RecordsRes<>(clientService.listCombo( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("userSubDivisionId") + // .build())); + // } + + // @GetMapping("/checkDuplicate") + // public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + // boolean isNameTaken = clientService.isNameTaken(name,id); + // return Map.of( + // "isTaken", isNameTaken + + // ); + // } + + // @GetMapping("/checkOvertime/{id}") + // public Map checkOvertime(@PathVariable Long id) { + // return clientService.isReminderOvertime(id); + // } + + // @GetMapping("/dashboard/combo") + // public RecordsRes> getDashboardCombo() { + // return new RecordsRes<>(clientService.getDashboardCombo()); + // } + + // @GetMapping("/dashboard/topRecord") + // public RecordsRes> getTop6(HttpServletRequest request) throws ServletRequestBindingException { + // return new RecordsRes<>(clientService.getTop6( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("year") + // .addInteger("userSubDivisionId") + // .build() + // )); + // } + + // @GetMapping("/dashboard/yearAward") + // public DataRes> getYearAwardCount(HttpServletRequest request) throws ServletRequestBindingException { + // return new DataRes<>(clientService.getYearAwardCount( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("year") + // .addInteger("userSubDivisionId") + // .build() + // )); + // } + + // @GetMapping("/dashboard/yearEvent") + // public DataRes> getYearEventCount(HttpServletRequest request) throws ServletRequestBindingException { + // return new DataRes<>(clientService.getYearEventCount( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("year") + // .addInteger("userSubDivisionId") + // .build() + // )); + // } + + // @GetMapping("/dashboard/yearDivisionSummary") + // public RecordsRes> getDivisionAwardSummary(@RequestParam Integer year, @RequestParam String branch, @RequestParam Boolean viewDivisionOnly, @RequestParam Integer userSubDivisionId) { + // if(!viewDivisionOnly){ + // List> tempRecords = clientService.getDivisionAwardSummary(Map.of("year",year, "branch", branch)); + + // if(tempRecords.size() == 0){ + // tempRecords = clientService.getDummyDivisionAwardSummary(Map.of("year",year,"branch", branch)); + // } + // return new RecordsRes<>(tempRecords); + + // } + // else{ + // List> tempRecords = clientService.getDummyDivisionAwardSummary(Map.of("year",year,"userSubDivisionId",userSubDivisionId)); + // return new RecordsRes<>(tempRecords); + + // } + + // } + + // @GetMapping("/dashboard/yearTagSummary") + // public RecordsRes> getTagAwardSummary(HttpServletRequest request) throws ServletRequestBindingException { + // return new RecordsRes<>(clientService.getTagAwardSummary( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("year") + // .addInteger("userSubDivisionId") + // .build() + // )); + // } + + // @GetMapping("/dashboard/yearCategorySummary") + // public RecordsRes> getCategoryAwardSummary(HttpServletRequest request) throws ServletRequestBindingException { + // return new RecordsRes<>(clientService.getCategoryAwardSummary( + // CriteriaArgsBuilder.withRequest(request) + // .addInteger("year") + // .addInteger("userSubDivisionId") + // .build() + // )); + // } + + // @GetMapping("/dashboard/awardSummaryReport") + // public ResponseEntity getAwardSummaryReport(@RequestParam Integer year, + // @RequestParam Integer userSubDivisionId, + // @RequestParam String branch, + // @RequestParam Boolean viewDivisionOnly + // ) throws IOException { + // if(!viewDivisionOnly){ + // List> result = clientService.getAwardSummaryReportPage1(Map.of("year",year, "branch", branch)); + // List> detailResult = clientService.getAwardSummaryReportPage2(Map.of("year",year, "branch", branch)); + // byte[] reportResult = excelReportService.generateDashboardExcelReport(result, detailResult, year); + // return ResponseEntity.ok() + // .header("filename", "award_summary_report_" + LocalDate.now()) + // .body(new ByteArrayResource(reportResult)); + // } + // else{ + // List> result = clientService.getAwardSummaryReportPage1(Map.of("year",year, "userSubDivisionId", userSubDivisionId)); + // List> detailResult = clientService.getAwardSummaryReportPage2(Map.of("year",year, "userSubDivisionId", userSubDivisionId)); + // byte[] reportResult = excelReportService.generateDashboardExcelReport(result, detailResult, year); + // return ResponseEntity.ok() + // .header("filename", "award_summary_report_" + LocalDate.now()) + // .body(new ByteArrayResource(reportResult)); + // } + + // } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/Application.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/Application.java new file mode 100644 index 0000000..db4eea6 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/Application.java @@ -0,0 +1,93 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "application") +@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) +public class Application extends BaseEntity{ + + @NotNull + @Column + private Long eventId; + + @NotBlank + @Column + private String name; + + @Column + private String description; + + @NotBlank + @Column + private String responsibleOfficer; + + @NotBlank + @Column + private String status; + + @Column + private LocalDateTime importDate; + + public LocalDateTime getImportDate() { + return this.importDate; + } + + public void setImportDate(LocalDateTime importDate) { + this.importDate = importDate; + } + + public Long getEventId() { + return this.eventId; + } + + public void setEventId(Long eventId) { + this.eventId = eventId; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getResponsibleOfficer() { + return this.responsibleOfficer; + } + + public void setResponsibleOfficer(String responsibleOfficer) { + this.responsibleOfficer = responsibleOfficer; + } + + public String getStatus() { + return this.status; + } + + public void setStatus(String status) { + this.status = status; + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/ApplicationRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/ApplicationRepository.java new file mode 100644 index 0000000..6dce951 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/ApplicationRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface ApplicationRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/Appreciation.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/Appreciation.java new file mode 100644 index 0000000..5cfb33d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/Appreciation.java @@ -0,0 +1,192 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "apr_appreciation") +public class Appreciation extends BaseEntity{ + + @NotBlank + @NotNull + @Column(columnDefinition = "JSON") + private String sbuIds; + + @Column + @NotNull + private LocalDate receiptDate; + + @Column + @NotNull + private String description; + + @Column + private String clientDepartment; + + @Column + private String clientOrganization; + + @Column + private String clientFullname; + + @Column + private String clientPost; + + @Column + private String venue; + + @Column + private LocalDate clientReplyDate; + + @Column + private LocalDate staffReplyDate; + + @Column + private Long noOfStaff; + + @Column + @NotNull + private LocalDate lnReceiptDate; + + @Column + @NotNull + private Long aprCategoryId; + + @Column + private String remarks; + + @Column + private LocalDateTime importDate; + + public String getSbuIds() { + return this.sbuIds; + } + + public void setSbuIds(String sbuIds) { + this.sbuIds = sbuIds; + } + + public LocalDate getReceiptDate() { + return this.receiptDate; + } + + public void setReceiptDate(LocalDate receiptDate) { + this.receiptDate = receiptDate; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getClientDepartment() { + return this.clientDepartment; + } + + public void setClientDepartment(String clientDepartment) { + this.clientDepartment = clientDepartment; + } + + public String getClientOrganization() { + return this.clientOrganization; + } + + public void setClientOrganization(String clientOrganization) { + this.clientOrganization = clientOrganization; + } + + public String getClientFullname() { + return this.clientFullname; + } + + public void setClientFullname(String clientFullname) { + this.clientFullname = clientFullname; + } + + public String getClientPost() { + return this.clientPost; + } + + public void setClientPost(String clientPost) { + this.clientPost = clientPost; + } + + public String getVenue() { + return this.venue; + } + + public void setVenue(String venue) { + this.venue = venue; + } + + public LocalDate getClientReplyDate() { + return this.clientReplyDate; + } + + public void setClientReplyDate(LocalDate clientReplyDate) { + this.clientReplyDate = clientReplyDate; + } + + public LocalDate getStaffReplyDate() { + return this.staffReplyDate; + } + + public void setStaffReplyDate(LocalDate staffReplyDate) { + this.staffReplyDate = staffReplyDate; + } + + public Long getNoOfStaff() { + return this.noOfStaff; + } + + public void setNoOfStaff(Long noOfStaff) { + this.noOfStaff = noOfStaff; + } + + public LocalDate getLnReceiptDate() { + return this.lnReceiptDate; + } + + public void setLnReceiptDate(LocalDate lnReceiptDate) { + this.lnReceiptDate = lnReceiptDate; + } + + public Long getAprCategoryId() { + return this.aprCategoryId; + } + + public void setAprCategoryId(Long aprCategoryId) { + this.aprCategoryId = aprCategoryId; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public LocalDateTime getImportDate() { + return this.importDate; + } + + public void setImportDate(LocalDateTime importDate) { + this.importDate = importDate; + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/AppreciationRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/AppreciationRepository.java new file mode 100644 index 0000000..b435d64 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/AppreciationRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface AppreciationRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/Award.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/Award.java new file mode 100644 index 0000000..d0e9087 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/Award.java @@ -0,0 +1,149 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "award") +@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) +public class Award extends BaseEntity{ + + @NotNull + @Column + private Long applicationId; + + @NotBlank + @Column + private String name; + + @Column + private String nameCht; + + @Column + private String remarks; + + @Column + private Long categoryId; + + @Column + private LocalDate receiveDate; + + @Column + private String storageLocation; + + @Column + private String physicalAward; + + @NotBlank + @Column + private String responsibleOfficer; + + @Column(columnDefinition = "JSON") + private String promotionChannel; + + @Column + private LocalDateTime importDate; + + public LocalDateTime getImportDate() { + return this.importDate; + } + + public void setImportDate(LocalDateTime importDate) { + this.importDate = importDate; + } + + public Long getApplicationId() { + return this.applicationId; + } + + public void setApplicationId(Long applicationId) { + this.applicationId = applicationId; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNameCht() { + return this.nameCht; + } + + public void setNameCht(String nameCht) { + this.nameCht = nameCht; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public Long getCategoryId() { + return this.categoryId; + } + + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; + } + + public LocalDate getReceiveDate() { + return this.receiveDate; + } + + public void setReceiveDate(LocalDate receiveDate) { + this.receiveDate = receiveDate; + } + + public String getStorageLocation() { + return this.storageLocation; + } + + public void setStorageLocation(String storageLocation) { + this.storageLocation = storageLocation; + } + + public String getPhysicalAward() { + return this.physicalAward; + } + + public void setPhysicalAward(String physicalAward) { + this.physicalAward = physicalAward; + } + + public String getResponsibleOfficer() { + return this.responsibleOfficer; + } + + public void setResponsibleOfficer(String responsibleOfficer) { + this.responsibleOfficer = responsibleOfficer; + } + + public String getPromotionChannel() { + return this.promotionChannel; + } + + public void setPromotionChannel(String promotionChannel) { + this.promotionChannel = promotionChannel; + } + +} + + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/AwardRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/AwardRepository.java new file mode 100644 index 0000000..26fc120 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/AwardRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface AwardRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/Event.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/Event.java new file mode 100644 index 0000000..d122393 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/Event.java @@ -0,0 +1,253 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "event") +@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) +public class Event extends BaseEntity{ + + @NotBlank + @Column + private String name; + + @Column + private String nameCht; + + @Column + private String description; + + @NotBlank + @Column + private String region; + + @NotBlank + @Column + private String organization; + + @NotBlank + @Column + private String eventType; + + @Column + private String frequency; + + @Column + private String series; + + @Column + @NotNull + private LocalDate startDate; + + @Column + private LocalDate eventFrom; + + @Column + private LocalDate eventTo; + + @Column + @NotNull + private LocalDate applicationDeadline; + + @Column + private LocalDate nextApplicationDate; + + @Column + private LocalDate announcementDate; + + @Column + private LocalDate awardDate; + + @Column + private Boolean reminderFlag; + + @Column + private Long reminderThreshold; + + @Column + private Long reminderInterval; + + @Column + private Long reminderLimit; + + @Column + private LocalDateTime importDate; + + public LocalDateTime getImportDate() { + return this.importDate; + } + + public void setImportDate(LocalDateTime importDate) { + this.importDate = importDate; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNameCht() { + return this.nameCht; + } + + public void setNameCht(String nameCht) { + this.nameCht = nameCht; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getRegion() { + return this.region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getOrganization() { + return this.organization; + } + + public void setOrganization(String organization) { + this.organization = organization; + } + + public String getEventType() { + return this.eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public String getFrequency() { + return this.frequency; + } + + public void setFrequency(String frequency) { + this.frequency = frequency; + } + + public String getSeries() { + return this.series; + } + + public void setSeries(String series) { + this.series = series; + } + + public LocalDate getStartDate() { + return this.startDate; + } + + public void setStartDate(LocalDate startDate) { + this.startDate = startDate; + } + + public LocalDate getEventFrom() { + return this.eventFrom; + } + + public void setEventFrom(LocalDate eventFrom) { + this.eventFrom = eventFrom; + } + + public LocalDate getEventTo() { + return this.eventTo; + } + + public void setEventTo(LocalDate eventTo) { + this.eventTo = eventTo; + } + + public LocalDate getApplicationDeadline() { + return this.applicationDeadline; + } + + public void setApplicationDeadline(LocalDate applicationDeadline) { + this.applicationDeadline = applicationDeadline; + } + + public LocalDate getNextApplicationDate() { + return this.nextApplicationDate; + } + + public void setNextApplicationDate(LocalDate nextApplicationDate) { + this.nextApplicationDate = nextApplicationDate; + } + + public LocalDate getAnnouncementDate() { + return this.announcementDate; + } + + public void setAnnouncementDate(LocalDate announcementDate) { + this.announcementDate = announcementDate; + } + + public LocalDate getAwardDate() { + return this.awardDate; + } + + public void setAwardDate(LocalDate awardDate) { + this.awardDate = awardDate; + } + + public Boolean isReminderFlag() { + return this.reminderFlag; + } + + public Boolean getReminderFlag() { + return this.reminderFlag; + } + + public void setReminderFlag(Boolean reminderFlag) { + this.reminderFlag = reminderFlag; + } + + public Long getReminderThreshold() { + return this.reminderThreshold; + } + + public void setReminderThreshold(Long reminderThreshold) { + this.reminderThreshold = reminderThreshold; + } + + public Long getReminderInterval() { + return this.reminderInterval; + } + + public void setReminderInterval(Long reminderInterval) { + this.reminderInterval = reminderInterval; + } + + public Long getReminderLimit() { + return this.reminderLimit; + } + + public void setReminderLimit(Long reminderLimit) { + this.reminderLimit = reminderLimit; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/EventRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/EventRepository.java new file mode 100644 index 0000000..3c457d2 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/EventRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface EventRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/File.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/File.java new file mode 100644 index 0000000..fca0135 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/File.java @@ -0,0 +1,85 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "file") +public class File extends BaseEntity{ + + @NotBlank + @Column + private String filename; + + @NotBlank + @Column + private String skey; + + @Column + private String extension; + + @Column + private String mimetype; + + @Column + private Long filesize; + + @Column + private String remarks; + + public String getFilename() { + return this.filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getSkey() { + return this.skey; + } + + public void setSkey(String skey) { + this.skey = skey; + } + + public String getExtension() { + return this.extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public String getMimetype() { + return this.mimetype; + } + + public void setMimetype(String mimetype) { + this.mimetype = mimetype; + } + + public Long getFilesize() { + return this.filesize; + } + + public void setFilesize(Long filesize) { + this.filesize = filesize; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlob.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlob.java new file mode 100644 index 0000000..e0a7de6 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlob.java @@ -0,0 +1,47 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +/** @author Jason Lam */ +@Entity +@Table(name = "file_blob") +public class FileBlob extends BaseEntity{ + @Column + private Long id; + + @Column + private Long fileId; + + @Column + private byte[] bytes; + + public Long getFileId() { + return this.fileId; + } + + public void setFileId(Long fileId) { + this.fileId = fileId; + } + + public byte[] getBytes() { + return this.bytes; + } + + public void setBytes(byte[] bytes) { + this.bytes = bytes; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlobRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlobRepository.java new file mode 100644 index 0000000..eb0f1f1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileBlobRepository.java @@ -0,0 +1,11 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.util.Optional; +import org.springframework.data.repository.query.Param; + +import com.ffii.core.support.AbstractRepository; + +public interface FileBlobRepository extends AbstractRepository { + + Optional findByFileId(@Param("fileId") Long fileId); +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRef.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRef.java new file mode 100644 index 0000000..92c24c2 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRef.java @@ -0,0 +1,98 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "file_ref") +public class FileRef extends BaseEntity{ + + @Column + private Long fileId; + + @Column + private String refCode; + + @NotNull + @Column + private Long refId; + + @NotBlank + @Column + private String refType; + + @Column + private String remarks; + + @Column + private Long thumbnailFileId; + + @Column + private LocalDateTime importDate; + + public Long getFileId() { + return this.fileId; + } + + public void setFileId(Long fileId) { + this.fileId = fileId; + } + + public String getRefCode() { + return this.refCode; + } + + public void setRefCode(String refCode) { + this.refCode = refCode; + } + + public Long getRefId() { + return this.refId; + } + + public void setRefId(Long refId) { + this.refId = refId; + } + + public String getRefType() { + return this.refType; + } + + public void setRefType(String refType) { + this.refType = refType; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public Long getThumbnailFileId() { + return this.thumbnailFileId; + } + + public void setThumbnailFileId(Long thumbnailFileId) { + this.thumbnailFileId = thumbnailFileId; + } + + public LocalDateTime getImportDate() { + return importDate; + } + + public void setImportDate(LocalDateTime importDate) { + this.importDate = importDate; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRefRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRefRepository.java new file mode 100644 index 0000000..0dcb53a --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRefRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface FileRefRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRepository.java new file mode 100644 index 0000000..845da84 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/FileRepository.java @@ -0,0 +1,5 @@ +package com.ffii.lioner.modules.lioner.entity; +import com.ffii.core.support.AbstractRepository; + +public interface FileRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpApplication.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpApplication.java new file mode 100644 index 0000000..bcddfad --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpApplication.java @@ -0,0 +1,41 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.util.List; + +import jakarta.persistence.Entity; + +public class ImpApplication extends Application { + + //For import use + private Long tempId; + + private List tempSubDivIdList; + + private List tempTagIdList; + + public Long getTempId() { + return this.tempId; + } + + public void setTempId(Long tempId) { + this.tempId = tempId; + } + + public List getTempSubDivIdList() { + return this.tempSubDivIdList; + } + + public void setTempSubDivIdList(List tempSubDivIdList) { + this.tempSubDivIdList = tempSubDivIdList; + } + + public List getTempTagIdList() { + return this.tempTagIdList; + } + + public void setTempTagIdList(List tempTagIdList) { + this.tempTagIdList = tempTagIdList; + } + + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpAward.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpAward.java new file mode 100644 index 0000000..b4b19a0 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpAward.java @@ -0,0 +1,49 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.util.List; + +import jakarta.persistence.Entity; + +public class ImpAward extends Award { + + //For import use + private Long tempId; + + private List tempSubDivIdList; + + private List tempChannelIdList; + + private String tempExternalLink; + + public Long getTempId() { + return this.tempId; + } + + public void setTempId(Long tempId) { + this.tempId = tempId; + } + + public List getTempSubDivIdList() { + return this.tempSubDivIdList; + } + + public void setTempSubDivIdList(List tempSubDivIdList) { + this.tempSubDivIdList = tempSubDivIdList; + } + + public List getTempChannelIdList() { + return this.tempChannelIdList; + } + + public void setTempChannelIdList(List tempChannelIdList) { + this.tempChannelIdList = tempChannelIdList; + } + + public String getTempExternalLink() { + return this.tempExternalLink; + } + + public void setTempExternalLink(String tempExternalLink) { + this.tempExternalLink = tempExternalLink; + } +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpEvent.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpEvent.java new file mode 100644 index 0000000..8a5562d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/ImpEvent.java @@ -0,0 +1,26 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.util.List; + +public class ImpEvent extends Event { + + private Long tempId; + + private List tempIdList; + + public Long getTempId() { + return tempId; + } + + public void setTempId(Long tempId) { + this.tempId = tempId; + } + + public List getTempIdList() { + return tempIdList; + } + + public void setTempIdList(List tempIdList) { + this.tempIdList = tempIdList; + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplate.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplate.java new file mode 100644 index 0000000..7514e67 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplate.java @@ -0,0 +1,67 @@ +package com.ffii.lioner.modules.lioner.entity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Objects; + +/** @author Jason Lam */ +@Entity +@Table(name = "search_criteria_template") +public class SearchCriteriaTemplate extends BaseEntity{ + + @NotNull + @Column + private Long userId; + + @NotBlank + @Column + private String name; + + @NotBlank + @Column + private String module; + + @NotBlank + @Column(columnDefinition = "JSON") + private String criteria; + + public Long getUserId() { + return this.userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModule() { + return this.module; + } + + public void setModule(String module) { + this.module = module; + } + + public String getCriteria() { + return this.criteria; + } + + public void setCriteria(String criteria) { + this.criteria = criteria; + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplateRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplateRepository.java new file mode 100644 index 0000000..af0c031 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/SearchCriteriaTemplateRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; +import com.ffii.core.support.AbstractRepository; + +public interface SearchCriteriaTemplateRepository extends AbstractRepository { +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminder.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminder.java new file mode 100644 index 0000000..c73ff08 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminder.java @@ -0,0 +1,111 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "todo_reminder") +public class TodoReminder extends BaseEntity{ + + @Column + private Long id; + + @NotNull + @Column + private Long eventId; + + @NotNull + @Column + private Long subDivisionId; + + @NotBlank + @Column + private String title; + + @NotBlank + @Column + private String message; + + @Column + private Long suppressedBy; + + @Column + private LocalDateTime suppressedDate; + + @Column + private String reminderType; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getEventId() { + return this.eventId; + } + + public void setEventId(Long eventId) { + this.eventId = eventId; + } + + public Long getSubDivisionId() { + return this.subDivisionId; + } + + public void setSubDivisionId(Long subDivisionId) { + this.subDivisionId = subDivisionId; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getMessage() { + return this.message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Long getSuppressedBy() { + return this.suppressedBy; + } + + public void setSuppressedBy(Long suppressedBy) { + this.suppressedBy = suppressedBy; + } + + public LocalDateTime getSuppressedDate() { + return this.suppressedDate; + } + + public void setSuppressedDate(LocalDateTime suppressedDate) { + this.suppressedDate = suppressedDate; + } + + public String getReminderType() { + return this.reminderType; + } + + public void setReminderType(String reminderType) { + this.reminderType = reminderType; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLog.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLog.java new file mode 100644 index 0000000..6b4a1b3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLog.java @@ -0,0 +1,141 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "todo_reminder_email_log") +public class TodoReminderEmailLog extends BaseEntity{ + + @NotNull + @Column + private Long id; + + @NotNull + @Column + private Long eventId; + + @NotNull + @Column + private Long subDivisionId; + + @NotNull + @Column + private Long userId; + + @NotNull + @Column + private LocalDateTime sendDate; + + @NotNull + @Column + private Boolean success; + + @Column + private String response; + + @NotNull + @Column + private String reminderType; + + @NotNull + @Column + private String content; + + @NotNull + @Column + private String resendSuccess; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getEventId() { + return this.eventId; + } + + public void setEventId(Long eventId) { + this.eventId = eventId; + } + + public Long getSubDivisionId() { + return this.subDivisionId; + } + + public void setSubDivisionId(Long subDivisionId) { + this.subDivisionId = subDivisionId; + } + + public Long getUserId() { + return this.userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public LocalDateTime getSendDate() { + return this.sendDate; + } + + public void setSendDate(LocalDateTime sendDate) { + this.sendDate = sendDate; + } + + public Boolean isSuccess() { + return this.success; + } + + public Boolean getSuccess() { + return this.success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getResponse() { + return this.response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getReminderType() { + return this.reminderType; + } + + public void setReminderType(String reminderType) { + this.reminderType = reminderType; + } + + public String getContent() { + return this.content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getResendSuccess() { + return this.resendSuccess; + } + + public void setResendSuccess(String resendSuccess) { + this.resendSuccess = resendSuccess; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLogRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLogRepository.java new file mode 100644 index 0000000..1e8bb6a --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderEmailLogRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; +import com.ffii.core.support.AbstractRepository; + +public interface TodoReminderEmailLogRepository extends AbstractRepository { +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNoted.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNoted.java new file mode 100644 index 0000000..82c61e0 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNoted.java @@ -0,0 +1,66 @@ +package com.ffii.lioner.modules.lioner.entity; + +import java.time.LocalDateTime; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; + +/** @author Jason Lam */ +@Entity +@Table(name = "todo_reminder_noted") +public class TodoReminderNoted extends BaseEntity{ + + @NotNull + @Column + private Long id; + + @NotNull + @Column + private Long todoReminderId; + + @NotNull + @Column + private Long userId; + + @NotNull + @Column + private LocalDateTime notedDate; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getTodoReminderId() { + return this.todoReminderId; + } + + public void setTodoReminderId(Long todoReminderId) { + this.todoReminderId = todoReminderId; + } + + public Long getUserId() { + return this.userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public LocalDateTime getNotedDate() { + return this.notedDate; + } + + public void setNotedDate(LocalDateTime notedDate) { + this.notedDate = notedDate; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNotedRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNotedRepository.java new file mode 100644 index 0000000..080ad5c --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderNotedRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; +import com.ffii.core.support.AbstractRepository; + +public interface TodoReminderNotedRepository extends AbstractRepository { +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderRepository.java b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderRepository.java new file mode 100644 index 0000000..78338d1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/entity/TodoReminderRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.lioner.entity; +import com.ffii.core.support.AbstractRepository; + +public interface TodoReminderRepository extends AbstractRepository { +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/reportDao/AppreciationCaseReportDao.java b/src/main/java/com/ffii/lioner/modules/lioner/reportDao/AppreciationCaseReportDao.java new file mode 100644 index 0000000..fc53dc6 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/reportDao/AppreciationCaseReportDao.java @@ -0,0 +1,160 @@ +package com.ffii.lioner.modules.lioner.reportDao; + +import java.util.List; +import java.util.Map; + +public class AppreciationCaseReportDao{ + + String formattedDateFrom; + + Long count; + + List> recordCount; + + List> catBreakDowns; + + String compareStr1; + + String compareStr2; + + String finYear; + + Long yearlyCount; + + List> yearlyRecordCount; + + List sbuList; + + List chartList; + + String lnReceiptDateRange; + + List> appreciationRecordDetail; + + public AppreciationCaseReportDao( + String formattedDateFrom, Long count, List> recordCount, + List> catBreakDowns, String compareStr1, String compareStr2, + String finYear, Long yearlyCount, List> yearlyRecordCount, + List sbuList, List chartList, String lnReceiptDateRange, + List> appreciationRecordDetail + ) { + this.formattedDateFrom = formattedDateFrom; + this.count = count; + this.recordCount = recordCount; + this.catBreakDowns = catBreakDowns; + this.compareStr1 = compareStr1; + this.compareStr2 = compareStr2; + this.finYear = finYear; + this.yearlyCount = yearlyCount; + this.yearlyRecordCount = yearlyRecordCount; + this.sbuList = sbuList; + this.chartList = chartList; + this.lnReceiptDateRange = lnReceiptDateRange; + this.appreciationRecordDetail = appreciationRecordDetail; + } + + public List> getAppreciationRecordDetail() { + return this.appreciationRecordDetail; + } + + public void setAppreciationRecordDetail(List> appreciationRecordDetail) { + this.appreciationRecordDetail = appreciationRecordDetail; + } + + public String getLnReceiptDateRange() { + return this.lnReceiptDateRange; + } + + public void setLnReceiptDateRange(String lnReceiptDateRange) { + this.lnReceiptDateRange = lnReceiptDateRange; + } + + public List getChartList() { + return this.chartList; + } + + public void setChartList(List chartList) { + this.chartList = chartList; + } + + public String getFinYear() { + return this.finYear; + } + + public void setFinYear(String finYear) { + this.finYear = finYear; + } + + public Long getYearlyCount() { + return this.yearlyCount; + } + + public void setYearlyCount(Long yearlyCount) { + this.yearlyCount = yearlyCount; + } + + public List> getYearlyRecordCount() { + return this.yearlyRecordCount; + } + + public void setYearlyRecordCount(List> yearlyRecordCount) { + this.yearlyRecordCount = yearlyRecordCount; + } + + public String getFormattedDateFrom() { + return this.formattedDateFrom; + } + + public void setFormattedDateFrom(String formattedDateFrom) { + this.formattedDateFrom = formattedDateFrom; + } + + public Long getCount() { + return this.count; + } + + public void setCount(Long count) { + this.count = count; + } + + public List> getRecordCount() { + return this.recordCount; + } + + public void setRecordCount(List> recordCount) { + this.recordCount = recordCount; + } + + public List> getCatBreakDowns() { + return this.catBreakDowns; + } + + public void setCatBreakDowns(List> catBreakDowns) { + this.catBreakDowns = catBreakDowns; + } + + public String getCompareStr1() { + return this.compareStr1; + } + + public void setCompareStr1(String compareStr1) { + this.compareStr1 = compareStr1; + } + + public String getCompareStr2() { + return this.compareStr2; + } + + public void setCompareStr2(String compareStr2) { + this.compareStr2 = compareStr2; + } + + public List getSbuList() { + return this.sbuList; + } + + public void setSbuList(List sbuList) { + this.sbuList = sbuList; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/reportDao/AppreciationChartDataDao.java b/src/main/java/com/ffii/lioner/modules/lioner/reportDao/AppreciationChartDataDao.java new file mode 100644 index 0000000..03e2804 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/reportDao/AppreciationChartDataDao.java @@ -0,0 +1,64 @@ +package com.ffii.lioner.modules.lioner.reportDao; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class AppreciationChartDataDao { + + private String sbuName; + + private List recordByMonth = Arrays.asList(new Integer[12]); + + public AppreciationChartDataDao(String sbuName){ + this.sbuName = sbuName; + Collections.fill(recordByMonth, 0); + } + + public void setRecordByMonthByIndex(Integer value, Integer location){ + recordByMonth.set(location, value); + } + + public void asAccumulate() { + Integer month = 0; + Integer prevCount = 0; + for (Integer data : recordByMonth) { + if(month == 0){ + prevCount = data; + month++; + } + else{ + setRecordByMonthByIndex(data + prevCount, month); + prevCount = data + prevCount; + month++; + } + } + } + public Integer calculateSum() { + Integer sum = 0; + for (Integer number : recordByMonth) { + if(number != null){ + sum += number; + } + } + return sum; + } + + public String getSbuName() { + return this.sbuName; + } + + public void setSbuName(String sbuName) { + this.sbuName = sbuName; + } + + public List getRecordByMonth() { + return this.recordByMonth; + } + + public void setRecordByMonth(List recordByMonth) { + this.recordByMonth = recordByMonth; + } + + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/reportDao/ImportErrorRecord.java b/src/main/java/com/ffii/lioner/modules/lioner/reportDao/ImportErrorRecord.java new file mode 100644 index 0000000..ca307e8 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/reportDao/ImportErrorRecord.java @@ -0,0 +1,73 @@ +package com.ffii.lioner.modules.lioner.reportDao; + + +public class ImportErrorRecord { + private Long id; + + private Long rowId; + + private String pageName; + + private String column; + + private String remarks; + + public ImportErrorRecord(){ + } + + public ImportErrorRecord(Long id, Long rowId, String column, String remarks){ + this.id = id; + this.rowId = rowId; + this.column = column; + this.remarks = remarks; + } + + public ImportErrorRecord(Long id, Long rowId, String pageName, String column, String remarks){ + this.id = id; + this.rowId = rowId; + this.pageName= pageName; + this.column = column; + this.remarks = remarks; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getRowId() { + return this.rowId; + } + + public void setRowId(Long rowId) { + this.rowId = rowId; + } + + public String getPageName() { + return this.pageName; + } + + public void setPageName(String pageName) { + this.pageName = pageName; + } + + public String getColumn() { + return this.column; + } + + public void setColumn(String column) { + this.column = column; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/CreateImportErrorReportReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/CreateImportErrorReportReq.java new file mode 100644 index 0000000..2874613 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/CreateImportErrorReportReq.java @@ -0,0 +1,19 @@ +package com.ffii.lioner.modules.lioner.req; + +import java.util.List; + +import com.ffii.lioner.modules.lioner.reportDao.ImportErrorRecord; + +public class CreateImportErrorReportReq { + + private List errorRecord; + + public List getErrorRecord() { + return this.errorRecord; + } + + public void setErrorRecord(List errorRecord) { + this.errorRecord = errorRecord; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateApplicationReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateApplicationReq.java new file mode 100644 index 0000000..90a6cb5 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateApplicationReq.java @@ -0,0 +1,93 @@ +package com.ffii.lioner.modules.lioner.req; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +public class UpdateApplicationReq { + + private Long id; + + private Long eventId; + + @Size(max = 255) + String name; + + @Size(max = 500) + String description; + + List subDivisionId; + + List tagId; + + @Size(max = 50) + String responsibleOfficer; + + @Size(max = 50) + String status; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getEventId() { + return this.eventId; + } + + public void setEventId(Long eventId) { + this.eventId = eventId; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getSubDivisionId() { + return this.subDivisionId; + } + + public void setSubDivisionId(List subDivisionId) { + this.subDivisionId = subDivisionId; + } + + public List getTagId() { + return this.tagId; + } + + public void setTagId(List tagId) { + this.tagId = tagId; + } + + public String getResponsibleOfficer() { + return this.responsibleOfficer; + } + + public void setResponsibleOfficer(String responsibleOfficer) { + this.responsibleOfficer = responsibleOfficer; + } + + public String getStatus() { + return this.status; + } + + public void setStatus(String status) { + this.status = status; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateAppreciationReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateAppreciationReq.java new file mode 100644 index 0000000..28becf5 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateAppreciationReq.java @@ -0,0 +1,176 @@ +package com.ffii.lioner.modules.lioner.req; + +import java.time.LocalDate; + +import jakarta.validation.constraints.Size; + +public class UpdateAppreciationReq { + + private Long id; + + private String sbuIds; + + private LocalDate receiptDate; + + private String description; + + @Size(max = 255) + private String clientDepartment; + + @Size(max = 255) + private String clientOrganization; + + @Size(max = 255) + private String clientFullname; + + @Size(max = 255) + private String clientPost; + + @Size(max = 255) + private String venue; + + private LocalDate clientReplyDate; + + private LocalDate staffReplyDate; + + private Integer noOfStaff; + + private LocalDate lnReceiptDate; + + private Integer aprCategoryId; + + @Size(max = 255) + private String remarks; + + private LocalDate importDate; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSbuIds() { + return this.sbuIds; + } + + public void setSbuIds(String sbuIds) { + this.sbuIds = sbuIds; + } + + public LocalDate getReceiptDate() { + return this.receiptDate; + } + + public void setReceiptDate(LocalDate receiptDate) { + this.receiptDate = receiptDate; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getClientDepartment() { + return this.clientDepartment; + } + + public void setClientDepartment(String clientDepartment) { + this.clientDepartment = clientDepartment; + } + + public String getClientOrganization() { + return this.clientOrganization; + } + + public void setClientOrganization(String clientOrganization) { + this.clientOrganization = clientOrganization; + } + + public String getClientFullname() { + return this.clientFullname; + } + + public void setClientFullname(String clientFullname) { + this.clientFullname = clientFullname; + } + + public String getClientPost() { + return this.clientPost; + } + + public void setClientPost(String clientPost) { + this.clientPost = clientPost; + } + + public String getVenue() { + return this.venue; + } + + public void setVenue(String venue) { + this.venue = venue; + } + + public LocalDate getClientReplyDate() { + return this.clientReplyDate; + } + + public void setClientReplyDate(LocalDate clientReplyDate) { + this.clientReplyDate = clientReplyDate; + } + + public LocalDate getStaffReplyDate() { + return this.staffReplyDate; + } + + public void setStaffReplyDate(LocalDate staffReplyDate) { + this.staffReplyDate = staffReplyDate; + } + + public Integer getNoOfStaff() { + return this.noOfStaff; + } + + public void setNoOfStaff(Integer noOfStaff) { + this.noOfStaff = noOfStaff; + } + + public LocalDate getLnReceiptDate() { + return this.lnReceiptDate; + } + + public void setLnReceiptDate(LocalDate lnReceiptDate) { + this.lnReceiptDate = lnReceiptDate; + } + + public Integer getAprCategoryId() { + return this.aprCategoryId; + } + + public void setAprCategoryId(Integer aprCategoryId) { + this.aprCategoryId = aprCategoryId; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public LocalDate getImportDate() { + return this.importDate; + } + + public void setImportDate(LocalDate importDate) { + this.importDate = importDate; + } + + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateEventReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateEventReq.java new file mode 100644 index 0000000..9c4b0a7 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateEventReq.java @@ -0,0 +1,243 @@ +package com.ffii.lioner.modules.lioner.req; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +import java.time.LocalDate; + +public class UpdateEventReq { + + private Long id; + + @Size(max = 255) + String name; + + @Size(max = 255) + String nameCht; + + @Size(max = 500) + String description; + + @Size(max = 50) + String region; + + @Size(max = 255) + String organization; + + @Size(max = 50) + String eventType; + + @Size(max = 50) + String frequency; + + @Size(max = 50) + String series; + + LocalDate startDate; + + LocalDate applicationDeadline; + + LocalDate nextApplicationDate; + + LocalDate announcementDate; + + LocalDate awardDate; + + LocalDate eventFrom; + + LocalDate eventTo; + + Boolean reminderFlag; + + Long reminderThreshold; + + Long reminderInterval; + + Long reminderLimit; + + List subDivisionIds; + + List subDivisionRemoveIds; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNameCht() { + return this.nameCht; + } + + public void setNameCht(String nameCht) { + this.nameCht = nameCht; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getSubDivisionIds() { + return this.subDivisionIds; + } + + public void setSubDivisionIds(List subDivisionIds) { + this.subDivisionIds = subDivisionIds; + } + + public List getSubDivisionRemoveIds() { + return this.subDivisionRemoveIds; + } + + public void setSubDivisionRemoveIds(List subDivisionRemoveIds) { + this.subDivisionRemoveIds = subDivisionRemoveIds; + } + + public String getRegion() { + return this.region; + } + + public void setRegion(String region) { + this.region = region; + } + + public String getOrganization() { + return this.organization; + } + + public void setOrganization(String organization) { + this.organization = organization; + } + + public String getEventType() { + return this.eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public String getFrequency() { + return this.frequency; + } + + public void setFrequency(String frequency) { + this.frequency = frequency; + } + + public String getSeries() { + return this.series; + } + + public void setSeries(String series) { + this.series = series; + } + + public LocalDate getStartDate() { + return this.startDate; + } + + public void setStartDate(LocalDate startDate) { + this.startDate = startDate; + } + + public LocalDate getApplicationDeadline() { + return this.applicationDeadline; + } + + public void setApplicationDeadline(LocalDate applicationDeadline) { + this.applicationDeadline = applicationDeadline; + } + + public LocalDate getNextApplicationDate() { + return this.nextApplicationDate; + } + + public void setNextApplicationDate(LocalDate nextApplicationDate) { + this.nextApplicationDate = nextApplicationDate; + } + + public LocalDate getAnnouncementDate() { + return this.announcementDate; + } + + public void setAnnouncementDate(LocalDate announcementDate) { + this.announcementDate = announcementDate; + } + + public LocalDate getAwardDate() { + return this.awardDate; + } + + public void setAwardDate(LocalDate awardDate) { + this.awardDate = awardDate; + } + + public Boolean isReminderFlag() { + return this.reminderFlag; + } + + public Boolean getReminderFlag() { + return this.reminderFlag; + } + + public void setReminderFlag(Boolean reminderFlag) { + this.reminderFlag = reminderFlag; + } + + public Long getReminderThreshold() { + return this.reminderThreshold; + } + + public void setReminderThreshold(Long reminderThreshold) { + this.reminderThreshold = reminderThreshold; + } + + public Long getReminderInterval() { + return this.reminderInterval; + } + + public void setReminderInterval(Long reminderInterval) { + this.reminderInterval = reminderInterval; + } + + public Long getReminderLimit() { + return this.reminderLimit; + } + + public void setReminderLimit(Long reminderLimit) { + this.reminderLimit = reminderLimit; + } + + public LocalDate getEventFrom() { + return this.eventFrom; + } + + public void setEventFrom(LocalDate eventFrom) { + this.eventFrom = eventFrom; + } + + public LocalDate getEventTo() { + return this.eventTo; + } + + public void setEventTo(LocalDate eventTo) { + this.eventTo = eventTo; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateExtRefReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateExtRefReq.java new file mode 100644 index 0000000..15a7898 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateExtRefReq.java @@ -0,0 +1,50 @@ +package com.ffii.lioner.modules.lioner.req; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateExtRefReq { + + private Long id; + + private Long refId; + + @Size(max = 255) + private String remarks; + + public UpdateExtRefReq() {} + + public UpdateExtRefReq(Long id, Long refId, String remarks) { + this.id = id; + this.refId = refId; + this.remarks = remarks; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getRefId() { + return this.refId; + } + + public void setRefId(Long refId) { + this.refId = refId; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateFileReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateFileReq.java new file mode 100644 index 0000000..dbdeab4 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateFileReq.java @@ -0,0 +1,85 @@ +package com.ffii.lioner.modules.lioner.req; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateFileReq { + + private Long id; + + @Size(max = 255) + private String fileName; + + @Size(max = 10) + private String extension; + + @Size(max = 10) + private String mimetype; + + private Integer filesize; + + @Size(max = 255) + private String remarks; + + private byte[] bytes; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getFileName() { + return this.fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + + public String getExtension() { + return this.extension; + } + + public void setExtension(String extension) { + this.extension = extension; + } + + public String getMimetype() { + return this.mimetype; + } + + public void setMimetype(String mimetype) { + this.mimetype = mimetype; + } + + public Integer getFilesize() { + return this.filesize; + } + + public void setFilesize(Integer filesize) { + this.filesize = filesize; + } + + public String getRemarks() { + return this.remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public byte[] getBytes() { + return this.bytes; + } + + public void setBytes(byte[] bytes) { + this.bytes = bytes; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateSearchCriteriaTemplateReq.java b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateSearchCriteriaTemplateReq.java new file mode 100644 index 0000000..dae0055 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/req/UpdateSearchCriteriaTemplateReq.java @@ -0,0 +1,69 @@ +package com.ffii.lioner.modules.lioner.req; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateSearchCriteriaTemplateReq { + + @NotNull + private Long id; + + @NotNull + private Long userId; + + @NotBlank + @Size(max = 255) + private String name; + + @NotBlank + @Size(max = 50) + private String module; + + @NotBlank + private String criteria; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getUserId() { + return this.userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModule() { + return this.module; + } + + public void setModule(String module) { + this.module = module; + } + + public String getCriteria() { + return this.criteria; + } + + public void setCriteria(String criteria) { + this.criteria = criteria; + } + + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/ApplicationService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/ApplicationService.java new file mode 100644 index 0000000..e00ceb4 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/ApplicationService.java @@ -0,0 +1,588 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.ffii.lioner.modules.lioner.entity.Application; +import com.ffii.lioner.modules.lioner.entity.ApplicationRepository; +import com.ffii.lioner.modules.lioner.entity.ImpApplication; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.SubDivision; +import com.ffii.core.exception.UnprocessableEntityException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.core.utils.JsonUtils; + +import jakarta.persistence.Table; + +@Service +public class ApplicationService extends AbstractBaseEntityService { + + private FileService fileService; + private FileRefService fileRefService; + private AuditLogService auditLogService; + + public ApplicationService(JdbcDao jdbcDao, ApplicationRepository repository, FileService fileService, FileRefService fileRefService, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.fileService = fileService; + this.fileRefService = fileRefService; + this.auditLogService = auditLogService; + } + + public Optional> getApplicationDetail(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " e.id as eventId," + + " a.id, " + + " a.eventId, " + + " e.name as eventName, " + + " a.name, " + + " a.description, " + + " a.status, " + + " a.responsibleOfficer " + + " FROM application a " + + " LEFT JOIN event e ON e.id = a.eventId " + + " WHERE a.deleted = FALSE " + + " AND a.id = :id; " + ); + + return jdbcDao.queryForMap(sql.toString(), req); + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT a.id, " + + " e.name AS eventName, " + + " a.name AS applicationName, " + + " a.status, " + + " a.responsibleOfficer, " + + " a.description " + + " FROM application a " + + " LEFT JOIN event e ON e.id = a.eventId " + + " LEFT JOIN application_tag at2 ON at2.applicationId = a.id AND at2.deleted = FALSE" + + " LEFT JOIN application_sub_division asd ON asd.applicationId = a.id AND asd.deleted = FALSE " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN `user` u ON u.id = a.createdBy " + + " WHERE a.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + if (args.containsKey("eventName")) sql.append(" AND e.name LIKE :eventName "); + if (args.containsKey("applicationName")) sql.append(" AND a.name LIKE :applicationName "); + if (args.containsKey("description")) sql.append(" AND a.description LIKE :description "); + if (args.containsKey("status")) sql.append(" AND a.status LIKE :status "); + if (args.containsKey("officer")) sql.append(" AND a.responsibleOfficer LIKE :officer "); + if (args.containsKey("tagList")) sql.append(" AND at2.tagId IN (:tagList) "); + if (args.containsKey("divisionIdList")) sql.append(" AND sd.divisionId IN (:divisionIdList) "); + if (args.containsKey("subDivisionIdList")) sql.append(" AND sd.id IN (:subDivisionIdList) "); + if (args.containsKey("keyword")){ + sql.append(" AND ( a.createdBy LIKE :keyword "); + sql.append(" OR u.username LIKE :keyword "); + sql.append(" OR u.fullname LIKE :keyword "); + sql.append(" OR u.email LIKE :keyword "); + sql.append(" OR sd.name LIKE :keyword "); + sql.append(" OR d.name LIKE :keyword "); + sql.append(" OR a.name LIKE :keyword "); + sql.append(" OR e.name LIKE :keyword) "); + } + } + sql.append(" ORDER BY a.id desc "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listReport(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT a.*, " + + " (SELECT " + + " GROUP_CONCAT( DISTINCT d2.name) " + + " FROM application a2 " + + " LEFT JOIN application_sub_division asd2 ON a2.id = asd2.applicationId " + + " LEFT JOIN sub_division sd2 ON asd2.subDivisionId = sd2.id " + + " LEFT JOIN division d2 ON d2.id = sd2.divisionId " + + " WHERE a2.id = a.id " + + " AND asd2.deleted = false " + + " GROUP BY a2.id " + + " ) AS divisionList, " + + " e.name AS eventName," + + " u.name as username " + + " FROM application a " + + " LEFT JOIN event e ON e.id = a.eventId " + + " LEFT JOIN application_tag at2 ON at2.applicationId = a.id AND at2.deleted = FALSE" + + " LEFT JOIN application_sub_division asd ON asd.applicationId = a.id AND asd.deleted = FALSE " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN `user` u ON u.id = a.createdBy " + + " WHERE a.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + if (args.containsKey("eventName")) sql.append(" AND e.name LIKE :eventName "); + if (args.containsKey("applicationName")) sql.append(" AND a.name LIKE :applicationName "); + if (args.containsKey("description")) sql.append(" AND a.description LIKE :description "); + if (args.containsKey("status")) sql.append(" AND a.status LIKE :status "); + if (args.containsKey("officer")) sql.append(" AND a.responsibleOfficer LIKE :officer "); + if (args.containsKey("tagList")) sql.append(" AND at2.tagId IN (:tagList) "); + if (args.containsKey("divisionIdList")) sql.append(" AND sd.divisionId IN (:divisionIdList) "); + if (args.containsKey("subDivisionIdList")) sql.append(" AND sd.id IN (:subDivisionIdList) "); + if (args.containsKey("keyword")){ + sql.append(" AND ( a.createdBy LIKE :keyword "); + sql.append(" OR u.username LIKE :keyword "); + sql.append(" OR u.fullname LIKE :keyword "); + sql.append(" OR u.email LIKE :keyword "); + sql.append(" OR sd.name LIKE :keyword "); + sql.append(" OR d.name LIKE :keyword "); + sql.append(" OR a.name LIKE :keyword "); + sql.append(" OR e.name LIKE :keyword) "); + } + } + sql.append(" ORDER BY a.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List getApplicationTag(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " tagId " + + " FROM application_tag at2 " + + " LEFT JOIN tag t ON t.id = at2.tagId " + + " WHERE at2.applicationId = :id " + + " AND t.deleted = FALSE " + + " AND at2.deleted = FALSE; " + ); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public void deleteSubDivision(Integer applicationId){ + List subDivisionIdList = this.getApplicationSubDivision(Map.of("id", applicationId)); + List> deleteDivisionIds = subDivisionIdList.stream() + .map(subDivisionId -> Map.of("applicationId", applicationId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + + if (!deleteDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE application_sub_division asd" + + " SET asd.deleted = TRUE" + + " WHERE applicationId = :applicationId AND subDivisionId = :subDivisionId", + deleteDivisionIds); + } + } + + public boolean isNameTaken(String name, Long id, Long eventId) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(a.id) " + + " FROM application a " + + " WHERE a.deleted =FALSE " + + " AND a.eventId = :eventId " + + " AND a.name = :name " + + " AND a.id != :id " + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id, "eventId", eventId)); + } + + + public void deleteFile(Long applicationId){ + List> fileList = fileService.listFile(Map.of("refId",applicationId,"refType","application")); + fileList.forEach(file -> fileService.deleteFile(Long.valueOf(file.get("fileId").toString()))); + } + + public void deleteExternalRef(Long awardId){ + List> fileRefList = fileRefService.listExternal(Map.of("refId",awardId,"refType","application")); + fileRefList.forEach(fileRef -> fileRefService.delete(Long.valueOf(fileRef.get("id").toString()))); + } + + public void markDeleteWithAuditLog(Application instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getApplicationSubDivisionName(input)); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + this.markDelete(instance.getId()); + this.deleteSubDivision(instance.getId().intValue()); + this.deleteFile(instance.getId()); + this.deleteExternalRef(instance.getId()); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getApplicationSubDivisionName(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + + //=====GET NEW AUDIT LOG=====// + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " a.id, " + + " a.created, " + + " a.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " a.version, " + + " a.modified, " + + " a.modifiedBy AS modifiedById, " + + " a.deleted, " + + " u2.name AS modifiedBy, " + + " a.eventId, " + + " e.name as eventName, " + + " a.name, " + + " a.description, " + + " a.responsibleOfficer, " + + " a.status " + + " FROM application a " + + " LEFT JOIN `user` u1 ON u1.id = a.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = a.modifiedBy " + + " LEFT JOIN event e ON e.id = a.eventId " + + " WHERE a.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List getApplicationSubDivision(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " subDivisionId " + + " FROM application_sub_division asd " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " WHERE asd.applicationId = :id " + + " AND sd.deleted = FALSE " + + " AND asd.deleted = FALSE; " + ); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public List getApplicationSubDivisionName(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.name " + + " FROM application_sub_division asd " + + " JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " WHERE asd.applicationId = :id " + + " AND asd.deleted = FALSE; " + ); + + return jdbcDao.queryForStrings(sql.toString(), req); + } + + public List> listByApplication(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT a2.id, " + + " a2.name, " + + " (SELECT " + + " GROUP_CONCAT(sd.name SEPARATOR ', ') " + + " FROM award_sub_division asd " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " WHERE asd.deleted = FALSE " + + " AND asd.awardId = a2.id) AS subDivisions, " + + " a2.receiveDate, " + + " a2.storageLocation, " + + " a2.responsibleOfficer " + + " FROM application a " + + " JOIN application_sub_division asd ON asd.applicationId = a.id " + + " LEFT JOIN sub_division sd3 ON asd.subDivisionId = sd3.id" + + " LEFT JOIN division d ON d.id = sd3.divisionId " + + " LEFT JOIN award a2 ON a.id = a2.applicationId " + + " WHERE a2.deleted = FALSE " + + " AND a.id = :applicationId " + ); + if (args.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + sql.append(" ORDER BY a2.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public Application saveImportRecord(ImpApplication req) { + Application instance; + if (req.getId() > 0) { + instance = find(req.getId()).get(); + } else { + instance = new Application(); + } + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getApplicationSubDivisionName(input)); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req, instance); + instance = save(instance); + Long applicationId = instance.getId(); + + List> newDivisionIds = new ArrayList>(); + for(Long subDivisionId: req.getTempSubDivIdList()){ + newDivisionIds.add( + Map.of("applicationId", applicationId, "subDivisionId", subDivisionId) + ); + } + + if (!newDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO application_sub_division (applicationId,subDivisionId)" + + " VALUES (:applicationId, :subDivisionId)", + newDivisionIds); + } + + List> newTagIds = new ArrayList>(); + for(Long subDivisionId: req.getTempTagIdList()){ + newTagIds.add( + Map.of("applicationId", applicationId, "tagId", subDivisionId) + ); + } + + if (!newTagIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO application_tag (applicationId,tagId)" + + " VALUES (:applicationId, :tagId)", + newTagIds); + } + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getApplicationSubDivisionName(input)); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + + return instance; + } + + public Map saveOrUpdate( + Long id, Long eventId, String eventName, String description, + String name, String responsibleOfficer, String status, + List subDivisionIdList, List subDivisionRemoveIdList, + ListtagIdList, ListtagRemoveIdList, + MultipartFile[] files + ) throws Exception { + Application instance; + List onUsingIdList = new ArrayList(); + if(id > 0){ + instance = find(id).get(); + onUsingIdList = this.getSelectedSubDivisionList(id); + } + else{ + instance = new Application(); + } + + List subDivisionNameList = new ArrayList(); + for (SubDivision subDivision : onUsingIdList) { + for (Long deleteId: subDivisionRemoveIdList) { + if(deleteId == subDivision.getId()){ + subDivisionNameList.add(subDivision.getName()); + } + } + } + + if(subDivisionNameList.size() > 0){ + String temp = ""; + for (String subDivisionName : subDivisionNameList) { + temp = temp + subDivisionName + "\n"; + } + throw new UnprocessableEntityException( + "Below Sub-Division already exist in award, cannot be deleted: \n" + + temp + ); + } + + instance.setDescription(description); + instance.setName(name); + instance.setResponsibleOfficer(responsibleOfficer); + instance.setStatus(status); + instance.setEventId(eventId); + + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getApplicationSubDivisionName(input)); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + instance = save(instance); + Long applicationId = instance.getId(); + + + List> newDivisionIds = subDivisionIdList.stream() + .map(subDivisionId -> Map.of("applicationId", applicationId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + List> deleteDivisionIds = subDivisionRemoveIdList.stream() + .map(subDivisionId -> Map.of("applicationId", applicationId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + if (!newDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO application_sub_division (applicationId,subDivisionId)" + + " VALUES (:applicationId, :subDivisionId)", + newDivisionIds); + } + if (!deleteDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE application_sub_division asd" + + " SET asd.deleted = TRUE" + + " WHERE applicationId = :applicationId AND subDivisionId = :subDivisionId", + deleteDivisionIds); + } + + List> newTagIds = tagIdList.stream() + .map(tagId -> Map.of("applicationId", applicationId, "tagId", tagId)) + .collect(Collectors.toList()); + List> deleteTagIds = tagRemoveIdList.stream() + .map(tagId -> Map.of("applicationId", applicationId, "tagId", tagId)) + .collect(Collectors.toList()); + if (!newTagIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO application_tag (applicationId,tagId)" + + " VALUES (:applicationId, :tagId)", + newTagIds); + } + if (!deleteTagIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE application_tag at" + + " SET at.deleted = TRUE" + + " WHERE applicationId = :applicationId AND tagId = :tagId", + deleteTagIds); + } + + Map idMap = new HashMap(); + if(files !=null){ + idMap = fileService.saveOrUpdate(files, applicationId, "application"); + } + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getApplicationSubDivisionName(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + + //=====GET NEW AUDIT LOG=====// + return Map.of( + "id", instance.getId(), + "fileId", idMap + ); + } + + public List> listApplicationCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT " + + " a.id, " + + " a.id as `key`, " + + " a.name as label " + + " FROM application a " + + " LEFT JOIN application_sub_division asd ON asd.applicationId = a.id " + + " WHERE a.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey("eventId")) + sql.append(" AND a.eventId = :eventId "); + + if (args.containsKey("userSubDivisionId")) + sql.append(" AND asd.subDivisionId = :userSubDivisionId "); + } + + sql.append(" ORDER BY a.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List getApplicationByEventId(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " a.* " + + " FROM application a " + + " WHERE a.deleted = FALSE " + + " AND a.eventId = :eventId " + ); + + return jdbcDao.queryForList(sql.toString(), req,Application.class); + } + + public List getSelectedSubDivisionList(Long applicationId) { + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT " + + " sd.* " + + " FROM award a " + + " JOIN award_sub_division asd ON asd.awardId = a.id " + + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " WHERE a.applicationId = :applicationId " + ); + + return jdbcDao.queryForList(sql.toString(), Map.of("applicationId", applicationId), SubDivision.class); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/AppreciationService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/AppreciationService.java new file mode 100644 index 0000000..ff3a993 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/AppreciationService.java @@ -0,0 +1,1104 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.docx4j.Docx4J; +import org.docx4j.openpackaging.exceptions.Docx4JException; +import org.docx4j.openpackaging.packages.WordprocessingMLPackage; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ffii.lioner.modules.lioner.entity.Appreciation; +import com.ffii.lioner.modules.lioner.entity.AppreciationRepository; +import com.ffii.lioner.modules.lioner.reportDao.AppreciationCaseReportDao; +import com.ffii.lioner.modules.lioner.reportDao.AppreciationChartDataDao; +import com.ffii.lioner.modules.lioner.reportDao.ImportErrorRecord; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.lioner.modules.common.service.WordReportService; +import com.ffii.lioner.modules.master.service.AppreciationCategoryService; +import com.ffii.lioner.modules.master.service.ClientDepartmentService; +import com.ffii.lioner.modules.master.service.SBUDivisionService; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.core.utils.ExcelUtils; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.NumberUtils; +import com.ffii.core.utils.Params; + +import io.micrometer.common.util.StringUtils; +import jakarta.persistence.Table; +import jakarta.xml.bind.JAXBException; + +@Service +public class AppreciationService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + private FileService fileService; + private SBUDivisionService sbuDivisionService; + private ClientDepartmentService clientDepartmentService; + private AppreciationCategoryService appreciationCategoryService; + private WordReportService wordReportService; + private ExcelReportService excelReportService; + + public AppreciationService(JdbcDao jdbcDao, AppreciationRepository repository, + AuditLogService auditLogService, + FileService fileService, + SBUDivisionService sbuDivisionService, + ClientDepartmentService clientDepartmentService, + AppreciationCategoryService appreciationCategoryService, + WordReportService wordReportService, + ExcelReportService excelReportService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + this.fileService = fileService; + this.sbuDivisionService = sbuDivisionService; + this.clientDepartmentService = clientDepartmentService; + this.appreciationCategoryService = appreciationCategoryService; + this.wordReportService = wordReportService; + this.excelReportService = excelReportService; + } + + public Map getAuditLogObject(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " aa.id, " + + " aa.created, " + + " aa.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " aa.version, " + + " aa.modified, " + + " aa.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " aa.deleted, " + + " aa.sbuIds, " + + " aa.receiptDate, " + + " aa.description, " + + " aa.clientDepartment, " + + " aa.clientOrganization, " + + " aa.clientFullname, " + + " aa.clientPost, " + + " aa.venue, " + + " aa.clientReplyDate, " + + " aa.staffReplyDate, " + + " aa.noOfStaff, " + + " aa.lnReceiptDate, " + + " aa.aprCategoryId, " + + " aa.remarks, " + + " aa.importDate " + + " FROM apr_appreciation aa " + + " LEFT JOIN `user` u1 ON u1.id = aa.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = aa.modifiedBy " + + " WHERE aa.id = :id "); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public void markDeleteWithAuditLog(Appreciation instance) { + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getDescription().length() > 300 ? (instance.getDescription().substring(0, 300) + "...") + : instance.getDescription(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + } + + public Map saveOrUpdate( + Long id, String sbuIds, LocalDate receiptDate, String description, String clientDepartment, + String clientOrganization, String clientFullname, String clientPost, String venue, + LocalDate clientReplyDate, + LocalDate staffReplyDate, Long noOfStaff, LocalDate lnReceiptDate, Long aprCategoryId, String remarks, + MultipartFile[] files) throws Exception { + Appreciation instance; + if (id > 0) { + instance = find(id).get(); + } else { + instance = new Appreciation(); + } + + instance.setSbuIds(sbuIds); + instance.setReceiptDate(receiptDate); + instance.setDescription(description); + if (clientDepartment.compareTo("undefined") != 0) { + instance.setClientDepartment(clientDepartment); + } + instance.setClientOrganization(clientOrganization); + instance.setClientFullname(clientFullname); + instance.setClientPost(clientPost); + instance.setVenue(venue); + instance.setClientReplyDate(clientReplyDate); + instance.setStaffReplyDate(staffReplyDate); + instance.setNoOfStaff(noOfStaff); + instance.setLnReceiptDate(lnReceiptDate); + instance.setAprCategoryId(aprCategoryId); + if (remarks.compareTo("null") != 0) { + instance.setRemarks(remarks); + } + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + instance = save(instance); + Long awardId = instance.getId(); + + Map idMap = new HashMap(); + if (files != null) { + idMap = fileService.saveOrUpdate(files, awardId, "appreciation"); + } + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getDescription().length() > 300 ? (instance.getDescription().substring(0, 300) + "...") + : instance.getDescription(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + return Map.of( + "id", instance.getId(), + "fileId", idMap); + } + + public Appreciation saveImportRecord(Appreciation req) { + Appreciation instance; + if (req.getId() > 0) { + instance = find(req.getId()).get(); + } else { + instance = new Appreciation(); + } + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req, instance); + instance = save(instance); + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getDescription().length() > 300 ? (instance.getDescription().substring(0, 300) + "...") + : instance.getDescription(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + + return instance; + } + + public Map massImport(MultipartFile[] files) throws IOException { + Map result = new HashMap(); + List appreciationRecord = new ArrayList<>(); + List errorRecord = new ArrayList<>(); + LocalDateTime recordImportDate = LocalDateTime.now(); + Integer rowCount = 0; + Boolean templateVersion = true; + Sheet sheet = null; + + ImportErrorRecord templateError = new ImportErrorRecord( + 0L, + 0L, + "Template", + "Please download the latest template for import data"); + + for (MultipartFile file : files) { + Workbook workbook = new XSSFWorkbook(file.getInputStream()); + try { + sheet = workbook.getSheetAt(1); + } catch (Exception e) { + errorRecord.add(templateError); + break; + } + if (sheet.getSheetName().compareTo("Appreciation Records") != 0) { + templateVersion = false; + errorRecord.add(templateError); + break; + } + + for (Row row : sheet) { + // Skip the header row + if (row.getRowNum() == 0) { + // validate template + List validateList = Arrays.asList( + "sbu", "receiptDate", "description", "clientDepartment", "clientOrganization", + "clientName", "clientPost", "venue", "clientReplyDate", "staffReplyDate", + "noOfStaff", "lnReceiptDate", "category", "remarks"); + + for (int i = 0; i < validateList.size(); i++) { + Cell cell = row.getCell(i); + logger.info(getStringValueFromCell(cell) + " vs " + validateList.get(i)); + if (getStringValueFromCell(cell).compareTo(validateList.get(i)) != 0) { + templateVersion = false; + errorRecord.add(templateError); + break; + } + } + continue; + } else if (row.getRowNum() == 1) { + // Skip the header row + continue; + + } + + if (!templateVersion) { + break; + } + Appreciation record = createRecordFromRow(row, errorRecord, recordImportDate); + appreciationRecord.add(record); + rowCount++; + } + workbook.close(); + } + + if (errorRecord.size() == 0) { + // error free import, can process to import appreciation + for (Appreciation appreciation : appreciationRecord) { + saveImportRecord(appreciation); + } + } + + result = Map.of( + "recordCount", rowCount, + "recordList", errorRecord, + "status", errorRecord.size() == 0); + return result; + } + + private Appreciation createRecordFromRow(Row row, List errorRecord, + LocalDateTime recordImportDate) { + // Extract data from the cells of the row and create an AppreciationRecord + // object + Appreciation record = new Appreciation(); + record.setImportDate(recordImportDate); + Long rowNumber = (long) row.getRowNum() + 1; + record.setId((long) -1); + + Cell sbuCell = row.getCell(0); + String sbuString = ExcelUtils.getStringValue(sbuCell); + if (!StringUtils.isBlank(sbuString)) { + String[] sbuList = sbuString.split(",\\s*"); // split string for both ',' and ', ' case + List sbuIdList = new ArrayList<>(); + for (String sbuName : sbuList) { + sbuName = sbuName.trim(); + Long id = (long) sbuDivisionService.getIdByName(sbuName); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "SBU / Division", + sbuName + " doesn't exist"); + errorRecord.add(error); + } else { + sbuIdList.add(new SbuIdObject(id)); + } + } + ObjectMapper objectMapper = new ObjectMapper(); + try { + String sbuIdListJson = objectMapper.writeValueAsString(sbuIdList); + record.setSbuIds(sbuIdListJson); + } catch (JsonProcessingException e) { + // Handle the exception if necessary + e.printStackTrace(); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "SBU / Division", + "This column is mandatory."); + + errorRecord.add(error); + } + + Cell receiptDateCell = row.getCell(1); + if (receiptDateCell != null) { + LocalDate receiptDate = getDateValueFromCell(receiptDateCell); + if (receiptDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Receipt Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setReceiptDate(receiptDate); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Receipt Date", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell descriptionCell = row.getCell(2); + String description = ExcelUtils.getStringValue(descriptionCell); + if (!StringUtils.isBlank(description)) { + record.setDescription(description); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Brief Description", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell clientDepartmentCell = row.getCell(3); + String departmentString = ExcelUtils.getStringValue(clientDepartmentCell); + if (!StringUtils.isBlank(departmentString)) { + Long id = (long) clientDepartmentService.getIdByName(departmentString); + if (departmentString.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Department", + "Value must not exceed 255 character."); + errorRecord.add(error); + record.setClientDepartment(departmentString); + } + + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Department", + departmentString + " doesn't exist"); + errorRecord.add(error); + record.setClientDepartment(departmentString); + } else { + record.setClientDepartment(departmentString); + } + } + + Cell clientOrganizationCell = row.getCell(4); + String organization = ExcelUtils.getStringValue(clientOrganizationCell); + if (!StringUtils.isBlank(organization)) { + if (organization.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Organization", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setClientOrganization(organization); + } + + // post-checking of client department and organization + if (StringUtils.isBlank(record.getClientDepartment()) && + StringUtils.isBlank(record.getClientOrganization())) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Department / Client Organization", + "Please provide either one of the client department or client organization."); + errorRecord.add(error); + } + + // post-checking of client department and organization + if (!StringUtils.isBlank(record.getClientDepartment()) && + !StringUtils.isBlank(record.getClientOrganization())) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Department / Client Organization", + "Please remove one of the client department or client organization."); + errorRecord.add(error); + } + + Cell clientNameCell = row.getCell(5); + String clientName = ExcelUtils.getStringValue(clientNameCell); + if (!StringUtils.isBlank(clientName)) { + if (clientName.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Name", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setClientFullname(clientName); + } + + Cell clientPostCell = row.getCell(6); + String clientPost = ExcelUtils.getStringValue(clientPostCell); + if (!StringUtils.isBlank(clientPost)) { + if (clientPost.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Post", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setClientPost(clientPost); + } + + Cell venueCell = row.getCell(7); + String venue = ExcelUtils.getStringValue(venueCell); + if (!StringUtils.isBlank(venue)) { + if (venue.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Venue", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setVenue(venue); + } + + Cell clientReplyDateCell = row.getCell(8); + + if (clientReplyDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(clientReplyDateCell))) { + LocalDate clientReplyDate = getDateValueFromCell(clientReplyDateCell); + if (clientReplyDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Client Reply Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setClientReplyDate(clientReplyDate); + } + } + + Cell staffReplyDateCell = row.getCell(9); + if (staffReplyDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(staffReplyDateCell))) { + LocalDate staffReplyDate = getDateValueFromCell(staffReplyDateCell); + if (staffReplyDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Staff Reply Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setStaffReplyDate(staffReplyDate); + } + } + + Cell noOfStaffCell = row.getCell(10); + String noOfStaff = ExcelUtils.getStringValue(noOfStaffCell); + if (!StringUtils.isBlank(noOfStaff)) { + try { + Double noOfStaffDouble = Double.parseDouble(noOfStaff); + Long noOfStaffNum = noOfStaffDouble.longValue(); + record.setNoOfStaff(noOfStaffNum); + } catch (NumberFormatException e) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "No of Staff", + "Value must be an integer." + // "Failed to parse '" + noOfStaff + "' to a valid number" + ); + errorRecord.add(error); + } + } + + Cell lnReceiptDateCell = row.getCell(11); + if (lnReceiptDateCell != null) { + LocalDate lnReceiptDate = getDateValueFromCell(lnReceiptDateCell); + if (lnReceiptDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "LN Receipt Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setLnReceiptDate(lnReceiptDate); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "LN Receipt Date", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell categoryCell = row.getCell(12); + String categoryString = ExcelUtils.getStringValue(categoryCell); + if (!StringUtils.isBlank(categoryString)) { + Long id = (long) appreciationCategoryService.getIdByName(categoryString); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Category", + categoryString + " doesn't exist"); + errorRecord.add(error); + record.setAprCategoryId(id); + } else { + record.setAprCategoryId(id); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Category", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell remarksCell = row.getCell(13); + String remarks = ExcelUtils.getStringValue(remarksCell); + if (!StringUtils.isBlank(remarks)) { + if (remarks.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Remarks", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setRemarks(remarks); + } + + return record; + } + + private LocalDate getDateValueFromCell(Cell cell) { + // Handle date formatted numeric cell, if needed + if (cell != null) { + switch (cell.getCellType()) { + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + LocalDate dateValue = cell.getLocalDateTimeCellValue().toLocalDate(); + return dateValue; + } else { + return null; + } + default: + return null; + } + } else { + return null; + } + } + + private String getStringValueFromCell(Cell cell) { + String cellValue = ""; + if (cell != null) { + switch (cell.getCellType()) { + case STRING: + cellValue = cell.getStringCellValue(); + break; + case NUMERIC: + double numericValue = cell.getNumericCellValue(); + cellValue = String.valueOf(numericValue); + break; + default: + break; + } + } + return cellValue; + } + + public List> list(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " aa.id, " + + " aa.sbuIds, " + + " aa.receiptDate, " + + " aa.description, " + + " aa.clientDepartment, " + + " aa.clientOrganization, " + + " IF(aa.clientDepartment IS NULL OR aa.clientDepartment = '', aa.clientOrganization, aa.clientDepartment) AS clientDeptOrg," + + " aa.lnReceiptDate, " + + " ac.name AS category " + + " FROM apr_appreciation aa " + + " LEFT JOIN apr_category ac ON aa.aprCategoryId = ac.id " + + " WHERE aa.deleted = FALSE "); + + if (args != null) { + if (args.containsKey(Params.ID)) + sql.append(" AND aa.id = :id "); + if (args.containsKey("sbuIds")) + sql.append(" AND JSON_OVERLAPS(aa.sbuIds->'$[*].id', :sbuIds) "); + if (args.containsKey("categoryIds")) + sql.append(" AND aa.aprCategoryId IN (:categoryIds) "); + if (args.containsKey("clientDepOrOrg")) { + // check both dep and org + sql.append( + " AND aa.clientDepartment LIKE :clientDepOrOrg OR aa.clientOrganization LIKE :clientDepOrOrg "); + } + if (args.containsKey("clientName")) { + sql.append(" AND aa.clientFullname LIKE :clientName "); + } + + if (args.containsKey("receiptDateFrom")) + sql.append(" AND DATE(aa.receiptDate) >= :receiptDateFrom"); + if (args.containsKey("receiptDateTo")) + sql.append(" AND DATE(aa.receiptDate) < :receiptDateTo"); + + if (args.containsKey("lnReceiptDateFrom")) + sql.append(" AND DATE(aa.lnReceiptDate) >= :lnReceiptDateFrom"); + if (args.containsKey("lnReceiptDateTo")) + sql.append(" AND DATE(aa.lnReceiptDate) < :lnReceiptDateTo"); + + if (args.containsKey("clientReplyDateFrom")) + sql.append(" AND DATE(aa.clientReplyDate) >= :clientReplyDateFrom"); + if (args.containsKey("clientReplyDateTo")) + sql.append(" AND DATE(aa.clientReplyDate) < :clientReplyDateTo"); + + if (args.containsKey("staffReplyDateFrom")) + sql.append(" AND DATE(aa.staffReplyDate) >= :staffReplyDateFrom"); + if (args.containsKey("staffReplyDateTo")) + sql.append(" AND DATE(aa.staffReplyDate) < :staffReplyDateTo"); + + } + sql.append(" ORDER BY aa.lnReceiptDate desc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listReport(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " aa.*, " + + " ac.name AS category, " + + " u.name AS username " + + " FROM apr_appreciation aa " + + " LEFT JOIN apr_category ac ON aa.aprCategoryId = ac.id " + + " LEFT JOIN `user` u ON u.id = aa.createdBy " + + " WHERE aa.deleted = FALSE "); + + if (args != null) { + if (args.containsKey(Params.ID)) + sql.append(" AND aa.id = :id "); + if (args.containsKey("sbuIds")) + sql.append(" AND JSON_OVERLAPS(aa.sbuIds->'$[*].id', :sbuIds) "); + if (args.containsKey("categoryIds")) + sql.append(" AND aa.aprCategoryId IN (:categoryIds) "); + if (args.containsKey("clientDepOrOrg")) { + // check both dep and org + sql.append( + " AND aa.clientDepartment LIKE :clientDepOrOrg OR aa.clientOrganization LIKE :clientDepOrOrg "); + } + if (args.containsKey("clientName")) { + sql.append(" AND aa.clientFullname LIKE :clientName "); + } + + if (args.containsKey("receiptDateFrom")) + sql.append(" AND DATE(aa.receiptDate) >= :receiptDateFrom"); + if (args.containsKey("receiptDateTo")) + sql.append(" AND DATE(aa.receiptDate) < :receiptDateTo"); + + if (args.containsKey("lnReceiptDateFrom")) + sql.append(" AND DATE(aa.lnReceiptDate) >= :lnReceiptDateFrom"); + if (args.containsKey("lnReceiptDateTo")) + sql.append(" AND DATE(aa.lnReceiptDate) < :lnReceiptDateTo"); + + if (args.containsKey("clientReplyDateFrom")) + sql.append(" AND DATE(aa.clientReplyDate) >= :clientReplyDateFrom"); + if (args.containsKey("clientReplyDateTo")) + sql.append(" AND DATE(aa.clientReplyDate) < :clientReplyDateTo"); + + if (args.containsKey("staffReplyDateFrom")) + sql.append(" AND DATE(aa.staffReplyDate) >= :staffReplyDateFrom"); + if (args.containsKey("staffReplyDateTo")) + sql.append(" AND DATE(aa.staffReplyDate) < :staffReplyDateTo"); + + } + // sql.append(" ORDER BY aa.id"); + sql.append(" ORDER BY aa.lnReceiptDate desc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(aa.id) " + + " FROM apr_appreciation aa " + + " WHERE aa.deleted = FALSE " + + " AND aa.name = :name " + + " AND aa.id != :id"); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + + public List> getAppreciationsCaseCount(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " ac.name, " + + " COUNT(aa.id) AS appreciationCount " + + " FROM apr_appreciation aa " + + " LEFT JOIN apr_category ac ON ac.id = aa.aprCategoryId " + + " WHERE aa.deleted = FALSE " + + " AND DATE(aa.lnReceiptDate) >= :dateFrom " + + " AND DATE(aa.lnReceiptDate) < :dateTo " + + " GROUP BY ac.name "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> getAppreciationMonthlyCountByName(Map args) { + StringBuilder sql = new StringBuilder("SELECT " + + " YEAR(aa.lnReceiptDate) AS recordYear, " + + " MONTH(aa.lnReceiptDate) AS recordMonth, " + + " jt.id, " + + " as2.name, " + + " COUNT(*) AS count " + + " FROM apr_appreciation aa, " + + " JSON_TABLE(aa.sbuIds, '$[*]' COLUMNS ( " + + " id INT PATH '$.id' " + + " )) AS jt " + + " LEFT JOIN apr_sbu as2 ON jt.id = as2.id " + + " WHERE aa.deleted = FALSE " + + " AND DATE(aa.lnReceiptDate) >= :dateFrom " + + " AND DATE(aa.lnReceiptDate) < :dateTo " + + " AND as2.name LIKE :name " + + " GROUP BY jt.id, MONTH(aa.lnReceiptDate), YEAR(aa.lnReceiptDate) " + + " ORDER BY YEAR(aa.lnReceiptDate), MONTH(aa.lnReceiptDate) "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> getAppreciationSBUCountByPeriod(Map args) { + // StringBuilder sql = new StringBuilder("SELECT " + // + " jt.id, " + // + " as2.name, " + // + " COUNT(*) AS count " + // + " FROM apr_sbu as2" + // + " LEFT JOIN apr_appreciation aa, " + // + " JSON_TABLE(aa.sbuIds, '$[*]' COLUMNS ( " + // + " id INT PATH '$.id' " + // + " )) AS jt ON jt.id = as2.id" + // + " WHERE aa.deleted = FALSE " + // + " AND as2.deleted = FALSE " + // + " AND DATE(aa.lnReceiptDate) >= :dateFrom " + // + " AND DATE(aa.lnReceiptDate) < :dateTo " + // + " GROUP BY jt.id " + // + " ORDER BY as2.name ; "); + + // show all sbu even no record + StringBuilder sql = new StringBuilder("SELECT " + + " as2.id," + + " as2.name," + + " CAST(SUM(IFNULL(temp.count, 0)) AS SIGNED) as count" + + " FROM apr_sbu as2" + + " LEFT JOIN " + + " ( SELECT " + + " jt.id, " + + " COUNT(*) AS count" + + " FROM apr_appreciation aa, " + + " JSON_TABLE(aa.sbuIds, '$[*]' COLUMNS ( " + + " id INT PATH '$.id' " + + " )) AS jt" + + " WHERE aa.deleted = FALSE" + + " AND DATE(aa.lnReceiptDate) >= :dateFrom " + + " AND DATE(aa.lnReceiptDate) < :dateTo " + + " GROUP BY jt.id) temp ON temp.id = as2.id" + + " WHERE as2.deleted = false" + + " GROUP BY as2.id" + + " ORDER BY as2.name"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> getReportDetailAppreciation(Map args) { + StringBuilder sql = new StringBuilder("SELECT " + + " GROUP_CONCAT(as2.name SEPARATOR ', ') AS sbuNames, " + + " aa.* " + + " FROM apr_appreciation aa, " + + " JSON_TABLE(aa.sbuIds, '$[*]' COLUMNS ( " + + " id INT PATH '$.id' " + + " )) AS jt " + + " LEFT JOIN apr_sbu as2 ON jt.id = as2.id " + + " WHERE aa.deleted = FALSE " + + " AND DATE(aa.lnReceiptDate) >= :dateFrom " + + " AND DATE(aa.lnReceiptDate) < :dateTo " + + " GROUP BY aa.id "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> getReportSummaryRecord(Map args) { + StringBuilder sql = new StringBuilder("SELECT " + + " aa.description, " + + " aa.lnReceiptDate, " + + " ac.name AS category " + + " FROM apr_appreciation aa " + + " LEFT JOIN apr_category ac ON ac.id = aa.aprCategoryId " + + " WHERE aa.deleted = FALSE " + + " AND MONTH(aa.lnReceiptDate) = :month " + + " AND YEAR(aa.lnReceiptDate) = :year "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public byte[] generateCaseReport(LocalDate dateFrom, LocalDate dateTo) + throws IOException, Docx4JException, JAXBException { + LocalDate curMonthDateFrom = dateFrom.withDayOfMonth(1); + LocalDate curMonthDateTo = dateFrom.withDayOfMonth(dateFrom.lengthOfMonth()); + + LocalDate prevMonthDateFrom = curMonthDateFrom.minusMonths(1); + LocalDate prevMonthDateTo = prevMonthDateFrom.withDayOfMonth(prevMonthDateFrom.lengthOfMonth()); + + Integer year = dateFrom.getYear(); + Integer lastTwoDigits = year % 100; + + LocalDate yearlyDateFrom; + LocalDate yearlyDateTo; + + String finYear = ""; + Integer reportMonth = dateFrom.getMonthValue(); + if (reportMonth <= 3) { + // last financial year + yearlyDateFrom = LocalDate.of(curMonthDateFrom.getYear() - 1, 4, 1); + yearlyDateTo = LocalDate.of(curMonthDateFrom.getYear(), 3, 31); + finYear = String.format("%d/%02d", year - 1, lastTwoDigits); + } else { + // this financial year + yearlyDateFrom = LocalDate.of(curMonthDateFrom.getYear(), 4, 1); + yearlyDateTo = LocalDate.of(curMonthDateFrom.getYear() + 1, 3, 31); + finYear = String.format("%d/%02d", year, lastTwoDigits + 1); + } + + String formattedDateFrom = dateFrom.getMonth().getDisplayName(TextStyle.FULL, Locale.ENGLISH) + " " + + dateFrom.getYear(); + + List> recordCount = getAppreciationsCaseCount( + Map.of( + "dateFrom", curMonthDateFrom, + "dateTo", curMonthDateTo.plusDays(1))); + + List> prevMonthRecordCount = getAppreciationsCaseCount( + Map.of( + "dateFrom", prevMonthDateFrom, + "dateTo", prevMonthDateTo.plusDays(1))); + + // Mabel said that the summary should be cumulative instead of current month + // during 6/5/2024 meeting + List> cumulativeRecordCount = getAppreciationsCaseCount( + Map.of( + "dateFrom", yearlyDateFrom, + "dateTo", curMonthDateTo.plusDays(1))); + + List> yearlyRecordCount = getAppreciationSBUCountByPeriod( + Map.of( + "dateFrom", yearlyDateFrom, + "dateTo", yearlyDateTo.plusDays(1))); + + long cumulativeCount = 0L; + Long count = 0L; + Long prevCount = 0L; + Long yearlyCount = 0L; + + List> catBreakDowns = new ArrayList<>(); + + for (Map record : recordCount) { + count += (Long) record.get("appreciationCount"); + } + + for (Map record : prevMonthRecordCount) { + prevCount += (Long) record.get("appreciationCount"); + } + + for (Map record : cumulativeRecordCount) { + cumulativeCount += (Long) record.get("appreciationCount"); + catBreakDowns.add( + Map.of( + "catTotal", String.format("%s (%d)", + NumberUtils.convertToWord(((Long) record.get("appreciationCount")).intValue()), + ((Long) record.get("appreciationCount")).intValue()), + "catName", record.get("name"))); + } + + List sbuList = new ArrayList<>(); + for (Map record : yearlyRecordCount) { + yearlyCount += (Long) record.get("count"); + sbuList.add((String) record.get("name")); + } + + Long difference = Math.abs(count - prevCount); + String compareStr1 = count > prevCount ? "more than" : count == prevCount ? "equal to" : "less than"; + String compareStr2 = count == prevCount ? "" + : String.format("by %s", NumberUtils.convertToWord(difference.intValue())); + + List chartList = new ArrayList<>(); + for (String sbuName : sbuList) { + List> chartRecord = getAppreciationMonthlyCountByName( + Map.of( + "dateFrom", yearlyDateFrom, + "dateTo", yearlyDateTo.plusDays(1), + "name", sbuName)); + AppreciationChartDataDao chartDataDao = new AppreciationChartDataDao(sbuName); + for (Map monthlyRecord : chartRecord) { + if (((Integer) monthlyRecord.get("recordYear")) == yearlyDateFrom.getYear()) { + // 4,5,6,7,8,9,10,11,12 case, -4 + chartDataDao.setRecordByMonthByIndex(((Long) monthlyRecord.get("count")).intValue(), + (Integer) monthlyRecord.get("recordMonth") - 4); + } else { + // 1,2,3 case, +8 + chartDataDao.setRecordByMonthByIndex(((Long) monthlyRecord.get("count")).intValue(), + (Integer) monthlyRecord.get("recordMonth") + 8); + } + } + // chartDataDao.asAccumulate(); + chartList.add(chartDataDao); + } + Collections.reverse(chartList); + + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd MMM YYYY"); + String lnReceiptDateRange = dateFrom.format(dateFormat) + " - " + dateTo.minusDays(1).format(dateFormat); + + List> appreciationRecordDetail = getReportDetailAppreciation( + Map.of( + "dateFrom", dateFrom, + "dateTo", dateTo)); + + AppreciationCaseReportDao reportDao = new AppreciationCaseReportDao( + formattedDateFrom, cumulativeCount, cumulativeRecordCount, catBreakDowns, + compareStr1, compareStr2, + finYear, yearlyCount, yearlyRecordCount, sbuList, chartList, + lnReceiptDateRange, appreciationRecordDetail); + byte[] reportResult = wordReportService.generateAppreciationCasesXMLFile(reportDao); + + return convertXMLtoDocx(reportResult); + } + + public byte[] generateSummaryReport(Integer year, Integer month) + throws IOException, Docx4JException, JAXBException { + + List> reportRecord = getReportSummaryRecord(Map.of("year", year, "month", month)); + final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MMM YYYY"); + String reportHeading = LocalDate.of(year, month, 1).format(dateFormat); + byte[] reportResult = wordReportService.generateAppreciationSummaryXMLFile(reportHeading, reportRecord); + + return convertXMLtoDocx(reportResult); + } + + public byte[] generateImportErrorReport(List record) throws IOException { + byte[] reportResult = excelReportService.generateAppreciationImportErrorReport(record); + return reportResult; + } + + // Object for Import Template + class SbuIdObject { + private Long id; + + public SbuIdObject(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + public List> getAppreciationYearCombo() { + StringBuilder sql = new StringBuilder("SELECT " + + " DISTINCT year(aa.lnReceiptDate) AS id, " + + " CAST(YEAR(aa.lnReceiptDate) AS CHAR(4)) AS label " + + " FROM apr_appreciation aa " + + " WHERE aa.deleted = FALSE " + + " ORDER BY id DESC "); + + return jdbcDao.queryForList(sql.toString()); + } + + private byte[] convertXMLtoDocx(byte[] xmlByte) throws Docx4JException { + ByteArrayOutputStream docxOutputStream = new ByteArrayOutputStream(); + WordprocessingMLPackage wmlPackage = Docx4J.load(new ByteArrayInputStream(xmlByte)); + Docx4J.save(wmlPackage, docxOutputStream, Docx4J.FLAG_SAVE_ZIP_FILE); + return docxOutputStream.toByteArray(); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/AwardService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/AwardService.java new file mode 100644 index 0000000..63fc87d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/AwardService.java @@ -0,0 +1,2234 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ffii.lioner.modules.lioner.entity.Application; +import com.ffii.lioner.modules.lioner.entity.Award; +import com.ffii.lioner.modules.lioner.entity.AwardRepository; +import com.ffii.lioner.modules.lioner.entity.Event; +import com.ffii.lioner.modules.lioner.entity.ImpApplication; +import com.ffii.lioner.modules.lioner.entity.ImpAward; +import com.ffii.lioner.modules.lioner.entity.ImpEvent; +import com.ffii.lioner.modules.lioner.reportDao.ImportErrorRecord; +import com.ffii.lioner.modules.lioner.req.UpdateExtRefReq; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.lioner.modules.master.service.CategoryService; +import com.ffii.lioner.modules.master.service.PromotionChannelService; +import com.ffii.lioner.modules.master.service.SubDivisionService; +import com.ffii.lioner.modules.master.service.TagService; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.core.utils.ExcelUtils; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.StringUtils; + +import jakarta.persistence.Table; + +@Service +public class AwardService extends AbstractBaseEntityService { + + private static List eventRegions = Arrays.asList("Local", "Oversea"); + private static List eventTypes = Arrays.asList("Event", "Competition"); + private static List applicationStatus = Arrays.asList("Open", "Submitted", "Cancelled"); + private static List frequencies = Arrays.asList("Annually", "Bi-annually", "Others"); + + private FileService fileService; + private FileRefService fileRefService; + private AuditLogService auditLogService; + private SubDivisionService subDivisionService; + private TagService tagService; + private CategoryService categoryService; + private PromotionChannelService promotionChannelService; + private ExcelReportService excelReportService; + private EventService eventService; + private ApplicationService applicationService; + + public AwardService(JdbcDao jdbcDao, AwardRepository repository, FileService fileService, + FileRefService fileRefService, AuditLogService auditLogService, SubDivisionService subDivisionService, + TagService tagService, CategoryService categoryService, PromotionChannelService promotionChannelService, + ExcelReportService excelReportService, EventService eventService, ApplicationService applicationService) { + super(jdbcDao, repository); + this.fileService = fileService; + this.fileRefService = fileRefService; + this.auditLogService = auditLogService; + this.subDivisionService = subDivisionService; + this.tagService = tagService; + this.categoryService = categoryService; + this.promotionChannelService = promotionChannelService; + this.excelReportService = excelReportService; + this.eventService = eventService; + this.applicationService = applicationService; + } + + public Optional> getAwardDetail(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " a.id, " + + " a2.id as applicationId, " + + " a2.eventId, " + + " a.name, " + + " a.nameCht, " + + " a.remarks, " + + " e.name AS eventName, " + + " a2.name AS applicationName, " + + " a.receiveDate, " + + " a.storageLocation, " + + " a.physicalAward, " + + " a.categoryId, " + + " c.name as categoryName, " + + " a.promotionChannel, " + + " a.responsibleOfficer " + + " FROM award a " + + " LEFT JOIN category c ON a.categoryId = c.id " + + " LEFT JOIN application a2 ON a2.id = a.applicationId " + + " LEFT JOIN event e ON e.id = a2.eventId " + + " WHERE a.deleted = FALSE " + + " AND a.id = :id; "); + + return jdbcDao.queryForMap(sql.toString(), req); + } + + public Map getAuditLogObject(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " a.id, " + + " a.created, " + + " a.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " a.version, " + + " a.modified, " + + " a.modifiedBy AS modifiedById, " + + " a.deleted, " + + " a.applicationId, " + + " a2.name AS applicationName, " + + " a.name, " + + " a.nameCht, " + + " a.remarks, " + + " a.categoryId, " + + " c.name AS categoryName, " + + " a.receiveDate, " + + " a.storageLocation, " + + " a.physicalAward, " + + " a.responsibleOfficer, " + + " a.promotionChannel " + + " FROM award a " + + " LEFT JOIN `user` u1 ON u1.id = a.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = a.modifiedBy " + + " LEFT JOIN application a2 ON a.applicationId = a2.id " + + " LEFT JOIN category c ON c.id = a.categoryId " + + " WHERE a.id = :id "); + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List> list(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT a.id, " + + " e.name AS eventName, " + + " a2.name AS applicationName, " + + " a.name AS awardName, " + + " a.responsibleOfficer " + + " FROM award a " + + " LEFT JOIN award_sub_division asd ON a.id = asd.awardId AND asd.deleted = FALSE " + + " LEFT JOIN application a2 ON a2.id = a.applicationId " + + " LEFT JOIN application_tag at2 ON a2.id = at2.applicationId AND at2.deleted = FALSE" + + " LEFT JOIN event e ON e.id = a2.eventId " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN `user` u ON u.id = a.createdBy " + + " WHERE a.deleted = FALSE "); + + if (args != null) { + if (args.containsKey("userSubDivisionId")) + sql.append( + " AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + if (args.containsKey("eventName")) + sql.append(" AND e.name LIKE :eventName "); + if (args.containsKey("applicationName")) + sql.append(" AND a2.name LIKE :applicationName "); + if (args.containsKey("fromDate")) + sql.append(" AND DATE(e.startDate) >= :fromDate"); + if (args.containsKey("toDate")) + sql.append(" AND DATE(e.startDate) < :toDate"); + if (args.containsKey("awardFromDate")) + sql.append(" AND DATE(e.awardDate) >= :awardFromDate"); + if (args.containsKey("awardToDate")) + sql.append(" AND DATE(e.awardDate) < :awardToDate"); + if (args.containsKey("awardName")) + sql.append(" AND a.name LIKE :awardName "); + if (args.containsKey("categoryIds")) + sql.append(" AND a.categoryId IN (:categoryIds) "); + if (args.containsKey("tagIds")) + sql.append(" AND at2.tagId IN (:tagIds) "); + if (args.containsKey("divisionIdList")) + sql.append(" AND sd.divisionId IN (:divisionIdList) "); + if (args.containsKey("subDivisionIdList")) + sql.append(" AND sd.id IN (:subDivisionIdList) "); + if (args.containsKey("storageLocation")) + sql.append(" AND a.storageLocation LIKE :storageLocation "); + if (args.containsKey("physicalAward")) + sql.append(" AND a.physicalAward LIKE :physicalAward "); + // if (args.containsKey("promotionChannel")) sql.append(" AND a.promotionChannel + // LIKE :promotionChannel "); + if (args.containsKey("promotionChannel")) + sql.append(" AND JSON_OVERLAPS(a.promotionChannel->'$[*].id', :promotionChannel)"); + if (args.containsKey("responsibleOfficer")) + sql.append(" AND a.responsibleOfficer LIKE :responsibleOfficer "); + if (args.containsKey("keyword")) { + sql.append(" AND ( a.createdBy LIKE :keyword "); + sql.append(" OR u.username LIKE :keyword "); + sql.append(" OR u.fullname LIKE :keyword "); + sql.append(" OR u.email LIKE :keyword "); + sql.append(" OR sd.name LIKE :keyword "); + sql.append(" OR d.name LIKE :keyword "); + sql.append(" OR a.name LIKE :keyword "); + sql.append(" OR a2.name LIKE :keyword "); + sql.append(" OR e.name LIKE :keyword) "); + } + } + sql.append(" ORDER BY a.id desc "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listReport(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT a.*, " + + " (SELECT " + + " GROUP_CONCAT( DISTINCT d2.name) " + + " FROM award a2 " + + " LEFT JOIN award_sub_division asd2 ON a2.id = asd2.awardId " + + " LEFT JOIN sub_division sd2 ON asd2.subDivisionId = sd2.id " + + " LEFT JOIN division d2 ON d2.id = sd2.divisionId " + + " WHERE a2.id = a.id " + + " AND asd2.deleted = false " + + " GROUP BY a2.id " + + " ) AS divisionList, " + + " e.name AS eventName, " + + " a2.name AS applicationName, " + + " u.name AS username, " + + " c.name AS categoryName " + + " FROM award a " + + " LEFT JOIN award_sub_division asd ON a.id = asd.awardId AND asd.deleted = FALSE " + + " LEFT JOIN application a2 ON a2.id = a.applicationId " + + " LEFT JOIN category c ON a.categoryId = c.id " + + " LEFT JOIN application_tag at2 ON a2.id = at2.applicationId AND at2.deleted = FALSE" + + " LEFT JOIN event e ON e.id = a2.eventId " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN `user` u ON u.id = a.createdBy " + + " WHERE a.deleted = FALSE "); + + if (args != null) { + if (args.containsKey("userSubDivisionId")) + sql.append( + " AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + if (args.containsKey("eventName")) + sql.append(" AND e.name LIKE :eventName "); + if (args.containsKey("applicationName")) + sql.append(" AND a2.name LIKE :applicationName "); + if (args.containsKey("fromDate")) + sql.append(" AND DATE(e.startDate) >= :fromDate"); + if (args.containsKey("toDate")) + sql.append(" AND DATE(e.startDate) < :toDate"); + if (args.containsKey("awardFromDate")) + sql.append(" AND DATE(e.awardDate) >= :awardFromDate"); + if (args.containsKey("awardToDate")) + sql.append(" AND DATE(e.awardDate) < :awardToDate"); + if (args.containsKey("awardName")) + sql.append(" AND a.name LIKE :awardName "); + if (args.containsKey("categoryIds")) + sql.append(" AND a.categoryId IN (:categoryIds) "); + if (args.containsKey("tagIds")) + sql.append(" AND at2.tagId IN (:tagIds) "); + if (args.containsKey("divisionIdList")) + sql.append(" AND sd.divisionId IN (:divisionIdList) "); + if (args.containsKey("subDivisionIdList")) + sql.append(" AND sd.id IN (:subDivisionIdList) "); + if (args.containsKey("storageLocation")) + sql.append(" AND a.storageLocation LIKE :storageLocation "); + if (args.containsKey("physicalAward")) + sql.append(" AND a.physicalAward LIKE :physicalAward "); + if (args.containsKey("promotionChannel")) + sql.append(" AND JSON_OVERLAPS(a.promotionChannel->'$[*].id', :promotionChannel)"); + if (args.containsKey("responsibleOfficer")) + sql.append(" AND a.responsibleOfficer LIKE :responsibleOfficer "); + if (args.containsKey("keyword")) { + sql.append(" AND ( a.createdBy LIKE :keyword "); + sql.append(" OR u.username LIKE :keyword "); + sql.append(" OR u.fullname LIKE :keyword "); + sql.append(" OR u.email LIKE :keyword "); + sql.append(" OR sd.name LIKE :keyword "); + sql.append(" OR d.name LIKE :keyword "); + sql.append(" OR a.name LIKE :keyword "); + sql.append(" OR a2.name LIKE :keyword "); + sql.append(" OR e.name LIKE :keyword) "); + } + } + sql.append(" ORDER BY a.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public void markDeleteWithAuditLog(Award instance) { + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getAwardSubDivisionName(input)); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + this.markDelete(instance.getId()); + this.deleteSubDivision(instance.getId().intValue()); + this.deleteFile(instance.getId()); + this.deleteExternalRef(instance.getId()); + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getAwardSubDivisionName(input)); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + } + + public void deleteSubDivision(Integer awardId) { + List subDivisionIdList = this.getAwardSubDivision(Map.of("id", awardId)); + List> deleteDivisionIds = subDivisionIdList.stream() + .map(subDivisionId -> Map.of("awardId", awardId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + + if (!deleteDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE award_sub_division asd" + + " SET asd.deleted = TRUE" + + " WHERE awardId = :awardId AND subDivisionId = :subDivisionId", + deleteDivisionIds); + } + } + + public void deleteFile(Long awardId) { + List> fileList = fileService.listFile(Map.of("refId", awardId, "refType", "award")); + fileList.forEach(file -> fileService.deleteFile(Long.valueOf(file.get("fileId").toString()))); + } + + public void deleteExternalRef(Long awardId) { + List> fileRefList = fileRefService + .listExternal(Map.of("refId", awardId, "refType", "award")); + fileRefList.forEach(fileRef -> fileRefService.delete(Long.valueOf(fileRef.get("id").toString()))); + } + + public List getAwardSubDivision(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " subDivisionId " + + " FROM award_sub_division asd " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " WHERE asd.awardId = :id " + + " AND sd.deleted = FALSE " + + " AND asd.deleted = FALSE; "); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public List getAwardSubDivisionName(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " sd.name " + + " FROM award_sub_division asd " + + " JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " WHERE asd.awardId = :id " + + " AND asd.deleted = FALSE; "); + + return jdbcDao.queryForStrings(sql.toString(), req); + } + + public byte[] generateImportErrorReport(List record) throws IOException { + byte[] reportResult = excelReportService.generateAwardImportErrorReport(record); + return reportResult; + } + + public Award saveImportRecord(ImpAward req) throws JsonProcessingException { + Award instance; + if (req.getId() > 0) { + instance = find(req.getId()).get(); + } else { + instance = new Award(); + } + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getAwardSubDivisionName(input)); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req, instance); + + List jsonObjects = new ArrayList<>(); + ObjectMapper objectMapper = new ObjectMapper(); + + for (Long promoId : req.getTempChannelIdList()) { + Map channelObj = promotionChannelService.getChannelById(Map.of("id", promoId)); + + String jsonObject = objectMapper.writeValueAsString(channelObj); + jsonObjects.add(jsonObject); + } + + String channelJson = "[" + String.join(",", jsonObjects) + "]"; + + instance.setPromotionChannel(channelJson); + instance = save(instance); + Long awardId = instance.getId(); + + List> newDivisionIds = new ArrayList>(); + for (Long subDivisionId : req.getTempSubDivIdList()) { + newDivisionIds.add( + Map.of("awardId", awardId, "subDivisionId", subDivisionId)); + } + + if (!newDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO award_sub_division (awardId,subDivisionId)" + + " VALUES (:awardId, :subDivisionId)", + newDivisionIds); + } + + if (req.getTempExternalLink() != null) { + fileRefService.saveExternal(new UpdateExtRefReq( + -1L, + awardId, + req.getTempExternalLink()), "award", req.getImportDate()); + } + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getAwardSubDivisionName(input)); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + + return instance; + } + + public Map saveOrUpdate( + Long id, Long applicationId, String name, String nameCht, String remarks, Long categoryId, + LocalDate receiveDate, + String storageLocation, String physicalAward, String responsibleOfficer, + String promotionChannel, List subDivisionIdList, List subDivisionRemoveIdList, + MultipartFile[] files) throws Exception { + Award instance; + if (id > 0) { + instance = find(id).get(); + } else { + instance = new Award(); + } + + instance.setApplicationId(applicationId); + instance.setName(name); + instance.setNameCht(nameCht); + instance.setRemarks(remarks); + instance.setCategoryId(categoryId); + instance.setReceiveDate(receiveDate); + instance.setStorageLocation(storageLocation); + instance.setPhysicalAward(physicalAward); + instance.setResponsibleOfficer(responsibleOfficer); + instance.setPromotionChannel(promotionChannel); + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getAwardSubDivisionName(input)); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + instance = save(instance); + Long awardId = instance.getId(); + + List> newDivisionIds = subDivisionIdList.stream() + .map(subDivisionId -> Map.of("awardId", awardId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + List> deleteDivisionIds = subDivisionRemoveIdList.stream() + .map(subDivisionId -> Map.of("awardId", awardId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + if (!newDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO award_sub_division (awardId,subDivisionId)" + + " VALUES (:awardId, :subDivisionId)", + newDivisionIds); + } + if (!deleteDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE award_sub_division asd" + + " SET asd.deleted = TRUE" + + " WHERE awardId = :awardId AND subDivisionId = :subDivisionId", + deleteDivisionIds); + } + + Map idMap = new HashMap(); + if (files != null) { + idMap = fileService.saveOrUpdate(files, awardId, "award"); + } + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getAwardSubDivisionName(input)); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + return Map.of( + "id", instance.getId(), + "fileId", idMap); + } + + public boolean isNameTaken(String name, Long id, Long applicationId) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(a.id) " + + " FROM award a " + + " WHERE a.deleted = FALSE " + + " AND a.applicationId = :applicationId " + + " AND a.name = :name " + + " AND a.id != :id "); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id, "applicationId", applicationId)); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + public Map massImport(MultipartFile[] files) throws IOException { + Map result = new HashMap(); + List eventRecord = new ArrayList(); + List applicationRecord = new ArrayList(); + List awardRecord = new ArrayList(); + List errorRecord = new ArrayList<>(); + LocalDateTime recordImportDate = LocalDateTime.now(); + Integer eventCount = 0; + int applicationCount = 0; + int awardCount = 0; + Boolean templateVersion = true; + Sheet sheet = null; + + ImportErrorRecord templateError = new ImportErrorRecord( + 0L, + 0L, + "Template", + "Please download the latest template for import data"); + + List validatePage = Arrays.asList( + "Event", "Application", "Award"); + + for (MultipartFile file : files) { + Workbook workbook = new XSSFWorkbook(file.getInputStream()); + try { + sheet = workbook.getSheetAt(1); + } catch (Exception e) { + logger.info("ERROR!"); + errorRecord.add(templateError); + break; + } + + // validate template + for (String page : validatePage) { + Sheet testSheet = workbook.getSheet(page); + if (testSheet != null) { + List validateList = new ArrayList<>(); + switch (page) { + case "Event": + validateList = Arrays.asList( + "name", "nameCht", "desc", "region", "org", + "type", "from", "to", "appDate", "series", + "awardDate", "appDeadline", "announcementDate", "subDiv1", "subDiv2", + "subDiv3", "subDiv4", "subDiv5", "frequency", "newAppDate", + "reminderEnable", "reminderBefore", "reminderInterval", "reminderLimit"); + break; + case "Application": + validateList = Arrays.asList( + "event", "name", "desc", "status", "ro", + "subDiv1", "subDiv2", "subDiv3", "subDiv4", "subDiv5", + "tag1", "tag2", "tag3", "tag4", "tag5"); + break; + case "Award": + validateList = Arrays.asList( + "event", "application", "name", "nameCht", "recDate", + "storageLoc", "PhyAward", "category", "ro", + "remarks", "subDiv1", "subDiv2", "subDiv3", "subDiv4", + "subDiv5", "proCh1", "proCh2", "proCh3", "proCh4", + "proCh5", "extLink"); + break; + default: + break; + } + for (Row row : testSheet) { + if (row.getRowNum() == 0) { + for (int i = 0; i < validateList.size(); i++) { + Cell cell = row.getCell(i); + if (getStringValueFromCell(cell).compareTo(validateList.get(i)) != 0) { + // logger.info(getStringValueFromCell(cell) + " vs " + validateList.get(i)); + templateVersion = false; + errorRecord.add(templateError); + break; + } + } + } else { + break; + } + } + } else { + templateVersion = false; + errorRecord.add(templateError); + break; + } + } + + for (String page : validatePage) { + sheet = workbook.getSheet(page); + List duplicatedCheckList = new ArrayList<>(); + if (templateVersion) { + for (Row row : sheet) { + // Skip the header row + if (row.getRowNum() <= 1) { + // Skip the header row + continue; + } + + if (row.getCell(0) == null || row.getCell(0).toString().trim().length() == 0) { + // end of record + break; + } + + switch (page) { + case "Event": + ImpEvent eventObj = createEventRecordFromRow(row, errorRecord, recordImportDate, + duplicatedCheckList); + eventRecord.add(eventObj); + eventCount++; + break; + case "Application": + ImpApplication applicationObj = createApplicationRecordFromRow(row, errorRecord, + recordImportDate, eventRecord, duplicatedCheckList); + applicationRecord.add(applicationObj); + applicationCount++; + break; + case "Award": + ImpAward record = createAwardRecordFromRow(row, errorRecord, recordImportDate, + eventRecord, + applicationRecord, duplicatedCheckList); + awardRecord.add(record); + awardCount++; + break; + default: + break; + } + } + } + } + workbook.close(); + } + + // final checking + postDataChecking(eventRecord, applicationRecord, awardRecord, errorRecord); + + // sort by page and row number + errorRecord.sort((e1, e2) -> { + int compIdx = Integer.compare(validatePage.indexOf(e1.getPageName()), + validatePage.indexOf(e2.getPageName())); + if (compIdx == 0) { + compIdx = e1.getRowId().compareTo(e2.getRowId()); + } + return compIdx; + }); + + // import record + Long importCount = 0L; + if (errorRecord.size() == 0) { + // error free import, can process to import appreciation + for (ImpEvent event : eventRecord) { + Event savedEvent = eventService.saveImportRecord(event); + importCount++; + // get all related application + List importApplicationList = new ArrayList(); + for (ImpApplication application : applicationRecord) { + if (application.getEventId() == event.getTempId()) { + importApplicationList.add(application); + + } + } + + for (ImpApplication application : importApplicationList) { + // import application + + // update Event Id + application.setEventId(savedEvent.getId()); + Application savedApplication = applicationService.saveImportRecord(application); + importCount++; + applicationRecord.remove(application); + + // get all related award + List importAwardList = new ArrayList(); + for (ImpAward award : awardRecord) { + if (award.getApplicationId() == application.getTempId()) { + importAwardList.add(award); + } + } + + for (ImpAward award : importAwardList) { + // import award + + // update Award Id + award.setApplicationId(savedApplication.getId()); + saveImportRecord(award); + importCount++; + awardRecord.remove(award); + } + } + } + } + + result = Map.of( + "eventCount", eventCount, + "applicationCount", applicationCount, + "awardCount", awardCount, + "recordList", errorRecord, + "status", errorRecord.size() == 0); + return result; + } + + private void postDataChecking( + List events, List applications, List awards, + List importErrorRecords) { + for (ImpEvent event : events) { + // Check if event exist in DB + if (eventService.getIdByName(event.getName()) != 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) importErrorRecords.size(), + event.getTempId(), + "Event", + "Event Name", + event.getName() + " already exist in the system."); + importErrorRecords.add(error); + } + + // Check if any application for event + int eventIdMatchesCount = 0; + for (Application application : applications) { + if (application.getEventId() == event.getTempId()) { + eventIdMatchesCount++; + } + } + if (eventIdMatchesCount == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) importErrorRecords.size(), + event.getTempId(), + "Event", + "Event Name", + "No associated application found in the template."); + importErrorRecords.add(error); + } + } + + for (ImpApplication application : applications) { + + // check for sub division + ImpEvent parentEvent = null; + for (ImpEvent event : events) { + if (event.getTempId() != null && event.getTempId().equals(application.getEventId())) { + parentEvent = event; + break; + } + } + + if (parentEvent != null) { + List parentSubDivList = parentEvent.getTempIdList(); + List applicationSubDivList = application.getTempSubDivIdList(); + + for (Long subDivId : applicationSubDivList) { + if (!parentSubDivList.contains(subDivId)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) importErrorRecords.size(), + application.getTempId(), + "Application", + "Sub-Division", + "Sub-division does not exist in the associated event."); + importErrorRecords.add(error); + } + } + } + + // Check if contain award + int awardIdMatchesCount = 0; + for (Award award : awards) { + if (award.getApplicationId() == application.getTempId()) { + awardIdMatchesCount++; + } + } + if (awardIdMatchesCount == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) importErrorRecords.size(), + application.getTempId(), + "Application", + "Application Name", + "No associated award found in the template."); + importErrorRecords.add(error); + } + } + + for (ImpAward award : awards) { + // check for sub division + ImpApplication parentApplication = null; + for (ImpApplication application : applications) { + if (application.getTempId() != null && application.getTempId().equals(award.getApplicationId())) { + parentApplication = application; + break; + } + } + + if (parentApplication != null) { + List parentSubDivList = parentApplication.getTempSubDivIdList(); + List applicationSubDivList = award.getTempSubDivIdList(); + + for (Long subDivId : applicationSubDivList) { + if (!parentSubDivList.contains(subDivId)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) importErrorRecords.size(), + award.getTempId(), + "Award", + "Sub-Division", + "Sub-division does not exist in the associated application."); + importErrorRecords.add(error); + } + } + } + } + } + + private ImpEvent createEventRecordFromRow(Row row, List errorRecord, + LocalDateTime recordImportDate, List duplicatedCheckList) { + // Extract data from the cells of the row and create an AppreciationRecord + ImpEvent record = new ImpEvent(); + record.setImportDate(recordImportDate); + Long rowNumber = (long) row.getRowNum() + 1; + record.setId((long) -1); + + List subDivIdList = new ArrayList(); + Cell nameCell = row.getCell(0); + String nameString = StringUtils.removeLineBreak(ExcelUtils.getStringValue(nameCell)); + if (!StringUtils.isBlank(nameString)) { + if (nameString.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Name", + "Value must not exceed 255 character."); + errorRecord.add(error); + } else if (duplicatedCheckList.contains(nameString)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Name", + "Duplicated Event."); + errorRecord.add(error); + } + record.setName(nameString); + duplicatedCheckList.add(nameString); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Name", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell nameChiCell = row.getCell(1); + String nameChiString = ExcelUtils.getStringValue(nameChiCell); + if (!StringUtils.isBlank(nameChiString)) { + if (nameChiString.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Name(Chinese)", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setNameCht(nameChiString); + ; + } + + Cell descriptionCell = row.getCell(2); + String description = ExcelUtils.getStringValue(descriptionCell); + if (!StringUtils.isBlank(description)) { + if (description.length() > 500) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Description", + "Value must not exceed 500 character."); + errorRecord.add(error); + } + record.setDescription(description); + } + + Cell regionCell = row.getCell(3); + String region = ExcelUtils.getStringValue(regionCell); + if (!StringUtils.isBlank(region)) { + if (!eventRegions.contains(region)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Region", + "Invalid value."); + errorRecord.add(error); + } else { + record.setRegion(region); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Region", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell orgCell = row.getCell(4); + String orgString = ExcelUtils.getStringValue(orgCell); + if (!StringUtils.isBlank(orgString)) { + if (orgString.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Organization", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setOrganization(orgString); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Organization", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell eventTypeCell = row.getCell(5); + String eventType = ExcelUtils.getStringValue(eventTypeCell); + if (!StringUtils.isBlank(eventType)) { + if (!eventTypes.contains(eventType)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Type", + "Invalid value."); + errorRecord.add(error); + } else { + record.setEventType(eventType); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Type", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell eventFromCell = row.getCell(6); + if (eventFromCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(eventFromCell))) { + LocalDate eventFromDate = getDateValueFromCell(eventFromCell); + if (eventFromDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event From Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setEventFrom(eventFromDate); + } + } + + Cell eventToCell = row.getCell(7); + if (eventToCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(eventToCell))) { + LocalDate eventToDate = getDateValueFromCell(eventToCell); + if (eventToDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event To Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + // check if valid from to date + if (eventFromCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(eventFromCell))) { + LocalDate eventFromDate = getDateValueFromCell(eventFromCell); + if (eventFromDate != null && eventFromDate.isAfter(eventToDate)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event To Date", + "Event To Date must be later than Event From Date."); + errorRecord.add(error); + } + } + record.setEventTo(eventToDate); + } + } + + Cell appDateCell = row.getCell(8); + if (appDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(appDateCell))) { + LocalDate applicationDate = getDateValueFromCell(appDateCell); + if (applicationDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Application Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setStartDate(applicationDate); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Application Date", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell seriesCell = row.getCell(9); + String seriesString = ExcelUtils.getStringValue(seriesCell); + if (!StringUtils.isBlank(seriesString)) { + if (seriesString.length() > 50) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Series", + "Value must not exceed 50 character."); + errorRecord.add(error); + } + record.setSeries(seriesString); + } + + Cell awardDateCell = row.getCell(10); + if (awardDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(awardDateCell))) { + LocalDate awardDate = getDateValueFromCell(awardDateCell); + if (awardDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Award Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setAwardDate(awardDate); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Award Date", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell appDeadLineCell = row.getCell(11); + if (appDeadLineCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(appDeadLineCell))) { + LocalDate appDeadLine = getDateValueFromCell(appDeadLineCell); + if (appDeadLine == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Application Deadline", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setApplicationDeadline(appDeadLine); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Application Deadline", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell announcementDateCell = row.getCell(12); + if (announcementDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(announcementDateCell))) { + LocalDate announcementDate = getDateValueFromCell(announcementDateCell); + if (announcementDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Announcement Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setAnnouncementDate(announcementDate); + } + } + + Cell subDiv1Cell = row.getCell(13); + if (subDiv1Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv1Cell))) { + String subDiv1 = ExcelUtils.getStringValue(subDiv1Cell); + if (subDiv1 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv1); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Sub-Division 1", + subDiv1 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv2Cell = row.getCell(14); + if (subDiv2Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv2Cell))) { + String subDiv2 = ExcelUtils.getStringValue(subDiv2Cell); + if (subDiv2 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv2); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Sub-Division 2", + subDiv2 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv3Cell = row.getCell(15); + if (subDiv3Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv3Cell))) { + String subDiv3 = ExcelUtils.getStringValue(subDiv3Cell); + if (subDiv3 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv3); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Sub-Division 3", + subDiv3 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv4Cell = row.getCell(16); + if (subDiv4Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv4Cell))) { + String subDiv4 = ExcelUtils.getStringValue(subDiv4Cell); + if (subDiv4 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv4); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Sub-Division 4", + subDiv4 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv5Cell = row.getCell(17); + if (subDiv5Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv5Cell))) { + String subDiv5 = ExcelUtils.getStringValue(subDiv5Cell); + if (subDiv5 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv5); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Sub-Division 5", + subDiv5 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell freqCell = row.getCell(18); + String freqString = ExcelUtils.getStringValue(freqCell); + if (!StringUtils.isBlank(freqString)) { + if (!frequencies.contains(freqString)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Frequency", + "Invalid value."); + errorRecord.add(error); + } else { + record.setFrequency(freqString); + } + } + + Cell newAppDateCell = row.getCell(19); + if (newAppDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(newAppDateCell))) { + LocalDate newAppDate = getDateValueFromCell(newAppDateCell); + if (newAppDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Next Application Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setNextApplicationDate(newAppDate); + } + } + + Cell reminderCell = row.getCell(20); + if (reminderCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(reminderCell))) { + String reminderFlag = ExcelUtils.getStringValue(reminderCell); + if (!StringUtils.isBlank(nameString)) { + if (reminderFlag.compareTo("TRUE") == 0 || + reminderFlag.compareTo("FALSE") == 0) { + record.setReminderFlag(reminderFlag.compareTo("TRUE") == 0 ? true : false); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Enabled", + "Invalid value."); + errorRecord.add(error); + record.setReminderFlag(false); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Event Name", + "This column is mandatory."); + errorRecord.add(error); + record.setReminderFlag(false); + } + } else { + record.setReminderFlag(false); + } + + Cell reminderBeforeCell = row.getCell(21); + String reminderBeforeValue = ExcelUtils.getStringValue(reminderBeforeCell); + if (!StringUtils.isBlank(reminderBeforeValue)) { + try { + Long value = Long.parseLong(reminderBeforeValue); + record.setReminderThreshold(value); + } catch (NumberFormatException e) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Before", + "Invalid value."); + errorRecord.add(error); + } + } else if (record.getReminderFlag()) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Before", + "This column is mandatory."); + errorRecord.add(error); + } else { + record.setReminderThreshold(0L); + } + + Cell reminderIntervalCell = row.getCell(22); + String reminderIntervalValue = ExcelUtils.getStringValue(reminderIntervalCell); + if (!StringUtils.isBlank(reminderIntervalValue)) { + try { + Long value = Long.parseLong(reminderIntervalValue); + record.setReminderInterval(value); + } catch (NumberFormatException e) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Interval", + "Invalid value."); + errorRecord.add(error); + } + } else if (record.isReminderFlag()) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Interval", + "This column is mandatory."); + errorRecord.add(error); + } else { + record.setReminderInterval(0L); + } + + Cell reminderLimitCell = row.getCell(23); + String reminderLimitValue = ExcelUtils.getStringValue(reminderLimitCell); + if (!StringUtils.isBlank(reminderLimitValue)) { + try { + Long value = Long.parseLong(reminderLimitValue); + record.setReminderLimit(value); + } catch (NumberFormatException e) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Limit", + "Invalid value."); + errorRecord.add(error); + } + } else if (record.getReminderFlag()) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Event", + "Reminder Limit", + "This column is mandatory."); + errorRecord.add(error); + } else { + record.setReminderLimit(0L); + } + + record.setTempId(rowNumber); + record.setTempIdList(subDivIdList); + return record; + } + + private ImpApplication createApplicationRecordFromRow(Row row, List errorRecord, + LocalDateTime recordImportDate, List events, List duplicatedCheckList) { + // Extract data from the cells of the row and create an Application Record + ImpApplication record = new ImpApplication(); + record.setImportDate(recordImportDate); + Long rowNumber = (long) row.getRowNum() + 1; + record.setId((long) -1); + record.setTempId(rowNumber); + + List subDivIdList = new ArrayList<>(); + List tagIdList = new ArrayList<>(); + Cell eventNameCell = row.getCell(0); + String eventNameString = StringUtils.removeLineBreak(ExcelUtils.getStringValue(eventNameCell)); + if (!StringUtils.isBlank(eventNameString)) { + // find event tempId + ImpEvent event = events.stream() + .filter(e -> eventNameString.equals(e.getName())) + .findFirst() + .orElse(null); + + if (event != null) { + record.setEventId(event.getTempId()); + } else { + record.setEventId(null); + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Event", + eventNameCell + " doesn't exist in Event sheet"); + errorRecord.add(error); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Event", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell applicationNameCell = row.getCell(1); + String applicationNameString = StringUtils.removeLineBreak(ExcelUtils.getStringValue(applicationNameCell)); + if (!StringUtils.isBlank(applicationNameString)) { + if (applicationNameString.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Application Name", + "Value must not exceed 255 character."); + errorRecord.add(error); + } else if (duplicatedCheckList.contains(eventNameString + applicationNameString)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Application Name", + "Duplicated Application."); + errorRecord.add(error); + } + record.setName(applicationNameString); + duplicatedCheckList.add(eventNameString + applicationNameString); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Application Name", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell descriptionCell = row.getCell(2); + String description = ExcelUtils.getStringValue(descriptionCell); + if (!StringUtils.isBlank(description)) { + if (description.length() > 500) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Application Description", + "Value must not exceed 500 character."); + errorRecord.add(error); + } + record.setDescription(description); + } + + Cell statusCell = row.getCell(3); + String status = ExcelUtils.getStringValue(statusCell); + if (!StringUtils.isBlank(status)) { + if (!applicationStatus.contains(status)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Status", + "Invalid value."); + errorRecord.add(error); + + } else { + record.setStatus(status); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Status", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell officerCell = row.getCell(4); + String officerString = ExcelUtils.getStringValue(officerCell); + if (!StringUtils.isBlank(officerString)) { + record.setResponsibleOfficer(officerString); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Responsible Officer", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell subDiv1Cell = row.getCell(5); + if (subDiv1Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv1Cell))) { + String subDiv1 = ExcelUtils.getStringValue(subDiv1Cell); + if (subDiv1 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv1); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Sub-Division 1", + subDiv1 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Sub-Division 1", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell subDiv2Cell = row.getCell(6); + if (subDiv2Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv2Cell))) { + String subDiv2 = ExcelUtils.getStringValue(subDiv2Cell); + if (subDiv2 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv2); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Sub-Division 2", + subDiv2 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv3Cell = row.getCell(7); + if (subDiv3Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv3Cell))) { + String subDiv3 = ExcelUtils.getStringValue(subDiv3Cell); + if (subDiv3 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv3); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Sub-Division 3", + subDiv3 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv4Cell = row.getCell(8); + if (subDiv4Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv4Cell))) { + String subDiv4 = ExcelUtils.getStringValue(subDiv4Cell); + if (subDiv4 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv4); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Sub-Division 4", + subDiv4 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv5Cell = row.getCell(9); + if (subDiv5Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv5Cell))) { + String subDiv5 = ExcelUtils.getStringValue(subDiv5Cell); + if (subDiv5 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv5); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Sub-Division 5", + subDiv5 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell tag1Cell = row.getCell(10); + if (tag1Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(tag1Cell))) { + String tag1 = ExcelUtils.getStringValue(tag1Cell); + if (tag1 != null) { + Long id = (long) tagService.getIdByName(tag1); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Tag 1", + tag1 + " doesn't exist"); + errorRecord.add(error); + } else { + tagIdList.add(id); + } + } + } + + Cell tag2Cell = row.getCell(11); + if (tag2Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(tag2Cell))) { + String tag2 = ExcelUtils.getStringValue(tag2Cell); + if (tag2 != null) { + Long id = (long) tagService.getIdByName(tag2); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Tag 2", + tag2 + " doesn't exist"); + errorRecord.add(error); + } else { + tagIdList.add(id); + } + } + } + + Cell tag3Cell = row.getCell(12); + if (tag3Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(tag3Cell))) { + String tag3 = ExcelUtils.getStringValue(tag3Cell); + if (tag3 != null) { + Long id = (long) tagService.getIdByName(tag3); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Tag 3", + tag3 + " doesn't exist"); + errorRecord.add(error); + } else { + tagIdList.add(id); + } + } + } + + Cell tag4Cell = row.getCell(13); + if (tag4Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(tag4Cell))) { + String tag4 = ExcelUtils.getStringValue(tag4Cell); + if (tag4 != null) { + Long id = (long) tagService.getIdByName(tag4); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Tag 4", + tag4 + " doesn't exist"); + errorRecord.add(error); + } else { + tagIdList.add(id); + } + } + } + + Cell tag5Cell = row.getCell(14); + if (tag5Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(tag5Cell))) { + String tag5 = ExcelUtils.getStringValue(tag5Cell); + if (tag5 != null) { + Long id = (long) tagService.getIdByName(tag5); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Application", + "Tag 5", + tag5 + " doesn't exist"); + errorRecord.add(error); + } else { + tagIdList.add(id); + } + } + } + + record.setTempSubDivIdList(subDivIdList); + record.setTempTagIdList(tagIdList); + return record; + } + + private ImpAward createAwardRecordFromRow(Row row, List errorRecord, + LocalDateTime recordImportDate, List events, List applications, + List duplicatedCheckList) { + // Extract data from the cells of the row and create an AppreciationRecord + ImpAward record = new ImpAward(); + record.setImportDate(recordImportDate); + Long rowNumber = (long) row.getRowNum() + 1; + record.setId((long) -1); + record.setTempId(rowNumber); + + List subDivIdList = new ArrayList<>(); + List channelIdList = new ArrayList<>(); + + Cell eventNameCell = row.getCell(0); + String eventNameString = StringUtils.removeLineBreak(ExcelUtils.getStringValue(eventNameCell)); + if (!StringUtils.isBlank(eventNameString)) { + // find event tempId + ImpEvent event = events.stream() + .filter(e -> eventNameString.equals(e.getName())) + .findFirst() + .orElse(null); + + if (event != null) { + // No value to set for award case + // record.set(event.getTempId()); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Event", + eventNameCell + " doesn't exist in Event sheet"); + errorRecord.add(error); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Event", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell applicationNameCell = row.getCell(1); + String applicationName = StringUtils.removeLineBreak(ExcelUtils.getStringValue(applicationNameCell)); + if (!StringUtils.isBlank(applicationName)) { + // find application tempId + List matchedApplications = applications.stream() + .filter(e -> applicationName.equals(e.getName())).collect(Collectors.toList()); + + if (matchedApplications.size() > 0) { + ImpEvent event = events.stream() + .filter(e -> eventNameString.equals(e.getName())) + .findFirst() + .orElse(null); + // handle duplicated application name but different event + for (ImpApplication matchedApplication : matchedApplications) { + if (matchedApplication.getEventId() == event.getTempId()) { + record.setApplicationId(matchedApplication.getTempId()); + } + } + } else { + record.setApplicationId(null); + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Application", + applicationName + " doesn't exist in Application sheet"); + errorRecord.add(error); + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Application", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell awardNameCell = row.getCell(2); + String awardNameString = StringUtils.removeLineBreak(ExcelUtils.getStringValue(awardNameCell)); + if (!StringUtils.isBlank(awardNameString)) { + if (awardNameString.length() > 500) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Award Name", + "Value must not exceed 500 character."); + errorRecord.add(error); + } else if (duplicatedCheckList.contains(eventNameString + applicationName + awardNameString)) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Award Name", + "Duplicated Award."); + errorRecord.add(error); + } + duplicatedCheckList.add(eventNameString + applicationName + awardNameString); + record.setName(awardNameString); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Award Name", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell awardNameChiCell = row.getCell(3); + String awardNameChi = ExcelUtils.getStringValue(awardNameChiCell); + if (!StringUtils.isBlank(awardNameChi)) { + if (awardNameChi.length() > 500) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Award Name (Chinese)", + "Value must not exceed 500 character."); + errorRecord.add(error); + } + record.setNameCht(awardNameChi); + } + + Cell receiveDateCell = row.getCell(4); + if (receiveDateCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(receiveDateCell))) { + LocalDate receiveDate = getDateValueFromCell(receiveDateCell); + if (receiveDate == null) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Received Date", + "Incorrect Date Format."); + errorRecord.add(error); + } else { + record.setReceiveDate(receiveDate); + } + } + + Cell storageLocationCell = row.getCell(5); + String storageLoc = ExcelUtils.getStringValue(storageLocationCell); + if (!StringUtils.isBlank(storageLoc)) { + if (storageLoc.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Storage Location", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setStorageLocation(storageLoc); + } + + Cell physicalAwardCell = row.getCell(6); + String physicalAward = ExcelUtils.getStringValue(physicalAwardCell); + if (!StringUtils.isBlank(physicalAward)) { + if (physicalAward.length() > 100) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Physical Award", + "Value must not exceed 100 character."); + errorRecord.add(error); + } + record.setPhysicalAward(physicalAward); + } + + Cell categoryCell = row.getCell(7); + if (categoryCell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(categoryCell))) { + String categoryString = ExcelUtils.getStringValue(categoryCell); + if (categoryString != null) { + Long id = (long) categoryService.getIdByName(categoryString); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Category", + categoryString + " doesn't exist"); + errorRecord.add(error); + } else { + record.setCategoryId(id); + } + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Category", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell officerCell = row.getCell(8); + String officeName = ExcelUtils.getStringValue(officerCell); + if (!StringUtils.isBlank(officeName)) { + record.setResponsibleOfficer(officeName); + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Responsiable Officer", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell remarkCell = row.getCell(9); + String remarks = ExcelUtils.getStringValue(remarkCell); + if (!StringUtils.isBlank(remarks)) { + if (remarks.length() > 500) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Remarks", + "Value must not exceed 500 character."); + errorRecord.add(error); + } + record.setRemarks(remarks); + } + + Cell subDiv1Cell = row.getCell(10); + if (subDiv1Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv1Cell))) { + String subDiv1 = ExcelUtils.getStringValue(subDiv1Cell); + if (subDiv1 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv1); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Sub-Division 1", + subDiv1 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } else { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Sub-Division 1", + "This column is mandatory."); + errorRecord.add(error); + } + + Cell subDiv2Cell = row.getCell(11); + if (subDiv2Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv2Cell))) { + String subDiv2 = ExcelUtils.getStringValue(subDiv2Cell); + if (subDiv2 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv2); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Sub-Division 2", + subDiv2 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv3Cell = row.getCell(12); + if (subDiv3Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv3Cell))) { + String subDiv3 = ExcelUtils.getStringValue(subDiv3Cell); + if (subDiv3 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv3); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Sub-Division 3", + subDiv3 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv4Cell = row.getCell(13); + if (subDiv4Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv4Cell))) { + String subDiv4 = ExcelUtils.getStringValue(subDiv4Cell); + if (subDiv4 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv4); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Sub-Division 4", + subDiv4 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell subDiv5Cell = row.getCell(14); + if (subDiv5Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(subDiv5Cell))) { + String subDiv5 = ExcelUtils.getStringValue(subDiv5Cell); + if (subDiv5 != null) { + Long id = (long) subDivisionService.getIdByName(subDiv5); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Sub-Division 5", + subDiv5 + " doesn't exist"); + errorRecord.add(error); + } else { + subDivIdList.add(id); + } + } + } + + Cell channel1Cell = row.getCell(15); + if (channel1Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(channel1Cell))) { + String channel1 = ExcelUtils.getStringValue(channel1Cell); + if (channel1 != null) { + Long id = (long) promotionChannelService.getIdByName(channel1); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Promotion Channel 1", + channel1 + " doesn't exist"); + errorRecord.add(error); + } else { + channelIdList.add(id); + } + } + } + + Cell channel2Cell = row.getCell(16); + if (channel2Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(channel2Cell))) { + String channel2 = ExcelUtils.getStringValue(channel2Cell); + if (channel2 != null) { + Long id = (long) promotionChannelService.getIdByName(channel2); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Promotion Channel 2", + channel2 + " doesn't exist"); + errorRecord.add(error); + } else { + channelIdList.add(id); + } + } + } + + Cell channel3Cell = row.getCell(17); + if (channel3Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(channel3Cell))) { + String channel3 = ExcelUtils.getStringValue(channel3Cell); + if (channel3 != null) { + Long id = (long) promotionChannelService.getIdByName(channel3); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Promotion Channel 3", + channel3 + " doesn't exist"); + errorRecord.add(error); + } else { + channelIdList.add(id); + } + } + } + + Cell channel4Cell = row.getCell(18); + if (channel4Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(channel4Cell))) { + String channel4 = ExcelUtils.getStringValue(channel4Cell); + if (channel4 != null) { + Long id = (long) promotionChannelService.getIdByName(channel4); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Promotion Channel 4", + channel4 + " doesn't exist"); + errorRecord.add(error); + } else { + channelIdList.add(id); + } + } + } + + Cell channel5Cell = row.getCell(19); + if (channel5Cell != null && StringUtils.isNotBlank(ExcelUtils.getStringValue(channel5Cell))) { + String channel5 = ExcelUtils.getStringValue(channel5Cell); + if (channel5 != null) { + Long id = (long) promotionChannelService.getIdByName(channel5); + if (id == 0) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "Promotion Channel 5", + channel5 + " doesn't exist"); + errorRecord.add(error); + } else { + channelIdList.add(id); + } + } + } + + Cell externalLinkCell = row.getCell(20); + String externalLinkString = ExcelUtils.getStringValue(externalLinkCell); + if (!StringUtils.isBlank(externalLinkString)) { + if (externalLinkString.length() > 255) { + ImportErrorRecord error = new ImportErrorRecord( + (long) errorRecord.size(), + rowNumber, + "Award", + "External Link", + "Value must not exceed 255 character."); + errorRecord.add(error); + } + record.setTempExternalLink(externalLinkString); + } + + record.setTempSubDivIdList(subDivIdList); + record.setTempChannelIdList(channelIdList); + return record; + } + + private LocalDate getDateValueFromCell(Cell cell) { + // Handle date formatted numeric cell, if needed + if (cell != null) { + switch (cell.getCellType()) { + case NUMERIC: + if (DateUtil.isCellDateFormatted(cell)) { + LocalDate dateValue = cell.getLocalDateTimeCellValue().toLocalDate(); + return dateValue; + } else { + return null; + } + default: + return null; + } + } else { + return null; + } + } + + private String getStringValueFromCell(Cell cell) { + String cellValue = ""; + if (cell != null) { + switch (cell.getCellType()) { + case STRING: + cellValue = cell.getStringCellValue(); + break; + case NUMERIC: + double numericValue = cell.getNumericCellValue(); + cellValue = String.valueOf(numericValue); + break; + default: + break; + } + } + return cellValue; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/EventService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/EventService.java new file mode 100644 index 0000000..96b0705 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/EventService.java @@ -0,0 +1,1016 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.entity.Event; +import com.ffii.lioner.modules.lioner.entity.EventRepository; +import com.ffii.lioner.modules.lioner.entity.ImpEvent; +import com.ffii.lioner.modules.lioner.req.UpdateEventReq; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.SubDivision; +import com.ffii.core.exception.UnprocessableEntityException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; +import com.ffii.core.utils.JsonUtils; + +import jakarta.persistence.Table; + +@Service +public class EventService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + public EventService(JdbcDao jdbcDao, EventRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " e.id, " + + " e.created, " + + " e.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " e.version, " + + " e.modified, " + + " e.modifiedBy AS modifiedById, " + + " e.deleted, " + + " u2.name AS modifiedBy, " + + " e.name, " + + " e.nameCht, " + + " e.description, " + + " e.region, " + + " e.organization, " + + " e.eventType, " + + " e.frequency, " + + " e.series, " + + " e.startDate, " + + " e.eventFrom, " + + " e.eventTo, " + + " e.applicationDeadline, " + + " e.nextApplicationDate, " + + " e.announcementDate, " + + " e.awardDate, " + + " e.reminderFlag, " + + " e.reminderThreshold, " + + " e.reminderInterval, " + + " e.reminderLimit " + + " FROM event e " + + " LEFT JOIN `user` u1 ON u1.id = e.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = e.modifiedBy " + + " WHERE e.id = :id " + ); + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public Event saveImportRecord(ImpEvent req) { + Event instance; + if (req.getId() > 0) { + instance = find(req.getId()).get(); + } else { + instance = new Event(); + } + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getEventDivisionName(input)); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req, instance); + logger.info(instance.toString()); + instance = save(instance); + Long eventId = instance.getId(); + updateSubDivision(req.getTempIdList(),new ArrayList<>(),eventId); + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("subDivision", this.getEventDivisionName(input)); + newValueObject = logData; + } + + if (auditLogService.compareMaps(newValueObject, oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject, newValueObject).size() != 0) { + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + + return instance; + } + + public Map saveOrUpdate(UpdateEventReq req) { + Event instance; + List onUsingIdList = new ArrayList(); + if(req.getId()>0){ + instance = find(req.getId()).get(); + onUsingIdList = this.getSelectedSubDivisionList(req.getId()); + } + else{ + instance = new Event(); + } + + List subDivisionNameList = new ArrayList(); + for (SubDivision subDivision : onUsingIdList) { + for (Long deleteId: req.getSubDivisionRemoveIds()) { + if(deleteId == subDivision.getId()){ + subDivisionNameList.add(subDivision.getName()); + } + } + } + + if(subDivisionNameList.size() > 0){ + String temp = ""; + for (String subDivisionName : subDivisionNameList) { + temp = temp + subDivisionName + "\n"; + } + throw new UnprocessableEntityException( + "Below Sub-Division already exist in application, cannot be deleted: \n" + + temp + ); + } + + BeanUtils.copyProperties(req,instance); + if(!instance.getReminderFlag()){ + instance.setReminderThreshold((long) 0); + instance.setReminderInterval((long) 0); + instance.setReminderLimit((long) 0); + } + + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getEventDivisionName(input)); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + + instance = save(instance); + Long eventId = instance.getId(); + updateSubDivision(req.getSubDivisionIds(),req.getSubDivisionRemoveIds(),eventId); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getEventDivisionName(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + + return Map.of( + "id", instance.getId() + ); + } + + public void markDeleteWithAuditLog(Event instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getEventDivisionName(input)); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + this.markDelete(instance.getId()); + this.deleteEventDivision(instance.getId().intValue()); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", this.getEventDivisionName(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public void deleteEventDivision(Integer eventId) { + List subDivisionIdList = this.getEventDivision(Map.of("id", eventId)); + List> deleteDivisionIds = subDivisionIdList.stream() + .map(subDivisionId -> Map.of("eventId", eventId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + + if (!deleteDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE event_sub_division esd" + + " SET esd.deleted = TRUE" + + " WHERE eventId = :eventId AND subDivisionId = :subDivisionId", + deleteDivisionIds); + } + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT e.id, " + + " e.name AS name, " + + " e.applicationDeadline, " + + " e.startDate, " + + " e.eventFrom " + + " FROM event e " + + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + + " LEFT JOIN sub_division sd ON esd.subDivisionId = sd.id " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN `user` u ON u.id = e.createdBy " + + " WHERE e.deleted = FALSE " + + " AND esd.deleted = FALSE " + + ); + + if (args != null) { + if (args.containsKey("eventName")) + sql.append(" AND e.name LIKE :eventName "); + if (args.containsKey("description")) + sql.append(" AND e.description LIKE :description "); + if (args.containsKey("organization")) + sql.append(" AND e.organization LIKE :organization "); + if (args.containsKey("fromDate")) + sql.append(" AND DATE(e.startDate) >= :fromDate"); + if (args.containsKey("toDate")) + sql.append(" AND DATE(e.startDate) < :toDate"); + if (args.containsKey("region")) + sql.append(" AND e.region LIKE :region "); + if (args.containsKey("type")) + sql.append(" AND e.eventType LIKE :type "); + if (args.containsKey("divisionIdList")) + sql.append(" AND sd.divisionId IN (:divisionIdList) "); + if (args.containsKey("subDivisionIdList")) + sql.append(" AND sd.id IN (:subDivisionIdList) "); + if (args.containsKey("keyword")){ + sql.append(" AND ( e.createdBy LIKE :keyword "); + sql.append(" OR u.username LIKE :keyword "); + sql.append(" OR u.fullname LIKE :keyword "); + sql.append(" OR u.email LIKE :keyword "); + sql.append(" OR sd.name LIKE :keyword "); + sql.append(" OR d.name LIKE :keyword "); + sql.append(" OR e.name LIKE :keyword) "); + } + + } + sql.append(" ORDER BY e.startDate desc "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listReport(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT e.*, " + + " (SELECT " + + " GROUP_CONCAT( DISTINCT d2.name) " + + " FROM event e2 " + + " LEFT JOIN event_sub_division esd2 ON e2.id = esd2.eventId " + + " LEFT JOIN sub_division sd2 ON esd2.subDivisionId = sd2.id " + + " LEFT JOIN division d2 ON d2.id = sd2.divisionId " + + " WHERE e2.id = e.id " + + " AND esd2.deleted = false " + + " GROUP BY e2.id " + + " ) AS divisionList, " + + " u.name as username " + + " FROM event e " + + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + + " LEFT JOIN sub_division sd ON esd.subDivisionId = sd.id " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN `user` u ON u.id = e.createdBy " + + " WHERE e.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey("eventName")) + sql.append(" AND e.name LIKE :eventName "); + if (args.containsKey("description")) + sql.append(" AND e.description LIKE :description "); + if (args.containsKey("organization")) + sql.append(" AND e.organization LIKE :organization "); + if (args.containsKey("fromDate")) + sql.append(" AND DATE(e.startDate) >= :fromDate"); + if (args.containsKey("toDate")) + sql.append(" AND DATE(e.startDate) < :toDate"); + if (args.containsKey("region")) + sql.append(" AND e.region LIKE :region "); + if (args.containsKey("type")) + sql.append(" AND e.eventType LIKE :type "); + if (args.containsKey("divisionIdList")) + sql.append(" AND sd.divisionId IN (:divisionIdList) "); + if (args.containsKey("subDivisionIdList")) + sql.append(" AND sd.id IN (:subDivisionIdList) "); + if (args.containsKey("keyword")){ + sql.append(" AND ( e.createdBy LIKE :keyword "); + sql.append(" OR u.username LIKE :keyword "); + sql.append(" OR u.fullname LIKE :keyword "); + sql.append(" OR u.email LIKE :keyword "); + sql.append(" OR sd.name LIKE :keyword "); + sql.append(" OR d.name LIKE :keyword "); + sql.append(" OR e.name LIKE :keyword) "); + } + + } + sql.append(" ORDER BY e.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listByEvent(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT a.id, " + + " a.name AS applicationName, " + + " a.responsibleOfficer, " + + " (SELECT " + + " GROUP_CONCAT(t.name SEPARATOR ', ') " + + " FROM application_tag at2 " + + " LEFT JOIN tag t ON at2.tagId = t.id " + + " WHERE at2.deleted = FALSE " + + " AND at2.applicationId = a.id) AS tags, " + + " (SELECT " + + " GROUP_CONCAT(sd.name SEPARATOR ', ') " + + " FROM application_sub_division asd2 " + + " LEFT JOIN sub_division sd ON sd.id = asd2.subDivisionId " + + " WHERE asd2.deleted = FALSE " + + " AND asd2.applicationId = a.id) AS subDivisions " + + " FROM application a " + + " JOIN application_sub_division asd ON asd.applicationId = a.id " + + " LEFT JOIN sub_division sd3 ON asd.subDivisionId = sd3.id" + + " LEFT JOIN division d ON d.id = sd3.divisionId " + + " WHERE a.deleted = FALSE " + + " AND a.eventId = :eventId " + ); + if (args.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + sql.append(" ORDER BY a.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List getEventByWithinYear(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT e.* " + + " FROM event e " + + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + + " WHERE e.deleted = FALSE " + + " AND e.reminderFlag = TRUE " + + " AND e.startDate >= :yearRange " + ); + + if (args != null) { + if (args.containsKey("announcementDate")) { + sql.append(" AND e.announcementDate = :announcementDate"); + } + } + + sql.append(" ORDER BY e.id"); + + return jdbcDao.queryForList(sql.toString(), args, Event.class); + } + + public List getEventDivision(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " esd.subDivisionId " + + " FROM event_sub_division esd " + + " LEFT JOIN sub_division sd ON sd.id = esd.subDivisionId " + + " WHERE esd.deleted = FALSE " + + " AND sd.deleted = FALSE " + + " AND esd.eventId = :id " + ); + + sql.append(" ORDER BY esd.id"); + + return jdbcDao.queryForInts(sql.toString(), args); + } + + public List getEventDivisionName(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.name " + + " FROM event_sub_division esd " + + " JOIN sub_division sd ON esd.subDivisionId = sd.id " + + " WHERE esd.deleted = FALSE " + + " AND esd.eventId = :id " + ); + + sql.append(" ORDER BY esd.id"); + + return jdbcDao.queryForStrings(sql.toString(), args); + } + + public List> getEventDivisionAllData(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " esd.* " + + " FROM event_sub_division esd " + + " WHERE esd.deleted = FALSE " + + " AND esd.eventId = :id " + ); + + sql.append(" ORDER BY esd.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT " + + " e.id, " + + " e.id as `key`, " + + " e.name as label " + + " FROM event e " + + " LEFT JOIN event_sub_division esd ON esd.eventId = e.id " + + " WHERE e.deleted = FALSE " + ); + if (args != null) { + if (args.containsKey("userSubDivisionId")) + sql.append(" AND esd.subDivisionId = :userSubDivisionId "); + + } + + sql.append(" ORDER BY e.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + private void updateSubDivision(List newIds, List removeIds, Long eventId){ + List> newDivisionIds = newIds.stream() + .map(subDivisionId -> Map.of("eventId", eventId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + List> deleteDivisionIds = removeIds.stream() + .map(subDivisionId -> Map.of("eventId", eventId, "subDivisionId", subDivisionId)) + .collect(Collectors.toList()); + if (!newDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO event_sub_division (eventId, subDivisionId, newReminderCount, suppressedNewReminder, oldReminderCount, suppressedOldReminder )" + + " VALUES (:eventId, :subDivisionId, 0, 0, 0, 0)", + newDivisionIds); + } + if (!deleteDivisionIds.isEmpty()) { + jdbcDao.batchUpdate( + "UPDATE event_sub_division esd" + + " SET esd.deleted = TRUE" + + " WHERE eventId = :eventId AND subDivisionId = :subDivisionId", + deleteDivisionIds); + } + + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(e.id) " + + " FROM event e " + + " WHERE e.deleted =FALSE " + + " AND e.name = :name " + + " AND e.id != :id " + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + + public Boolean hasApplicationRecord(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " COUNT(*) " + + " FROM event e " + + " JOIN application a ON e.id = a.eventId " + + " WHERE e.deleted = FALSE " + + " AND e.id = :id " + ); + + sql.append(" ORDER BY e.id"); + + return jdbcDao.queryForBoolean(sql.toString(), args); + } + + public Map isReminderOvertime(Long id){ + Event event = find(id).get(); + + if(event.getReminderFlag() == false){ + return Map.of("warning",false); + } + else{ + LocalDate firstIssueDate = null; + Boolean hasApplicationRecord = hasApplicationRecord(Map.of("id",event.getId())); + + if(!hasApplicationRecord){ + firstIssueDate = event.getStartDate().minusDays(event.getReminderThreshold()); + } + else{ + if(event.getNextApplicationDate() != null){ + firstIssueDate = event.getNextApplicationDate().minusDays(event.getReminderThreshold()); + } + else{ + return Map.of( + "warning", false + ); + } + } + Integer limit = event.getReminderLimit().intValue(); + Integer interval = event.getReminderInterval().intValue(); + List reminderDateSchedule = new ArrayList(); + reminderDateSchedule.add(firstIssueDate); + for(Integer i=1; i< limit+1 ; i++){ + reminderDateSchedule.add(firstIssueDate.plusDays(i*interval)); + } + logger.info(reminderDateSchedule); + LocalDate lastLocalDate = null; + for (LocalDate localDate : reminderDateSchedule) { + if(localDate.compareTo(event.getApplicationDeadline()) > 0){ + //reminder overflow + return Map.of( + "warning", true, + "lastNotiDate", lastLocalDate + ); + } + else{ + lastLocalDate = localDate; + } + } + return Map.of( + "warning", false + ); + } + } + + public List> getTop6(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ROW_NUMBER() OVER (ORDER BY SUM(IF(temp.awardId IS NOT NULL, 1, 0)) DESC) as `rank`, " + + " temp.eventId, " + + " temp.eventName, " + + " SUM(IF(temp.awardId IS NOT NULL, 1, 0)) as awardCount " + + " FROM ( " + + " (SELECT " + + " e.id as eventId, " + + " e.name as eventName, " + + " a2.id as awardId " + + " FROM event e " + + " LEFT JOIN application a ON a.eventId = e.id " + + " LEFT JOIN award a2 ON a2.applicationId = a.id " + + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId AND asd.deleted = false " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " WHERE e.deleted = FALSE " + + " AND a.deleted = FALSE " + + " AND a2.deleted = FALSE " + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + sql.append( + " AND YEAR(e.startDate) = :year " + + " GROUP BY e.id, e.name, a2.id) " + + " UNION " + + " (SELECT " + + " e.id as eventId, " + + " e.name as eventName, " + + " null " + + " FROM event e " + + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId AND esd.deleted = false " + + " LEFT JOIN sub_division sd ON sd.id = esd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " WHERE e.deleted = FALSE " + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + + sql.append( + " AND YEAR(e.startDate) = :year " + + " GROUP BY e.id, e.name) " + + " ) temp " + + " GROUP BY temp.eventId, temp.eventName " + + " ORDER BY SUM(IF(temp.awardId IS NOT NULL, 1, 0)) DESC " + + " LIMIT 6; " + ); + + return jdbcDao.queryForList(sql.toString(), req); + } + + public List> getDashboardCombo(){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT year(e.startDate) AS id, " + + " year(e.startDate) AS label " + + " FROM event e " + + " WHERE e.deleted = FALSE " + + " ORDER BY id DESC " + ); + + return jdbcDao.queryForList(sql.toString()); + } + + public Map getYearEventCount(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " COUNT(temp.eventId) AS count " + + " FROM( " + + " SELECT " + + " e.id AS eventId " + + " FROM event e " + + " LEFT JOIN event_sub_division esd ON e.id = esd.eventId " + + " LEFT JOIN sub_division sd ON sd.id =esd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " WHERE e.deleted = FALSE " + + " AND esd.deleted = false " + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + sql.append(" AND YEAR(e.startDate) = :year"); + sql.append(" GROUP BY e.id "); + sql.append(" ) temp; "); + + return jdbcDao.queryForMap(sql.toString(),req).get(); + } + + public Map getYearAwardCount(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " COUNT(temp.awardId) AS count " + + " FROM( " + + " SELECT " + + " a2.id AS awardId " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId AND asd.deleted = false " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " WHERE e.deleted = FALSE " + + " AND a.deleted = FALSE " + + " AND a2.deleted = FALSE " + + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + sql.append(" AND YEAR(e.awardDate) = :year"); + sql.append(" GROUP BY a2.id, a2.id "); + sql.append(" ) temp; "); + + return jdbcDao.queryForMap(sql.toString(),req).get(); + } + + public List> getDummyDivisionAwardSummary(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ROW_NUMBER() OVER (ORDER BY awardCount DESC) AS `rank`, " + + " name, " + + " awardCount " + + " FROM ( " + + " SELECT " + + " queryResult.name, " + + " COUNT(queryResult.awardName) AS awardCount " + + " FROM ( " + + " SELECT " + + " DISTINCT " + + " d.id, " + + " d.name, " + + " e.name AS eventName," + + " a.name AS application," + + " a2.name AS awardName " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " JOIN category c ON a2.categoryId = c.id " + + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " LEFT JOIN division d ON sd.divisionId = d.id " + + " WHERE d.deleted = FALSE " + + " AND YEAR(e.startDate) = :year " + + " AND asd.deleted = false " + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND sd.id = :userSubDivisionId "); + if (req.containsKey("branch")) sql.append(" AND d.branch LIKE :branch "); + + sql.append( + " AND sd.deleted = FALSE " + + " ) AS queryResult " + + " GROUP BY queryResult.id " + + " UNION ALL " + + " SELECT " + + " dummy.name, " + + " 0 AS awardCount " + + " FROM ( " + + " SELECT 1 AS id, 'z' AS name " + + " UNION ALL SELECT 2, 'z' " + + " ) AS dummy " + + " ) AS rankedData " + + " ORDER BY name ASC " + + " LIMIT 10; " + ); + + return jdbcDao.queryForList(sql.toString(),req); + } + + + public List> getDivisionAwardSummaryUntil10(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ROW_NUMBER() OVER (ORDER BY awardCount DESC) AS `rank`, " + + " name, " + + " awardCount " + + " FROM ( " + + " SELECT " + + " queryResult.name, " + + " COUNT(queryResult.awardName) AS awardCount " + + " FROM ( " + + " SELECT " + + " DISTINCT " + + " d.id, " + + " d.name, " + + " e.name AS eventName," + + " a.name AS application," + + " a2.name AS awardName " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " JOIN category c ON a2.categoryId = c.id " + + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " LEFT JOIN division d ON sd.divisionId = d.id " + + " WHERE d.deleted = FALSE " + + " AND YEAR(e.startDate) = :year " + + " AND sd.deleted = FALSE " + + " AND d.branch LIKE :branch " + + " AND asd.deleted = false " + + " ) AS queryResult " + + " GROUP BY queryResult.id " + + " UNION ALL " + + " SELECT " + + " dummy.name, " + + " 0 AS awardCount " + + " FROM ( " + + " SELECT 1 AS id, 'z' AS name " + + " UNION ALL SELECT 2, 'z' " + + " UNION ALL SELECT 3, 'z' " + + " UNION ALL SELECT 4, 'z' " + + " UNION ALL SELECT 5, 'z' " + + " UNION ALL SELECT 6, 'z' " + + " UNION ALL SELECT 7, 'z' " + + " UNION ALL SELECT 8, 'z' " + + " UNION ALL SELECT 9, 'z' " + + " UNION ALL SELECT 10, 'z' " + + " ) AS dummy " + + " ) AS rankedData " + + " ORDER BY name ASC " + + " LIMIT 10; " + ); + + return jdbcDao.queryForList(sql.toString(),req); + } + + public List> getDivisionAwardSummary(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ROW_NUMBER() OVER (ORDER BY awardCount DESC) AS `rank`, " + + " name, " + + " awardCount " + + " FROM ( " + + " SELECT " + + " queryResult.name, " + + " COUNT(queryResult.awardName) AS awardCount " + + " FROM ( " + + " SELECT " + + " DISTINCT " + + " d.id, " + + " d.name, " + + " e.name AS eventName, " + + " a.name AS application, " + + " a2.name AS awardName " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " JOIN category c ON a2.categoryId = c.id " + + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " LEFT JOIN division d ON sd.divisionId = d.id " + + " WHERE d.deleted = FALSE " + + " AND YEAR(e.startDate) = :year " + + " AND sd.deleted = FALSE " + + " AND d.branch LIKE :branch " + + " AND asd.deleted = false " + + " ) AS queryResult " + + " GROUP BY queryResult.id " + + " ) AS rankedData " + + " ORDER BY name ASC " + ); + + return jdbcDao.queryForList(sql.toString(),req); + } + + public List> getTagAwardSummary(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " temp.id, " + + " temp.name, " + + " COUNT(temp.awardId) AS awardCount " + + " FROM( " + + " SELECT " + + " t.id, " + + " t.name, " + + " a2.id AS awardId " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN application_tag at2 ON at2.applicationId = a.id " + + " JOIN tag t ON t.id = at2.tagId " + + " JOIN award a2 ON a2.applicationId = a.id " + + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId AND asd.deleted = FALSE " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " WHERE e.deleted = FALSE " + + " AND a.deleted = FALSE " + + " AND a2.deleted = FALSE " + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + sql.append(" AND YEAR(e.startDate) = :year "); + sql.append(" GROUP BY t.id, a2.id "); + sql.append(" ) temp "); + sql.append(" GROUP BY temp.id "); + sql.append(" ORDER BY COUNT(temp.id) DESC"); + sql.append(" LIMIT 6 ;"); + + return jdbcDao.queryForList(sql.toString(),req); + } + + public List> getCategoryAwardSummary(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " temp.id, " + + " temp.name, " + + " COUNT(temp.awardId) AS awardCount " + + " FROM( " + + " SELECT " + + " c.id, " + + " c.name, " + + " a2.id AS awardId " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " LEFT JOIN award_sub_division asd ON a2.id = asd.awardId " + + " LEFT JOIN sub_division sd ON sd.id = asd.subDivisionId " + + " LEFT JOIN division d ON d.id = sd.divisionId " + + " LEFT JOIN category c ON a2.categoryId = c.id " + + " WHERE e.deleted = FALSE " + + " AND a.deleted = FALSE " + + " AND a2.deleted = FALSE " + + " AND asd.deleted = false " + + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND d.id = (SELECT sd2.divisionId FROM sub_division sd2 WHERE sd2.id = :userSubDivisionId)"); + sql.append(" AND YEAR(e.startDate) = :year "); + sql.append(" GROUP BY c.id, a2.id "); + sql.append(" ) temp "); + sql.append(" GROUP BY temp.id "); + sql.append(" ORDER BY COUNT(temp.id) DESC"); + sql.append(" LIMIT 6 ;"); + + return jdbcDao.queryForList(sql.toString(),req); + } + + public List> getAwardSummaryReportPage1(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " name, " + + " count " + + " FROM ( " + + " SELECT " + + " queryResult.name, " + + " COUNT(queryResult.awardName) AS count " + + " FROM ( " + + " SELECT " + + " DISTINCT " + + " d.id, " + + " d.name, " + + " e.name AS eventName," + + " a.name AS application," + + " a2.name AS awardName " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " JOIN category c ON a2.categoryId = c.id " + + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id " + + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " LEFT JOIN division d ON sd.divisionId = d.id " + + " WHERE d.deleted = FALSE " + + " AND YEAR(e.startDate) = :year " + + " AND asd.deleted = false " + ); + + if (req.containsKey("userSubDivisionId")) sql.append(" AND sd.id = :userSubDivisionId "); + if (req.containsKey("branch")) sql.append(" AND d.branch LIKE :branch "); + + sql.append( + " AND sd.deleted = FALSE " + + " ) AS queryResult " + + " GROUP BY queryResult.id " + + " ) AS rankedData " + + " ORDER BY count DESC " + ); + + return jdbcDao.queryForList(sql.toString(),req); + } + + public List> getAwardSummaryReportPage2(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT " + + " d.name, " + + " (SELECT GROUP_CONCAT( DISTINCT sd.name SEPARATOR ', ') " + + " FROM award_sub_division asd2 " + + " LEFT JOIN sub_division sd ON sd.id = asd2.subDivisionId " + + " LEFT JOIN division d2 ON sd.divisionId = d2.id " + + " WHERE asd2.deleted = FALSE " + + " AND d2.id = d.id " + + " ) AS subDivision, " + + " e.name AS eventName, " + + " a.name AS applicationName, " + + " a2.name AS awardName, " + + " a2.receiveDate, " + + " c.name AS category , " + + " a2.responsibleOfficer " + + " FROM event e " + + " JOIN application a ON a.eventId = e.id " + + " JOIN award a2 ON a2.applicationId = a.id " + + " JOIN category c ON a2.categoryId = c.id " + + " LEFT JOIN award_sub_division asd ON asd.awardId = a2.id AND asd.deleted = FALSE " + + " LEFT JOIN sub_division sd ON asd.subDivisionId =sd.id " + + " LEFT JOIN division d ON sd.divisionId = d.id " + + " WHERE a.deleted = FALSE " + + " AND a.deleted = FALSE " + + " AND a2.deleted = FALSE " + + " AND YEAR(e.startDate) = :year " + ); + if (req.containsKey("userSubDivisionId")) sql.append(" AND sd.id = :userSubDivisionId "); + if (req.containsKey("branch")) sql.append(" AND d.branch LIKE :branch "); + + return jdbcDao.queryForList(sql.toString(),req); + } + + public List getSelectedSubDivisionList(Long eventId) { + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT " + + " sd.* " + + " FROM application a " + + " JOIN application_sub_division asd ON a.id = asd.applicationId " + + " LEFT JOIN sub_division sd ON asd.subDivisionId = sd.id " + + " WHERE a.eventId = :eventId " + ); + + return jdbcDao.queryForList(sql.toString(), Map.of("eventId", eventId), SubDivision.class); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " e.id " + + " FROM event e " + + " WHERE e.deleted = FALSE" + + " AND e.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/FileBlobService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/FileBlobService.java new file mode 100644 index 0000000..4a03b4a --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/FileBlobService.java @@ -0,0 +1,46 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.entity.FileBlob; +import com.ffii.lioner.modules.lioner.entity.FileBlobRepository; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; + +@Service +public class FileBlobService extends AbstractBaseEntityService { + + public FileBlobService(JdbcDao jdbcDao, FileBlobRepository repository) { + super(jdbcDao, repository); + } + + public Long saveOrUpdate(Long fileId, InputStream inputStream) { + String sql = "INSERT INTO file_blob (fileId, bytes) VALUES (:fileId, :inputStream)"; + long rows = 0; + try { + rows = jdbcDao.executeUpdateAndReturnId(sql, Map.of("fileId", fileId, "inputStream", inputStream)); + inputStream.close(); + } catch (Exception e) { + logger.info(ExceptionUtils.getStackTrace(e)); + } + return rows; + } + + public Long saveByRepository(FileBlob obj) { + FileBlob temp = repository.save(obj); + return temp.getId(); + } + + public Long findIdByFileId(Long fileId) { + Map args = Map.of("fileId", fileId); + return (long) jdbcDao.queryForInt("SELECT fb.id " + + "FROM file_blob fb where fb.fileId = :fileId ", args); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/FileRefService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/FileRefService.java new file mode 100644 index 0000000..64aedbf --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/FileRefService.java @@ -0,0 +1,114 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.entity.FileRef; +import com.ffii.lioner.modules.lioner.entity.FileRefRepository; +import com.ffii.lioner.modules.lioner.req.UpdateExtRefReq; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.BeanUtils; + +@Service +public class FileRefService extends AbstractBaseEntityService { + + public FileRefService(JdbcDao jdbcDao, FileRefRepository repository) { + super(jdbcDao, repository); + } + + public Long saveOrUpdate(FileRef obj) { + FileRef instance; + if(obj.getId()!= null){ + instance = find(obj.getId()).get(); + } + else{ + instance = new FileRef(); + } + BeanUtils.copyProperties(obj,instance); + instance = save(instance); + + return instance.getId(); + } + + public Long saveExternal(UpdateExtRefReq req, String refType) { + return saveExternal(req, refType, null); + } + + public Long saveExternal(UpdateExtRefReq req, String refType, LocalDateTime importDate) { + FileRef instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new FileRef(); + } + + BeanUtils.copyProperties(req,instance); + instance.setRefType(refType); + if(importDate != null) instance.setImportDate(importDate); + instance = save(instance); + return instance.getId(); + } + + public List> listExternal(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " fr.refType, " + + " fr.remarks, " + + " fr.id " + + " FROM file_ref fr " + + " LEFT JOIN file f ON f.id = fr.fileId " + + " WHERE fr.deleted = FALSE " + + " AND f.id IS NULL"); + + if (args != null) { + if (args.containsKey("refId")) + sql.append(" AND fr.refId = :refId "); + if (args.containsKey("refType")) + sql.append(" AND fr.refType = :refType"); + } + + sql.append(" ORDER BY fr.modified "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public Optional findByFileId(Long fileId) { + Map args = Map.of("fileId", fileId); + return jdbcDao.queryForEntity("SELECT * "+ + "FROM file_ref fr where fr.fileId = :fileId " + , args, FileRef.class); + } + + public BigDecimal getFileStoredSize(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " COALESCE(SUM(f.filesize)/1024/1024,0) AS refSize " + + " FROM file_ref fr " + + " JOIN file f ON fr.fileId = f.id " + + " WHERE refId = :refId " + + " AND refType = :refType " + ); + + return jdbcDao.queryForDecimal(sql.toString(), args); + } + + public Integer getVideoExist(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " COALESCE(COUNT(f.id),0) AS videoCount " + + " FROM file_ref fr " + + " JOIN file f ON fr.fileId = f.id " + + " WHERE refId = :refId " + + " AND refType = :refType " + + " AND f.mimetype LIKE \"%video%\" " + ); + + return jdbcDao.queryForInt(sql.toString(), args); + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/FileService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/FileService.java new file mode 100644 index 0000000..6284d70 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/FileService.java @@ -0,0 +1,314 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.sql.Blob; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import javax.imageio.ImageIO; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.xmlbeans.impl.common.IOUtil; +import org.bytedeco.javacv.FFmpegFrameGrabber; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.Java2DFrameConverter; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import com.ffii.lioner.modules.lioner.entity.File; +import com.ffii.lioner.modules.lioner.entity.FileBlob; +import com.ffii.lioner.modules.lioner.entity.FileRef; +import com.ffii.lioner.modules.lioner.entity.FileRepository; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.FileUtils; +import com.ffii.core.utils.StringUtils; + +@Service +public class FileService extends AbstractBaseEntityService { + + private FileBlobService fileBlobService; + private FileRefService fileRefService; + + private static final int NUM_THREADS = 4; // Number of threads to use for parallel processing + private ExecutorService executor; + + public FileService(JdbcDao jdbcDao, FileRepository repository, FileBlobService fileBlobService, + FileRefService fileRefService) { + super(jdbcDao, repository); + this.fileBlobService = fileBlobService; + this.fileRefService = fileRefService; + executor = Executors.newFixedThreadPool(NUM_THREADS); + } + + @Transactional + public Map deleteFile(Long id) { + File file = find(id).get(); + FileRef fileRef = fileRefService.findByFileId(id).get(); + Long fileBlobId = fileBlobService.findIdByFileId(id); + if (fileRef.getThumbnailFileId() != null) { + Long thumbnailId = fileRef.getThumbnailFileId(); + Long fileThumbnailBlobId = fileBlobService.find(thumbnailId).get().getId(); + fileBlobService.delete(fileThumbnailBlobId); + fileRefService.delete(thumbnailId); + } + delete(file.getId()); + fileRefService.delete(fileRef.getId()); + fileBlobService.delete(fileBlobId); + return Map.of( + "fileId", file.getId(), + "fileRefId", fileRef.getId(), + "fileBlobId", fileBlobId); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public Optional findFileByIdAndKey(Long fileId, String skey) { + + Map args = Map.of("fileId", fileId, "skey", skey); + return jdbcDao.queryForEntity("SELECT * " + + "FROM file f where f.id = :fileId " + + "AND f.skey = :skey", args, File.class); + } + + public Pair loadFile(Long id, String skey, String filename) { + // String data = id + skey + filename + FilenameUtils.getExtension(filename); + File file = findFileByIdAndKey(id, skey).get(); + byte[] fileByte = getfileBlob(id); + + return ImmutablePair.of(file, fileByte); + } + + public File loadFileOnly(Long id, String skey, String filename) { + // String data = id + skey + filename + FilenameUtils.getExtension(filename); + File file = findFileByIdAndKey(id, skey).get(); + return file; + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + public Long updateFileDisplay(Long fileId, MultipartFile file, String remarks) throws IOException { + // String data = id + skey + filename + FilenameUtils.getExtension(filename); + FileRef fileRef = fileRefService.findByFileId(fileId).get(); + fileRef.setRemarks(remarks); + + if (file != null) { + FileBlob thumbnail = fileBlobService.find(fileRef.getThumbnailFileId()).get(); + thumbnail.setBytes(file.getBytes()); + fileBlobService.save(thumbnail); + } + fileRefService.saveOrUpdate(fileRef); + + return fileRef.getId(); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public Optional findFileBlobByFileId(Long fileId) { + Map args = Map.of("fileId", fileId); + + return jdbcDao.queryForEntity("SELECT * " + + "FROM file_blob fb where fb.fileId = :fileId", args, FileBlob.class); + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public byte[] getfileBlob(Long fileId) { + Map args = Map.of("fileId", fileId); + + try { + Blob blob = jdbcDao + .queryForBlob("SELECT bytes FROM file_blob fb where fb.fileId = :fileId LIMIT 1", args); + int blobLength = (int) blob.length(); + byte[] bytes = blob.getBytes(1, blobLength); + blob.free(); + return bytes; + + } catch (Exception e) { + logger.info(ExceptionUtils.getStackTrace(e)); + } + + return null; + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) + public byte[] getfileBlobById(Long id) { + Map args = Map.of("id", id); + + try { + Blob blob = jdbcDao + .queryForBlob("SELECT bytes FROM file_blob fb where fb.id = :id LIMIT 1", args); + int blobLength = (int) blob.length(); + byte[] bytes = blob.getBytes(1, blobLength); + blob.free(); + return bytes; + + } catch (Exception e) { + logger.info(ExceptionUtils.getStackTrace(e)); + } + + return null; + } + + @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = false) + public Map saveOrUpdate(MultipartFile[] files, Long refId, String refType) throws Exception { + List fileIdList = new ArrayList(); + List blobIdList = new ArrayList(); + List refIdList = new ArrayList(); + + for (MultipartFile multipartFile : files) { + File instance = new File(); + // byte[] compressedData = zipData(multipartFile.getBytes(), + // multipartFile.getOriginalFilename()); + + instance.setFilename(multipartFile.getOriginalFilename()); + instance.setSkey(RandomStringUtils.randomAlphanumeric(16)); + instance.setExtension(StringUtils.substringAfterLast(multipartFile.getOriginalFilename(), ".")); + instance.setMimetype(FileUtils.guessMimetype(instance.getFilename())); + instance.setFilesize(multipartFile.getSize()); + // instance.setFilesize(Long.parseLong(String.valueOf(compressedData.length))); + + instance = save(instance); + Long fileId = instance.getId(); + fileIdList.add(fileId); + // fileBlob.setBytes(compressedData); + Long blobId = fileBlobService.saveOrUpdate(fileId, multipartFile.getInputStream()); + blobIdList.add(blobId); + + FileRef fileRef = new FileRef(); + fileRef.setFileId(fileId); + fileRef.setRefId(refId); + fileRef.setRefType(refType); + + if (instance.getMimetype().equals("video/mpeg") || + instance.getMimetype().equals("video/mp4") || + instance.getMimetype().equals("video/x-matroska") || + instance.getMimetype().equals("video/quicktime") || + instance.getMimetype().equals("video/x-msvideo")) { + + byte[] thumbnailBlob; + if (instance.getMimetype().equals("video/mp4") || instance.getMimetype().equals("video/quicktime")) { + thumbnailBlob = processVideoAndGetThumbnail(multipartFile.getInputStream(), instance.getExtension(), + true); + + } else { + thumbnailBlob = processVideoAndGetThumbnail(multipartFile.getInputStream(), instance.getExtension(), + false); + } + Long thumbnailBlobId = fileBlobService.saveOrUpdate((long) -1, new ByteArrayInputStream(thumbnailBlob)); + fileRef.setThumbnailFileId(thumbnailBlobId); + } + + Long fileRefId = fileRefService.saveOrUpdate(fileRef); + refIdList.add(fileRefId); + } + + return Map.of( + "fileIdList", fileIdList, + "blobIdList", blobIdList, + "refIdList", refIdList); + } + + public List> listFile(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " fr.refType, " + + " fr.remarks, " + + " fr.fileId, " + + " f.id, " + + " f.skey, " + + " f.extension, " + + " f.filename, " + + " f.filesize, " + + " f.mimetype " + + " FROM file_ref fr " + + " LEFT JOIN file f ON f.id = fr.fileId " + + " WHERE fr.deleted = FALSE " + + " AND f.id IS NOT NULL"); + + if (args != null) { + if (args.containsKey("refId")) + sql.append(" AND fr.refId = :refId "); + if (args.containsKey("refType")) + sql.append(" AND fr.refType = :refType"); + } + + sql.append(" ORDER BY fr.modified "); + + return jdbcDao.queryForList(sql.toString(), args); + } + + private byte[] processVideoAndGetThumbnail(InputStream inputStream, String extension, Boolean isMp4) + throws IOException { + + // TODO Temporary to avoid memroy leak using inpustream on FFmpegFrameGrabber #javacv 1.5.9 + FileOutputStream tempOutStream = null; + java.io.File tempFile = null; + byte[] thumbnailBlob = null; + try { + tempFile = java.io.File.createTempFile("temp", "." + extension); + tempOutStream = new FileOutputStream(tempFile); + IOUtils.copy(inputStream, tempOutStream); + + // Create a frame grabber to read the video blob + FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(tempFile); + if (isMp4) { + frameGrabber.setOption("-pix_fmt", "yuv420p"); // Set the pixel format option + } + frameGrabber.start(); + // 5000000 = 5 seconds + frameGrabber.setTimestamp(5000000); + + // Submit the frame grabbing task to the executor service + Future frameFuture = executor.submit(() -> frameGrabber.grabImage()); + + // Wait for the frame grabbing task to complete and get the frame + Frame frame; + frame = frameFuture.get(); + // Grab first frame if video < 5 seconds + if (frame == null) { + frameGrabber.setTimestamp(0); + frameFuture = executor.submit(() -> frameGrabber.grabImage()); + frame = frameFuture.get(); + } + + // Convert the frame to a BufferedImage + Java2DFrameConverter converter = new Java2DFrameConverter(); + BufferedImage thumbnailImage = converter.getBufferedImage(frame); + + // Convert the thumbnail image to a byte array + ByteArrayOutputStream thumbnailOutputStream = new ByteArrayOutputStream(); + ImageIO.write(thumbnailImage, "jpg", thumbnailOutputStream); + thumbnailBlob = thumbnailOutputStream.toByteArray(); + + // Clean up resources + frame.close(); + frameGrabber.close(); + thumbnailImage.flush(); + converter.close(); + thumbnailOutputStream.close(); + } catch (Exception e) { + // Handle any exceptions that occurred during frame grabbing + throw new IOException("Error occurred while grabbing frame", e); + } finally { + tempOutStream.close(); + inputStream.close(); + if (tempFile.exists()) { + tempFile.delete(); + } + } + + return thumbnailBlob; + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/SearchCriteriaTemplateService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/SearchCriteriaTemplateService.java new file mode 100644 index 0000000..d7712c3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/SearchCriteriaTemplateService.java @@ -0,0 +1,195 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.entity.Event; +import com.ffii.lioner.modules.lioner.entity.SearchCriteriaTemplate; +import com.ffii.lioner.modules.lioner.entity.SearchCriteriaTemplateRepository; +import com.ffii.lioner.modules.lioner.req.UpdateSearchCriteriaTemplateReq; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class SearchCriteriaTemplateService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + public SearchCriteriaTemplateService(JdbcDao jdbcDao, SearchCriteriaTemplateRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " sct.id, " + + " sct.created, " + + " sct.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " sct.version, " + + " sct.modified, " + + " sct.modifiedBy AS modifiedById, " + + " sct.deleted, " + + " u2.name AS modifiedBy, " + + " sct.userId, " + + " u.username AS userName, " + + " sct.name, " + + " sct.module, " + + " sct.criteria " + + " FROM search_criteria_template sct " + + " LEFT JOIN `user` u1 ON u1.id = sct.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = sct.modifiedBy " + + " JOIN `user` u ON u.id = sct.userId " + + " WHERE sct.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public Long saveOrUpdate(UpdateSearchCriteriaTemplateReq req) { + SearchCriteriaTemplate instance; + if(req.getId() > 0){ + instance = find(req.getId()).get(); + } + else{ + instance = new SearchCriteriaTemplate(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + BeanUtils.copyProperties(req,instance); + instance = save(instance); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + return instance.getId(); + } + + public void markDeleteWithAuditLog(SearchCriteriaTemplate instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + this.markDelete(instance.getId()); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public List> search(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " sct.id, " + + " sct.module, " + + " sct.name " + + " FROM search_criteria_template sct " + + " WHERE sct.deleted = FALSE " + + " AND sct.userId = :userId" + ); + + if (args != null) { + if (args.containsKey("module")) sql.append(" AND sct.module LIKE :module "); + if (args.containsKey("templateName")) sql.append(" AND sct.name LIKE :templateName "); + } + + sql.append(" ORDER BY sct.module asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> combo(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " sct.id, " + + " sct.id AS `key`, " + + " sct.name as label" + + " FROM search_criteria_template sct " + + " WHERE sct.deleted = FALSE " + + " AND sct.module = :module " + + " AND sct.userId = :userId" + ); + + if (args != null) { + if (args.containsKey("module")) sql.append(" AND sct.module = :module "); + } + + sql.append(" ORDER BY sct.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isTemplateNameTaken(String name, Long userId, Long templateId, String module) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(sct.id) " + + " FROM search_criteria_template sct " + + " WHERE sct.deleted = FALSE " + + " AND sct.name = :name " + + " AND sct.userId = :userId " + + " AND sct.id != :templateId " + + " AND sct.module LIKE :module" + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "userId", userId,"templateId",templateId, "module", module)); + } +} + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/service/TodoReminderService.java b/src/main/java/com/ffii/lioner/modules/lioner/service/TodoReminderService.java new file mode 100644 index 0000000..51624a2 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/service/TodoReminderService.java @@ -0,0 +1,783 @@ +package com.ffii.lioner.modules.lioner.service; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.lioner.entity.Application; +import com.ffii.lioner.modules.lioner.entity.Event; +import com.ffii.lioner.modules.lioner.entity.TodoReminder; +import com.ffii.lioner.modules.lioner.entity.TodoReminderEmailLog; +import com.ffii.lioner.modules.lioner.entity.TodoReminderRepository; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.SettingNames; +import com.ffii.lioner.modules.common.mail.pojo.MailRequest; +import com.ffii.lioner.modules.common.mail.service.MailService; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.Division; +import com.ffii.lioner.modules.master.entity.SubDivision; +import com.ffii.lioner.modules.master.service.DivisionService; +import com.ffii.lioner.modules.master.service.SubDivisionService; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.lioner.modules.user.entity.User; +import com.ffii.lioner.modules.user.service.UserService; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; + +import jakarta.mail.internet.InternetAddress; +import jakarta.persistence.Table; + +@Service +public class TodoReminderService extends AbstractBaseEntityService { + private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + private Integer reminderBefore; + private Integer reminderInterval; + private Integer reminderLimit; + private Integer reminderMaxValue; + private Integer reminderEventWithin; + private EventService eventService; + private MailService mailService; + private UserService userService; + private DivisionService divisionService; + private ApplicationService applicationService; + private SubDivisionService subDivisionService; + private AuditLogService auditLogService; + private SettingsService settingsService; + + @Value("${host.url}") + private String url; + + public TodoReminderService(JdbcDao jdbcDao, TodoReminderRepository repository, SettingsService settingsService, + EventService eventService, MailService mailService, UserService userService, + DivisionService divisionService, SubDivisionService subDivisionService, + ApplicationService applicationService, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.eventService = eventService; + this.mailService = mailService; + this.userService = userService; + this.divisionService = divisionService; + this.auditLogService = auditLogService; + this.subDivisionService = subDivisionService; + this.applicationService = applicationService; + this.settingsService = settingsService; + this.reminderBefore = settingsService.getInt(SettingNames.SYS_REMINDER_BEFORE); + this.reminderInterval = settingsService.getInt(SettingNames.SYS_REMINDER_INTERVAL); + this.reminderLimit = settingsService.getInt(SettingNames.SYS_REMINDER_LIMIT); + this.reminderMaxValue = settingsService.getInt(SettingNames.SYS_REMINDER_LIMIT_MAX); + this.reminderEventWithin = settingsService.getInt(SettingNames.SYS_REMINDER_WITHIN); + } + + @Scheduled(cron = "0 0 6 * * ?") // Runs at 06:00 AM every day + public void generateApplicationReminder() throws UnsupportedEncodingException, ParseException { + logger.info("============= Generate Application Reminder Start ============="); + LocalDateTime range = LocalDateTime.now().minusYears(reminderEventWithin); + + logger.info("Fetch Event From: " + range.toString()); + + List eventList = eventService.getEventByWithinYear(Map.of( + "yearRange", range)); + + logger.info("Fetched Total Number of Event: " + eventList.size()); + + for (Event event : eventList) { + + logger.info("---------------------------------------------"); + logger.info("Checking Event: " + event.getName()); + Boolean isOldEvent = false; + LocalDate firstIssueDate = null; + Boolean hasApplicationRecord = eventService.hasApplicationRecord(Map.of("id", event.getId())); + + + LocalDate newEventFirstIssueDate = event.getStartDate().minusDays(event.getReminderThreshold()); + generateNewEventApplicationReminder(event, newEventFirstIssueDate); + if (hasApplicationRecord) {{ + if (event.getNextApplicationDate() == null) + continue; + isOldEvent = true; + + LocalDate oldEventFirstIssueDate = event.getNextApplicationDate().minusDays(event.getReminderThreshold()); + generateOldEventApplicationReminder(event, oldEventFirstIssueDate); + } + } + } + logger.info("============= Generate Application Reminder End ============="); + } + + public void generateOldEventApplicationReminder(Event event, LocalDate firstIssueDate) { + + logger.info("=> Checking Old Event Application Reminder"); + LocalDate now = LocalDate.now(); + Integer limit = event.getReminderLimit().intValue(); + Integer interval = event.getReminderInterval().intValue(); + + List reminderDateSchedule = new ArrayList(); + for (Integer i = 0; i < limit; i++) { + reminderDateSchedule.add(firstIssueDate.plusDays(i * interval)); + } + + logger.info("Reminder Dates: " + reminderDateSchedule.stream() + .map(date -> date.format(dateFormat)).collect(Collectors.joining(", "))); + + List> eventSubDivision = eventService + .getEventDivisionAllData(Map.of("id", event.getId())); + for (Map subDivision : eventSubDivision) { + Integer eventSubDivisionId = (Integer) subDivision.get("id"); + Integer subDivisionId = (Integer) subDivision.get("subDivisionId"); + Integer reminderCount = (Integer) subDivision.get("oldReminderCount"); + Boolean isSuppressed = (Boolean) subDivision.get("suppressedOldReminder"); + if (!isSuppressed) { + if (reminderCount < reminderDateSchedule.size()) { + LocalDate date = reminderDateSchedule.get(reminderCount); + + if (date.compareTo(now) <= 0) { + logger.info("*** Reminder Date Matched ***"); + + // update event_sub_division reminder count + jdbcDao.executeUpdate( + "UPDATE event_sub_division esd" + + " SET esd.oldReminderCount = esd.oldReminderCount + 1" + + " WHERE id = :eventSubDivisionId", + Map.of("eventSubDivisionId", eventSubDivisionId)); + + // Send email reminder + List userList = userService.listUserBySubDivision(subDivisionId); + for (User user : userList) { + logger.info("Send Email to user: " + user.getUsername() + " (" + + user.getEmail() + + ")"); + sendApplicationEmail(event.getId(), subDivisionId.longValue(), + user.getId(), + event, user, + true); + } + + // Update system reminder + String message = "Please note below event will be opened for application:\\n\\n" + + + " - Event Name: \\t\\t\\t " + event.getName() + "\\n" + + " - Application Deadline: \\t " + + event.getApplicationDeadline(); + + TodoReminder instance = new TodoReminder(); + instance.setEventId(event.getId()); + instance.setSubDivisionId(subDivisionId.longValue()); + instance.setTitle("Application Reminder (Old Event)"); + instance.setMessage(message); + instance.setReminderType("oldEventApplication"); + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class) + .name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null + && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + instance = save(instance); + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null + && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("noted", this.getTodoRead(input)); + newValueObject = logData; + } + + try { + auditLogService.save( + tableName, + instance.getId(), + instance.getTitle(), + SecurityUtils.getUser().isPresent() + ? SecurityUtils.getUser().get() + .getId() + : null, + new Date(), + JsonUtils.toJsonString( + auditLogService.compareMaps( + newValueObject, + oldValueObject)), + JsonUtils.toJsonString( + auditLogService.compareMaps( + oldValueObject, + newValueObject))); + } catch (Exception e) { + logger.info("Saving Audit Log Exception:"); + logger.info(ExceptionUtils.getStackTrace(e)); + } + // =====GET NEW AUDIT LOG=====//\ + } + } + } + + } + } + + public void generateNewEventApplicationReminder(Event event, LocalDate firstIssueDate) { + logger.info("=> Checking New Event Application Reminder"); + + LocalDate now = LocalDate.now(); + Integer limit = event.getReminderLimit().intValue(); + Integer interval = event.getReminderInterval().intValue(); + + List reminderDateSchedule = new ArrayList(); + for (Integer i = 0; i < limit; i++) { + reminderDateSchedule.add(firstIssueDate.plusDays(i * interval)); + } + + logger.info("Reminder Dates: " + reminderDateSchedule.stream() + .map(date -> date.format(dateFormat)).collect(Collectors.joining(", "))); + + logger.info("Application Deadline: " + event.getApplicationDeadline().format(dateFormat)); + List> eventSubDivision = eventService + .getEventDivisionAllData(Map.of("id", event.getId())); + for (Map subDivision : eventSubDivision) { + Integer eventSubDivisionId = (Integer) subDivision.get("id"); + Integer subDivisionId = (Integer) subDivision.get("subDivisionId"); + Integer reminderCount = (Integer) subDivision.get("newReminderCount"); + Boolean isSuppressed = (Boolean) subDivision.get("suppressedOldReminder"); + if (!isSuppressed) { + if (reminderCount < reminderDateSchedule.size()) { + LocalDate date = reminderDateSchedule.get(reminderCount); + + if (date.compareTo(now) <= 0 + && now.compareTo(event.getApplicationDeadline()) <= 0) { + logger.info("*** Reminder Date Matched ***"); + + // update event_sub_division reminder count + jdbcDao.executeUpdate( + "UPDATE event_sub_division esd" + + " SET esd.newReminderCount = esd.newReminderCount + 1" + + " WHERE id = :eventSubDivisionId", + Map.of("eventSubDivisionId", eventSubDivisionId)); + + // Send email reminder + List userList = userService.listUserBySubDivision(subDivisionId); + for (User user : userList) { + logger.info("Send Email to user: " + user.getUsername() + " (" + + user.getEmail() + + ")"); + sendApplicationEmail(event.getId(), subDivisionId.longValue(), + user.getId(), + event, user, + false); + } + + // Update system reminder + String message = "Please note below event will be opened for application:\\n\\n" + + + " - Event Name: \\t\\t\\t " + event.getName() + "\\n" + + " - Application Deadline: \\t " + + event.getApplicationDeadline(); + + TodoReminder instance = new TodoReminder(); + instance.setEventId(event.getId()); + instance.setSubDivisionId(subDivisionId.longValue()); + instance.setTitle( + "Application Reminder (New Event)"); + instance.setMessage(message); + instance.setReminderType("newEventApplication"); + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class) + .name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null + && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + instance = save(instance); + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null + && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("noted", this.getTodoRead(input)); + newValueObject = logData; + } + + try { + auditLogService.save( + tableName, + instance.getId(), + instance.getTitle(), + SecurityUtils.getUser().isPresent() + ? SecurityUtils.getUser().get() + .getId() + : null, + new Date(), + JsonUtils.toJsonString( + auditLogService.compareMaps( + newValueObject, + oldValueObject)), + JsonUtils.toJsonString( + auditLogService.compareMaps( + oldValueObject, + newValueObject))); + } catch (Exception e) { + logger.info("Saving Audit Log Exception:"); + logger.info(ExceptionUtils.getStackTrace(e)); + } + // =====GET NEW AUDIT LOG=====//\ + } + } + } + } + } + + @Scheduled(cron = "0 30 6 * * ?") // Runs at 06:30 AM every day + public void generateApplicationAnnouncement() throws UnsupportedEncodingException, ParseException { + logger.info("============= Generate Announcement Reminder Start ============="); + LocalDateTime range = LocalDateTime.now().minusYears(reminderEventWithin); + + logger.info("Fetch Event From: " + range.toString()); + + List eventList = eventService.getEventByWithinYear(Map.of( + "yearRange", range, "announcementDate", LocalDate.now())); + + logger.info("Fetched Total Number of Event: " + eventList.size()); + + for (Event event : eventList) { + logger.info("---------------------------------------------"); + logger.info("Event's award to be announced: " + event.getName()); + List applications = applicationService + .getApplicationByEventId(Map.of("eventId", event.getId())); + String applicationString = ""; + for (Application application : applications) { + // get application name list + logger.info("Send Email for Application: " + application.getName()); + if (applicationString.length() > 0) { + applicationString = applicationString + + "\\t\\t\\t\\t\\t\\t" + + application.getName() + "\\n"; + } else { + applicationString = application.getName() + "\\n"; + } + } + + for (Application application : applications) { + // send email in application level + List appSubDivisionIdList = applicationService + .getApplicationSubDivision(Map.of("id", application.getId())); + for (Integer subDivisionId : appSubDivisionIdList) { + List userList = userService.listUserBySubDivision(subDivisionId); + for (User user : userList) { + logger.info("Send Email to user: " + user.getUsername() + " (" + + user.getEmail() + + ")"); + sendAnnouncementEmail(event.getId(), subDivisionId.longValue(), + user.getId(), event, user); + } + String message = //"Result announcement details:\\n\\n" + + //" - Event Title: \\t\\t\\t" + event.getName() + "\\n" + + //" - Application : \\t\\t\\t" + applicationString + "\\n" + + " Announcement Date: \\t" + event.getAnnouncementDate(); + + TodoReminder instance = new TodoReminder(); + instance.setEventId(event.getId()); + instance.setSubDivisionId(subDivisionId.longValue()); + instance.setTitle("Result Announcement for " + event.getName()); + instance.setMessage(message); + instance.setReminderType("announcement"); + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class) + .name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null + && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + instance = save(instance); + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null + && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("noted", this.getTodoRead(input)); + newValueObject = logData; + } + + try { + auditLogService.save( + tableName, + instance.getId(), + instance.getTitle(), + SecurityUtils.getUser().isPresent() + ? SecurityUtils.getUser().get().getId() + : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps( + newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps( + oldValueObject, newValueObject))); + } catch (Exception e) { + logger.info("Saving Audit Log Exception:"); + logger.info(ExceptionUtils.getStackTrace(e)); + } + // =====GET NEW AUDIT LOG=====// + } + } + + } + logger.info("============= Generate Announcement Reminder End ============="); + } + + public Map getAuditLogObject(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " tr.id, " + + " tr.created, " + + " tr.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " tr.version, " + + " tr.modified, " + + " tr.modifiedBy AS modifiedById, " + + " tr.deleted, " + + " u2.name AS modifiedBy, " + + " e.name, " + + " sd.name, " + + " tr.reminderType, " + + " tr.title, " + + " tr.message, " + + " tr.suppressedBy, " + + " tr.suppressedDate " + + " FROM todo_reminder tr " + + " LEFT JOIN `user` u1 ON u1.id = tr.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = tr.modifiedBy " + + " JOIN event e ON e.id = tr.eventId " + + " JOIN sub_division sd ON sd.id = tr.subDivisionId " + + " WHERE tr.id = :id "); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public void sendTestEmail(Map req) + throws UnsupportedEncodingException, ParseException { + + String receiver = (String) req.get("receiver"); + String templateName = (String) req.get("templateName"); + Locale locale = Locale.ENGLISH; + Boolean isAnnouncement = templateName.contains("announcement"); + + logger.info(templateName); + String templateContent = settingsService.getString(templateName); + Long tempId = (long) -1; + Map dummyAnnouncementData = Map.of( + "subject", "Reminder: Upcoming Event for Enrolment", + "receiver", "RECEIVER", + "eventName", "EVENT NAME", + "applicationDetail", "APPLICATION DETAIL", + "announcementDate", "ANNOUNCEMENT DATE", + "subDivision", "SUBDIVISION", + "division", "DIVISION", + "sender", "SENDER", + "arsLink", url + "/award"); + + Map dummyApplicationData = Map.of( + "subject", "Result Announcement for EVENT NAME", + "receiver", "RECEIVER", + "eventName", "EVENT NAME", + "applicationDeadLine", "APPLICATION DEAD LINE", + "subDivision", "SUBDIVISION", + "division", "DIVISION", + "sender", "SENDER", + "arsLink", url + "/application"); + + MailRequest temp = MailRequest.builder() + .subject(isAnnouncement ? dummyAnnouncementData.get("subject").toString() + : dummyApplicationData.get("subject").toString()) + // .template("mail/applicationAnnouncement") + .templateContent(templateContent) + .args(isAnnouncement ? dummyAnnouncementData : dummyApplicationData) + .addTo(new InternetAddress( + // "mabelng@emsd.gov.hk", "Mable Ng")) + // user.getEmail(), user.getFullname())) + receiver, "RECEIVER")) + + .build(); + + List temp2 = new ArrayList(); + temp2.add(temp); + mailService.sendARS(temp2, locale, tempId, tempId, tempId, + isAnnouncement ? "announcement" : "application"); + } + + public void sendApplicationEmail(Long eventId, Long subDivisionId, Long userId, Event event, User user, + Boolean isOldEvent) { + try { + Locale locale = Locale.ENGLISH; + SubDivision subDivision = subDivisionService.find(subDivisionId).get(); + Division division = divisionService.find(subDivision.getDivisionId()).get(); + String templateContent = isOldEvent + ? settingsService.getString(SettingNames.SYS_EMAIL_TEMPLATE_REMINDER_OLDEVENT) + : settingsService.getString(SettingNames.SYS_EMAIL_TEMPLATE_REMINDER_NEWEVENT); + + MailRequest temp = MailRequest.builder() + .subject("Reminder: Upcoming Event for Enrolment") + // .template("mail/applicationReminder") + .templateContent(templateContent) + .args(Map.of( + "receiver", user.getFullname(), + "eventName", event.getName(), + "applicationDeadLine", + isOldEvent ? event.getNextApplicationDate() + : event.getApplicationDeadline(), + "subDivision", subDivision.getName(), + "division", division.getName(), + "sender", "Electrical and Mechanical Services Department", + "arsLink", url + "/event/maintain/" + event.getId())) + .addTo(new InternetAddress( + // "mabelng@emsd.gov.hk", "Mabel Ng")) + user.getEmail(), user.getFullname())) + // "jason.lam@2fi-solutions.com.hk", "Jason Lam")) + + .build(); + List temp2 = new ArrayList(); + temp2.add(temp); + mailService.sendARS(temp2, locale, eventId, subDivisionId, userId, "application"); + } catch (Exception e) { + logger.info(ExceptionUtils.getStackTrace(e)); + } + } + + public void sendAnnouncementEmail(Long eventId, Long subDivisionId, Long userId, Event event, User user) { + + try { + Locale locale = Locale.ENGLISH; + List applications = applicationService + .getApplicationByEventId(Map.of("eventId", event.getId())); + SubDivision subDivision = subDivisionService.find(subDivisionId).get(); + Division division = divisionService.find(subDivision.getDivisionId()).get(); + String templateContent = settingsService + .getString(SettingNames.SYS_EMAIL_TEMPLATE_ANNOUNCEMENT); + + String applicationString = ""; + for (Application application : applications) { + applicationString = applicationString + "- " + application.getName() + "
"; + } + + MailRequest temp = MailRequest.builder() + .subject("Reminder: Result Announcement for " + event.getName()) + // .template("mail/applicationAnnouncement") + .templateContent(templateContent) + .args(Map.of( + "receiver", user.getFullname(), + "eventName", event.getName(), + "applicationDetail", applicationString, + "announcementDate", event.getAnnouncementDate(), + "subDivision", subDivision.getName(), + "division", division.getName(), + "sender", "Electrical and Mechanical Services Department", + "arsLink", url + "/event/maintain/" + event.getId())) + .addTo(new InternetAddress( + // "mabelng@emsd.gov.hk", "Mable Ng")) + user.getEmail(), user.getFullname())) + // "jason.lam@2fi-solutions.com.hk", "Jason Lam")) + + .build(); + + List temp2 = new ArrayList(); + temp2.add(temp); + mailService.sendARS(temp2, locale, eventId, subDivisionId, userId, "announcement"); + } catch (Exception e) { + logger.info(ExceptionUtils.getStackTrace(e)); + } + } + + public List> list(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT tr.id, " + + " tr.eventId, " + + " tr.subDivisionId, " + + " tr.reminderType, " + + " tr.title, " + + " tr.message, " + + " tr.suppressedBy, " + + " trn.userId " + + " FROM `user` u " + + " LEFT JOIN sub_division sd ON u.subDivisionId = sd.id " + // + " LEFT JOIN division d ON sd.divisionId = d.id " + // + " LEFT JOIN sub_division sd2 ON sd2.divisionId = d.id " + + " LEFT JOIN todo_reminder tr ON tr.subDivisionId = sd.id " + + " LEFT JOIN todo_reminder_noted trn ON trn.todoReminderId = tr.id " + + " WHERE tr.deleted = FALSE " + + " AND u.id = :userId " + ); + if (args != null) { + if (args.containsKey("reminderType")) + sql.append(" AND tr.reminderType= :reminderType "); + else + sql.append(" AND tr.reminderType != \"announcement\" "); + } + + sql.append( + " AND IF(:read, trn.userId IS NOT NULL AND trn.userId = :userId , trn.userId IS NULL) "+ + " ORDER BY tr.id DESC" + ); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public Integer userRead(Long id, Long userId) { + TodoReminder instance = this.find(id).get(); + + Map value = Map.of("todoReminderId", id, "userId", userId, "notedDate", + LocalDateTime.now()); + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + jdbcDao.executeUpdate( + "INSERT IGNORE INTO todo_reminder_noted (todoReminderId, userId, notedDate)" + + " VALUE (:todoReminderId, :userId, :notedDate)", + value); + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("noted", this.getTodoRead(input).get("notedDate")); + newValueObject = logData; + } + auditLogService.save( + tableName, + instance.getId(), + instance.getTitle(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + // =====GET NEW AUDIT LOG=====// + + return 1; + + } + + public Map getTodoRead(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " trn.notedDate " + + " FROM todo_reminder_noted trn " + + " WHERE trn.todoReminderId =:id "); + + return jdbcDao.queryForMap(sql.toString(), req).orElse(null); + } + + public List getFailEmailRecord(Map req) { + StringBuilder sql = new StringBuilder("SELECT" + + " * " + + " FROM todo_reminder_email_log trel " + + " WHERE trel.resendSuccess=0 " + + " AND trel.success=0 "); + + return jdbcDao.queryForList(sql.toString(), req, TodoReminderEmailLog.class); + } + + public Long suppressTodo(Long id, Long userId) { + TodoReminder instance = find(id).get(); + instance.setSuppressedBy(userId); + instance.setSuppressedDate(LocalDateTime.now()); + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + oldValueObject = logData; + } + // =====GET OLD AUDIT LOG=====// + instance = save(instance); + + Map value = Map.of( + "eventId", instance.getEventId(), + "subDivisionId", instance.getSubDivisionId()); + + logger.info(instance.getReminderType()); + if (instance.getReminderType().equals("newEventApplication")) { + logger.info("here"); + jdbcDao.executeUpdate( + "UPDATE event_sub_division esd" + + " SET esd.suppressedNewReminder = TRUE" + + " WHERE eventId = :eventId " + + " AND subDivisionId = :subDivisionId", + value); + } else if (instance.getReminderType().equals("oldEventApplication")) { + logger.info("here2"); + jdbcDao.executeUpdate( + "UPDATE event_sub_division esd" + + " SET esd.suppressedOldReminder = TRUE" + + " WHERE eventId = :eventId " + + " AND subDivisionId = :subDivisionId", + value); + } + + Map value2 = Map.of("todoReminderId", id, "userId", userId, "notedDate", + LocalDateTime.now()); + + if (this.getTodoRead(Map.of("id", instance.getId())) == null) { + jdbcDao.executeUpdate( + "INSERT IGNORE INTO todo_reminder_noted (todoReminderId, userId, notedDate)" + + " VALUE (:todoReminderId, :userId, :notedDate)", + value2); + } + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("noted", this.getTodoRead(input).get("notedDate")); + newValueObject = logData; + } + auditLogService.save( + tableName, + instance.getId(), + instance.getTitle(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject, oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject, newValueObject))); + // =====GET NEW AUDIT LOG=====// + return instance.getId(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/ApplicationController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/ApplicationController.java new file mode 100644 index 0000000..187467f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/ApplicationController.java @@ -0,0 +1,224 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ffii.lioner.modules.lioner.req.UpdateExtRefReq; +import com.ffii.lioner.modules.lioner.service.ApplicationService; +import com.ffii.lioner.modules.lioner.service.FileRefService; +import com.ffii.lioner.modules.lioner.service.FileService; +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/application") +public class ApplicationController{ + + private final Log logger = LogFactory.getLog(getClass()); + private ApplicationService applicationService; + private FileService fileService; + private FileRefService fileRefService; + private ExcelReportService excelReportService; + + public ApplicationController(ApplicationService applicationService, FileService fileService, FileRefService fileRefService, ExcelReportService excelReportService) { + this.applicationService = applicationService; + this.fileService = fileService; + this.fileRefService = fileRefService; + this.excelReportService = excelReportService; + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, applicationService.getApplicationDetail(Map.of(Params.ID, id)).orElseThrow(NotFoundException::new), + "tagList", applicationService.getApplicationTag(Map.of(Params.ID, id)), + "subDivisionList", applicationService.getApplicationSubDivision(Map.of(Params.ID, id)) + ); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(applicationService.list( + CriteriaArgsBuilder.withRequest(request) + .addStringLike("eventName") + .addStringLike("applicationName") + .addStringLike("description") + .addStringLike("status") + .addStringLike("officer") + .addInteger("userSubDivisionId") + .addIntegerList("tagList") + .addIntegerList("divisionIdList") + .addIntegerList("subDivisionIdList") + .addStringLike("keyword") + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id, @RequestParam Long eventId) { + boolean isNameTaken = applicationService.isNameTaken(name,id,eventId); + return Map.of( + "isTaken", isNameTaken + ); + } + + @GetMapping("/export") + public ResponseEntity getApplicationExport(HttpServletRequest request) throws ServletRequestBindingException, IOException { + List> record = applicationService.listReport(CriteriaArgsBuilder.withRequest(request) + .addStringLike("eventName") + .addStringLike("applicationName") + .addStringLike("description") + .addStringLike("status") + .addStringLike("officer") + .addInteger("userSubDivisionId") + .addIntegerList("tagList") + .addIntegerList("divisionIdList") + .addIntegerList("subDivisionIdList") + .addStringLike("keyword") + .build()); + + byte[] reportResult = excelReportService.generateApplicationExcelReport(record); + return ResponseEntity.ok() + .header("filename", "application_export_" + LocalDate.now()) + .body(new ByteArrayResource(reportResult)); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map saveOrUpdate( + @RequestParam Long id, + @RequestParam Long eventId, + @RequestParam String eventName , + @RequestParam String description , + @RequestParam String name , + @RequestParam String responsibleOfficer, + @RequestParam String status , + @RequestParam List subDivisionIdList, + @RequestParam List subDivisionRemoveIdList, + @RequestParam List tagIdList , + @RequestParam List tagRemoveIdList, + @RequestParam(value = "files", required = false) MultipartFile[] files) throws Exception { + + Map resultMap = applicationService.saveOrUpdate( + id, eventId, eventName,description,name, + responsibleOfficer,status, + subDivisionIdList,subDivisionRemoveIdList, + tagIdList,tagRemoveIdList, + files + ); + + return Map.of(Params.DATA, resultMap); + } + + @GetMapping("/fileRef/{id}") + public RecordsRes> getFileRefList(@PathVariable Long id) throws ServletRequestBindingException { + return new RecordsRes<>(fileService.listFile( + Map.of( + "refId",id, + "refType", "application" + ) + )); + } + + @GetMapping("/externalRef/{id}") + public RecordsRes> getExtRefList(@PathVariable Long id) throws ServletRequestBindingException { + return new RecordsRes<>(fileRefService.listExternal( + Map.of( + "refId",id, + "refType", "application" + ) + )); + } + + @GetMapping("/storedSize/{id}") + public Map getFileStoredSize(@PathVariable Long id) throws ServletRequestBindingException { + BigDecimal size = fileRefService.getFileStoredSize( + Map.of( + "refId",id, + "refType", "application" + )); + + return Map.of("size", size); + } + + @GetMapping("/videoExist/{id}") + public Map getVideoExist(@PathVariable Long id) throws ServletRequestBindingException { + Integer count = fileRefService.getVideoExist( + Map.of( + "refId",id, + "refType", "application" + )); + + return Map.of("count", count); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping("/externalRef/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateExtRefReq req) { + return new IdRes(this.fileRefService.saveExternal(req, "application")); + } + + @DeleteMapping("/externalRef/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteFile(@PathVariable Long id) { + fileRefService.markDelete(id); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + applicationService.markDeleteWithAuditLog(applicationService.find(id).get()); + + } + + @GetMapping("/award") + public RecordsRes> listByApplication(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(applicationService.listByApplication( + CriteriaArgsBuilder.withRequest(request) + .addInteger("applicationId") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/combo") + public RecordsRes> getApplicationCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(applicationService.listApplicationCombo( + CriteriaArgsBuilder.withRequest(request) + .addInteger("eventId") + .addInteger("userSubDivisionId") + .build())); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/AppreciationController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/AppreciationController.java new file mode 100644 index 0000000..1c0f98b --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/AppreciationController.java @@ -0,0 +1,275 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ffii.lioner.modules.lioner.req.CreateImportErrorReportReq; +import com.ffii.lioner.modules.lioner.service.AppreciationService; +import com.ffii.lioner.modules.lioner.service.FileRefService; +import com.ffii.lioner.modules.lioner.service.FileService; +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.lioner.modules.master.service.SBUDivisionService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/appreciation") +public class AppreciationController{ + + private final Log logger = LogFactory.getLog(getClass()); + private AppreciationService appreciationService; + private FileRefService fileRefService; + private FileService fileService; + private ExcelReportService excelReportService; + private SBUDivisionService sbuDivisionService; + + public AppreciationController( + AppreciationService appreciationService, + FileRefService fileRefService, + FileService fileService, + ExcelReportService excelReportService, + SBUDivisionService sbuDivisionService + ) { + this.appreciationService = appreciationService; + this.fileRefService = fileRefService; + this.fileService = fileService; + this.excelReportService = excelReportService; + this.sbuDivisionService = sbuDivisionService; + } + + // @PostMapping("/save") + // public IdRes saveOrUpdate(@RequestBody @Valid UpdateAppreciationReq req) { + // return new IdRes(appreciationService.saveOrUpdate(req).getId()); + // } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map saveOrUpdate( + @RequestParam Long id, + @RequestParam String sbuIds, + @RequestParam LocalDate receiptDate , + @RequestParam String description , + @RequestParam(value = "clientDepartment", required = false) String clientDepartment , + @RequestParam(value = "clientOrganization", required = false) String clientOrganization , + @RequestParam(value = "clientFullname", required = false) String clientFullname , + @RequestParam(value = "clientPost", required = false) String clientPost , + @RequestParam(value = "venue", required = false) String venue , + @RequestParam(value = "clientReplyDate", required = false) LocalDate clientReplyDate , + @RequestParam(value = "staffReplyDate", required = false) LocalDate staffReplyDate , + @RequestParam(value = "noOfStaff", required = false) Long noOfStaff , + @RequestParam LocalDate lnReceiptDate , + @RequestParam Long aprCategoryId , + @RequestParam(value = "remarks", required = false) String remarks, + @RequestParam(value = "files", required = false) MultipartFile[] files) throws Exception { + + Map resultMap = appreciationService.saveOrUpdate( + id, sbuIds, receiptDate, description, clientDepartment, + clientOrganization, clientFullname, clientPost, venue, clientReplyDate, + staffReplyDate, noOfStaff, lnReceiptDate, aprCategoryId, remarks, + files + ); + + return Map.of(Params.DATA, resultMap); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping(value = "/massImport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map massImport( + @RequestParam(value = "files", required = false) MultipartFile[] files) throws Exception { + Map resultMap = appreciationService.massImport(files); + return Map.of(Params.DATA, resultMap); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + appreciationService.markDeleteWithAuditLog(appreciationService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, appreciationService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping("/yearCombo") + public Map getYearCombo() { + return Map.of( + Params.RECORDS, appreciationService.getAppreciationYearCombo() + ); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(appreciationService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addString("sbuIds") + .addIntegerList("categoryIds") + .addStringLike("clientDepOrOrg") + .addStringLike("clientName") + .addDate("receiptDateFrom") + .addDateTo("receiptDateTo") + .addDate("lnReceiptDateFrom") + .addDateTo("lnReceiptDateTo") + .addDate("clientReplyDateFrom") + .addDateTo("clientReplyDateTo") + .addDate("staffReplyDateFrom") + .addDateTo("staffReplyDateTo") + .build())); + } + + @GetMapping("/export") + public ResponseEntity getEventExport(HttpServletRequest request) throws ServletRequestBindingException, IOException { + List> record = appreciationService.listReport(CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addString("sbuIds") + .addIntegerList("categoryIds") + .addStringLike("clientDepOrOrg") + .addStringLike("clientName") + .addDate("receiptDateFrom") + .addDateTo("receiptDateTo") + .addDate("lnReceiptDateFrom") + .addDateTo("lnReceiptDateTo") + .addDate("clientReplyDateFrom") + .addDateTo("clientReplyDateTo") + .addDate("staffReplyDateFrom") + .addDateTo("staffReplyDateTo") + .build()); + + for (Map appreciation : record) { + String sbuIds = (String) appreciation.get("sbuIds"); + ObjectMapper objectMapper = new ObjectMapper(); + List> ids = objectMapper.readValue(sbuIds, List.class); + String divisionString = ""; + int count = 0; + for (Map map : ids) { + if(count != 0){ + divisionString += ", "; + } + divisionString += sbuDivisionService.getDivisionNameById(map); + count++; + } + appreciation.put("divisionString", divisionString); + } + + byte[] reportResult = excelReportService.generateAppreciationExcelReport(record); + final DateTimeFormatter fileDateFormat = DateTimeFormatter.ofPattern("YYYYMMdd"); + + return ResponseEntity.ok() + .header("filename", "appreciation_export_" + LocalDate.now().format(fileDateFormat) + ".xlsx") + .body(new ByteArrayResource(reportResult)); + } + + @GetMapping("/fileRef/{id}") + public RecordsRes> getFileRefList(@PathVariable Long id) throws ServletRequestBindingException { + return new RecordsRes<>(fileService.listFile( + Map.of( + "refId",id, + "refType", "appreciation" + ) + )); + } + + @GetMapping("/storedSize/{id}") + public Map getFileStoredSize(@PathVariable Long id) throws ServletRequestBindingException { + BigDecimal size = fileRefService.getFileStoredSize( + Map.of( + "refId",id, + "refType", "appreciation" + )); + + return Map.of("size", size); + } + + @GetMapping("/downloadTemplate") + public ResponseEntity getImportTemplate() throws IOException { + byte[] reportResult = excelReportService.exportAppreciationTemplate(); + return ResponseEntity.ok() + .header("filename", "Appreciation_Records_Import_Template_v1.0.xlsx") + .body(new ByteArrayResource(reportResult)); + } + + @RequestMapping(value = "/caseReport", method = RequestMethod.GET) + public ResponseEntity downloadAppreciationsCaseReport(HttpServletRequest request) throws Exception { + String lnReceiptDateFrom = request.getParameter("dateFrom"); + String lnReceiptDateTo = request.getParameter("dateTo"); + + LocalDate dateFrom = LocalDate.parse(lnReceiptDateFrom); + LocalDate dateTo = LocalDate.parse(lnReceiptDateTo).plusDays(1); + + byte[] reportResult = appreciationService.generateCaseReport(dateFrom,dateTo); + final DateTimeFormatter fileDateFormat = DateTimeFormatter.ofPattern("YYYYMMdd"); + + return ResponseEntity.ok() + .header("filename", "Appreciation_Cases_Report_" + LocalDate.now().format(fileDateFormat) + ".docx") + .body(new ByteArrayResource(reportResult)); + } + + @RequestMapping(value = "/summaryReport", method = RequestMethod.GET) + public ResponseEntity downloadAppreciationsSummaryReport(HttpServletRequest request) throws Exception { + Integer year = Integer.parseInt(request.getParameter("year")); + Integer month = Integer.parseInt(request.getParameter("month")); + + // final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("YYYYMMM"); + // String reportPeriod = LocalDate.of(year,month,1).format(dateFormat); + + final DateTimeFormatter fileDateFormat = DateTimeFormatter.ofPattern("YYYYMMdd"); + + byte[] reportResult = appreciationService.generateSummaryReport(year,month); + + return ResponseEntity.ok() + .header("filename", "Appreciation_Summary_Report_" + LocalDate.now().format(fileDateFormat) + ".docx") + .body(new ByteArrayResource(reportResult)); + } + + @PostMapping("/errorExport") + public ResponseEntity downloadAppreciationsImportErrorReport(@RequestBody @Valid CreateImportErrorReportReq req) throws Exception { + + byte[] reportResult = appreciationService.generateImportErrorReport(req.getErrorRecord()); + + final DateTimeFormatter fileDateFormat = DateTimeFormatter.ofPattern("YYYYMMdd"); + + return ResponseEntity.ok() + .header("filename", "Appreciation_Import_Error_Report_" + LocalDate.now().format(fileDateFormat) + ".xlsx") + .body(new ByteArrayResource(reportResult)); + } +} + + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/AwardController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/AwardController.java new file mode 100644 index 0000000..0cfad69 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/AwardController.java @@ -0,0 +1,271 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ffii.lioner.modules.lioner.req.CreateImportErrorReportReq; +import com.ffii.lioner.modules.lioner.req.UpdateExtRefReq; +import com.ffii.lioner.modules.lioner.service.AwardService; +import com.ffii.lioner.modules.lioner.service.FileRefService; +import com.ffii.lioner.modules.lioner.service.FileService; +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.lioner.modules.master.service.CategoryService; +import com.ffii.lioner.modules.master.service.PromotionChannelService; +import com.ffii.lioner.modules.master.service.SubDivisionService; +import com.ffii.lioner.modules.master.service.TagService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/award") +public class AwardController{ + + private final Log logger = LogFactory.getLog(getClass()); + private AwardService awardService; + private FileService fileService; + private FileRefService fileRefService; + private ExcelReportService excelReportService; + private PromotionChannelService promotionChannelService; + private TagService tagService; + private CategoryService categoryService; + private SubDivisionService subDivisionService; + + public AwardController(AwardService awardService, FileService fileService, + FileRefService fileRefService, ExcelReportService excelReportService, + PromotionChannelService promotionChannelService, TagService tagService, + CategoryService categoryService, SubDivisionService subDivisionService + ) { + this.awardService = awardService; + this.fileService = fileService; + this.fileRefService = fileRefService; + this.excelReportService = excelReportService; + this.promotionChannelService = promotionChannelService; + this.tagService = tagService; + this.categoryService = categoryService; + this.subDivisionService = subDivisionService; + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, awardService.getAwardDetail(Map.of(Params.ID, id)).orElseThrow(NotFoundException::new), + "subDivisionList", awardService.getAwardSubDivision(Map.of(Params.ID, id)) + ); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping(value = "/massImportAward", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map massImport( + @RequestParam(value = "files", required = false) MultipartFile[] files) throws Exception { + Map resultMap = awardService.massImport(files); + return Map.of(Params.DATA, resultMap); + } + + @PostMapping("/errorExport") + public ResponseEntity downloadAppreciationsImportErrorReport(@RequestBody @Valid CreateImportErrorReportReq req) throws Exception { + + byte[] reportResult = awardService.generateImportErrorReport(req.getErrorRecord()); + + final DateTimeFormatter fileDateFormat = DateTimeFormatter.ofPattern("YYYYMMdd"); + + return ResponseEntity.ok() + .header("filename", "Award_Import_Error_Report_" + LocalDate.now().format(fileDateFormat) + ".xlsx") + .body(new ByteArrayResource(reportResult)); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(awardService.list( + CriteriaArgsBuilder.withRequest(request) + .addStringLike("eventName") + .addStringLike("applicationName") + .addDate("fromDate") + .addDateTo("toDate") + .addDate("awardFromDate") + .addDateTo("awardToDate") + .addStringLike("awardName") + .addIntegerList("categoryIds") + .addIntegerList("tagIds") + .addIntegerList("divisionIdList") + .addInteger("userSubDivisionId") + .addIntegerList("subDivisionIdList") + .addStringLike("storageLocation") + .addStringLike("physicalAward") + .addString("promotionChannel") + .addStringLike("responsibleOfficer") + .addStringLike("keyword") + .build())); + } + + @GetMapping("/getExportTemplate") + public ResponseEntity getAwardImportTemplate() throws ServletRequestBindingException, IOException { + Map>> masterData = new HashMap<>(); + masterData.put("Promotion Channel", promotionChannelService.getNameList()); + masterData.put("Tag", tagService.getNameList()); + masterData.put("Category", categoryService.getNameList()); + masterData.put("Sub-Division", subDivisionService.getNameList()); + + byte[] reportResult = excelReportService.exportAwardImportTemplate(masterData); + return ResponseEntity.ok() + .header("filename", "Award_Record_Import_Template_v1.0.xlsx") + .body(new ByteArrayResource(reportResult)); + } + + @GetMapping("/export") + public ResponseEntity getAwardExport(HttpServletRequest request) throws ServletRequestBindingException, IOException { + List> record = awardService.listReport(CriteriaArgsBuilder.withRequest(request) + .addStringLike("eventName") + .addStringLike("applicationName") + .addDate("fromDate") + .addDateTo("toDate") + .addDate("awardFromDate") + .addDateTo("awardToDate") + .addStringLike("awardName") + .addIntegerList("categoryIds") + .addIntegerList("tagIds") + .addIntegerList("divisionIdList") + .addInteger("userSubDivisionId") + .addIntegerList("subDivisionIdList") + .addStringLike("storageLocation") + .addStringLike("physicalAward") + .addString("promotionChannel") + .addStringLike("responsibleOfficer") + .addStringLike("keyword") + .build()); + + byte[] reportResult = excelReportService.generateAwardExcelReport(record); + return ResponseEntity.ok() + .header("filename", "award_export_" + LocalDate.now()) + .body(new ByteArrayResource(reportResult)); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map saveOrUpdate( + @RequestParam Long id, + @RequestParam Long applicationId, + @RequestParam String name , + @RequestParam String nameCht , + @RequestParam String remarks , + @RequestParam Long categoryId , + @RequestParam(value = "receiveDate", required = false) LocalDate receiveDate , + @RequestParam(value = "storageLocation", required = false) String storageLocation, + @RequestParam(value = "physicalAward", required = false) String physicalAward , + @RequestParam String responsibleOfficer , + @RequestParam(value = "promotionChannel", required = false) String promotionChannel , + @RequestParam List subDivisionIdList, + @RequestParam List subDivisionRemoveIdList, + @RequestParam(value = "files", required = false) MultipartFile[] files) throws Exception { + + Map resultMap = awardService.saveOrUpdate( + id, applicationId, name, nameCht, remarks, categoryId,receiveDate, + storageLocation,physicalAward,responsibleOfficer, + promotionChannel,subDivisionIdList,subDivisionRemoveIdList, + files + ); + + return Map.of(Params.DATA, resultMap); + } + + @GetMapping("/fileRef/{id}") + public RecordsRes> getFileRefList(@PathVariable Long id) throws ServletRequestBindingException { + return new RecordsRes<>(fileService.listFile( + Map.of( + "refId",id, + "refType", "award" + ) + )); + } + + @GetMapping("/storedSize/{id}") + public Map getFileStoredSize(@PathVariable Long id) throws ServletRequestBindingException { + BigDecimal size = fileRefService.getFileStoredSize( + Map.of( + "refId",id, + "refType", "award" + )); + + return Map.of("size", size); + } + + @GetMapping("/videoExist/{id}") + public Map getVideoExist(@PathVariable Long id) throws ServletRequestBindingException { + Integer count = fileRefService.getVideoExist( + Map.of( + "refId",id, + "refType", "award" + )); + + return Map.of("count", count); + } + + @GetMapping("/externalRef/{id}") + public RecordsRes> getExtRefList(@PathVariable Long id) throws ServletRequestBindingException { + return new RecordsRes<>(fileRefService.listExternal( + Map.of( + "refId",id, + "refType", "award" + ) + )); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping("/externalRef/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateExtRefReq req) { + return new IdRes(this.fileRefService.saveExternal(req, "award")); + } + + @DeleteMapping("/externalRef/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void deleteFile(@PathVariable Long id) { + fileRefService.markDelete(id); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + awardService.markDeleteWithAuditLog(awardService.find(id).get()); + + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id, @RequestParam Long applicationId) { + boolean isNameTaken = awardService.isNameTaken(name,id,applicationId); + return Map.of( + "isTaken", isNameTaken + ); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/EventController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/EventController.java new file mode 100644 index 0000000..de09e59 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/EventController.java @@ -0,0 +1,248 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.lioner.req.UpdateEventReq; +import com.ffii.lioner.modules.lioner.service.EventService; +import com.ffii.lioner.modules.common.service.ExcelReportService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.DataRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/event") +public class EventController{ + + private final Log logger = LogFactory.getLog(getClass()); + private EventService eventService; + private ExcelReportService excelReportService; + + public EventController(EventService eventService, ExcelReportService excelReportService) { + this.eventService = eventService; + this.excelReportService = excelReportService; + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, eventService.find(id).orElseThrow(NotFoundException::new), + "eventDivision", eventService.getEventDivision(Map.of("id",id)) + ); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @PostMapping("/save") + public Map saveOrUpdate(@RequestBody @Valid UpdateEventReq req) { + return Map.of( + Params.DATA,eventService.saveOrUpdate(req) + ); + } + + @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + eventService.markDeleteWithAuditLog(eventService.find(id).get()); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(eventService.list( + CriteriaArgsBuilder.withRequest(request) + .addStringLike("eventName") + .addStringLike("description") + .addStringLike("organization") + .addDate("fromDate") + .addDateTo("toDate") + .addStringLike("region") + .addStringLike("type") + .addIntegerList("divisionIdList") + .addIntegerList("subDivisionIdList") + .addStringLike("keyword") + .build())); + } + + @GetMapping("/export") + public ResponseEntity getEventExport(HttpServletRequest request) throws ServletRequestBindingException, IOException { + List> record = eventService.listReport(CriteriaArgsBuilder.withRequest(request) + .addStringLike("eventName") + .addStringLike("description") + .addStringLike("organization") + .addDate("fromDate") + .addDateTo("toDate") + .addStringLike("region") + .addStringLike("type") + .addIntegerList("divisionIdList") + .addIntegerList("subDivisionIdList") + .addStringLike("keyword") + .build()); + + byte[] reportResult = excelReportService.generateEventExcelReport(record); + return ResponseEntity.ok() + .header("filename", "event_export_" + LocalDate.now()) + .body(new ByteArrayResource(reportResult)); + } + + @GetMapping("/application") + public RecordsRes> listByEvent(HttpServletRequest request) + throws ServletRequestBindingException { + return new RecordsRes<>(eventService.listByEvent( + CriteriaArgsBuilder.withRequest(request) + .addInteger("eventId") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/combo") + public RecordsRes> getEventCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(eventService.listCombo( + CriteriaArgsBuilder.withRequest(request) + .addInteger("userSubDivisionId") + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = eventService.isNameTaken(name,id); + return Map.of( + "isTaken", isNameTaken + + ); + } + + @GetMapping("/checkOvertime/{id}") + public Map checkOvertime(@PathVariable Long id) { + return eventService.isReminderOvertime(id); + } + + @GetMapping("/dashboard/combo") + public RecordsRes> getDashboardCombo() { + return new RecordsRes<>(eventService.getDashboardCombo()); + } + + @GetMapping("/dashboard/topRecord") + public RecordsRes> getTop6(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(eventService.getTop6( + CriteriaArgsBuilder.withRequest(request) + .addInteger("year") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/dashboard/yearAward") + public DataRes> getYearAwardCount(HttpServletRequest request) throws ServletRequestBindingException { + return new DataRes<>(eventService.getYearAwardCount( + CriteriaArgsBuilder.withRequest(request) + .addInteger("year") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/dashboard/yearEvent") + public DataRes> getYearEventCount(HttpServletRequest request) throws ServletRequestBindingException { + return new DataRes<>(eventService.getYearEventCount( + CriteriaArgsBuilder.withRequest(request) + .addInteger("year") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/dashboard/yearDivisionSummary") + public RecordsRes> getDivisionAwardSummary(@RequestParam Integer year, @RequestParam String branch, @RequestParam Boolean viewDivisionOnly, @RequestParam Integer userSubDivisionId) { + if(!viewDivisionOnly){ + List> tempRecords = eventService.getDivisionAwardSummary(Map.of("year",year, "branch", branch)); + + if(tempRecords.size() == 0){ + tempRecords = eventService.getDummyDivisionAwardSummary(Map.of("year",year,"branch", branch)); + } + return new RecordsRes<>(tempRecords); + + } + else{ + List> tempRecords = eventService.getDummyDivisionAwardSummary(Map.of("year",year,"userSubDivisionId",userSubDivisionId)); + return new RecordsRes<>(tempRecords); + + } + + } + + @GetMapping("/dashboard/yearTagSummary") + public RecordsRes> getTagAwardSummary(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(eventService.getTagAwardSummary( + CriteriaArgsBuilder.withRequest(request) + .addInteger("year") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/dashboard/yearCategorySummary") + public RecordsRes> getCategoryAwardSummary(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(eventService.getCategoryAwardSummary( + CriteriaArgsBuilder.withRequest(request) + .addInteger("year") + .addInteger("userSubDivisionId") + .build() + )); + } + + @GetMapping("/dashboard/awardSummaryReport") + public ResponseEntity getAwardSummaryReport(@RequestParam Integer year, + @RequestParam Integer userSubDivisionId, + @RequestParam String branch, + @RequestParam Boolean viewDivisionOnly + ) throws IOException { + if(!viewDivisionOnly){ + List> result = eventService.getAwardSummaryReportPage1(Map.of("year",year, "branch", branch)); + List> detailResult = eventService.getAwardSummaryReportPage2(Map.of("year",year, "branch", branch)); + byte[] reportResult = excelReportService.generateDashboardExcelReport(result, detailResult, year); + return ResponseEntity.ok() + .header("filename", "award_summary_report_" + LocalDate.now()) + .body(new ByteArrayResource(reportResult)); + } + else{ + List> result = eventService.getAwardSummaryReportPage1(Map.of("year",year, "userSubDivisionId", userSubDivisionId)); + List> detailResult = eventService.getAwardSummaryReportPage2(Map.of("year",year, "userSubDivisionId", userSubDivisionId)); + byte[] reportResult = excelReportService.generateDashboardExcelReport(result, detailResult, year); + return ResponseEntity.ok() + .header("filename", "award_summary_report_" + LocalDate.now()) + .body(new ByteArrayResource(reportResult)); + } + + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/FileController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/FileController.java new file mode 100644 index 0000000..c636e46 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/FileController.java @@ -0,0 +1,229 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.imageio.ImageIO; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.rendering.PDFRenderer; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.ffii.lioner.modules.lioner.entity.File; +import com.ffii.lioner.modules.lioner.entity.FileBlob; +import com.ffii.lioner.modules.lioner.entity.FileRef; +import com.ffii.lioner.modules.lioner.service.FileBlobService; +import com.ffii.lioner.modules.lioner.service.FileRefService; +import com.ffii.lioner.modules.lioner.service.FileService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.utils.Params; + +@RestController +@RequestMapping("/file") +public class FileController { + + private final Log logger = LogFactory.getLog(getClass()); + private FileService fileService; + private FileRefService fileRefService; + private FileBlobService fileBlobService; + + public FileController(FileService fileService, FileRefService fileRefService, FileBlobService fileBlobService) { + this.fileService = fileService; + this.fileBlobService = fileBlobService; + this.fileRefService = fileRefService; + } + + @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map saveOrUpdate(@RequestParam String eventName, @RequestParam MultipartFile[] files) { + //logger.info(files); + return Map.of(Params.DATA, " fileService.saveOrUpdate(req)"); + } + + @DeleteMapping("/{id}") + public Map delete(@PathVariable Long id) { + return fileService.deleteFile(id); + } + + @PostMapping(value = "/thumbnail/ul", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Map saveOrUpdate( + @RequestParam Long fileId, + @RequestParam(value = "remarks", required = false) String remarks, + @RequestParam(value = "file", required = false) MultipartFile file) + throws Exception { + + return Map.of(Params.DATA, fileService.updateFileDisplay(fileId, file, remarks)); + } + + @RequestMapping(value = "/dl/{id}/{skey}/{filename}", method = RequestMethod.GET) + public ResponseEntity download(@RequestParam(defaultValue = "false") boolean dl, + @PathVariable Long id, @PathVariable String skey) throws Exception { + + File file = fileService.findFileByIdAndKey(id, skey) + .orElseThrow(NotFoundException::new); + + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(10, TimeUnit.DAYS)) + .header("filename", file.getFilename()) + .body(new ByteArrayResource(fileService.getfileBlob(file.getId()))); + //.body(new ByteArrayResource(unzippedData)); + } + + @RequestMapping(value ="/thumbnail/{id}/{skey}/{filename}", method = RequestMethod.GET) + public ResponseEntity thumbnail( + @RequestParam(defaultValue = "false") boolean dl, + @PathVariable Long id, + @PathVariable String skey, + @PathVariable String filename) throws IOException { + + File quickRef = fileService.loadFileOnly(id,skey,filename); + + if(quickRef.getMimetype().equals("application/x-7z-compressed") || + quickRef.getMimetype().equals("application/x-rar-compressed") || + quickRef.getMimetype().equals("application/zip") + ){ + return null; + } + + if(quickRef.getMimetype().equals("video/mpeg") || + quickRef.getMimetype().equals("video/mp4") || + quickRef.getMimetype().equals("video/x-matroska") || + quickRef.getMimetype().equals("video/quicktime") || + quickRef.getMimetype().equals("video/x-msvideo") + ){ + FileRef fileRef = fileRefService.findByFileId(quickRef.getId()).get(); + byte[] thumbnailBlob = fileService.getfileBlobById(fileRef.getThumbnailFileId()); + //byte[] thumbnailBlob = processVideoAndGetThumbnail(unzippedData); + + // Create an appropriate HTTP response with the thumbnail blob + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_JPEG); + + return ResponseEntity.ok() + .header("filename", quickRef.getFilename()) + .body(new ByteArrayResource(thumbnailBlob)); + } + + List typeToGenerateThumbnail = List.of( + "image/jpeg", + "image/png", + "application/pdf" + ); + + if(typeToGenerateThumbnail.contains(quickRef.getMimetype())){ + Pair pair = fileService.loadFile(id, skey, filename); + if (pair.getLeft() != null && pair.getRight() != null) { + File file = pair.getLeft(); + byte[] fileByte = pair.getRight(); + + if (file.getMimetype().equals("image/jpeg") + || file.getMimetype().equals("image/png") + ) { + int limit = 500; + BufferedImage image = ImageIO.read(new ByteArrayInputStream(fileByte)); + //BufferedImage image = ImageIO.read(new ByteArrayInputStream(unzippedData)); + int width = image.getWidth(); + int height = image.getHeight(); + if (width > height) { + if (width > limit) { + height = height * limit / width; + width = limit; + } + } else { + if (height > limit) { + width = width * limit / height; + height = limit; + } + } + image = scale(image, width, height); + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + ImageIO.write(image, "jpg", tmp); + tmp.close(); + + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(10, TimeUnit.DAYS)) + .header("filename", file.getFilename()) + .body(new ByteArrayResource(tmp.toByteArray())); + } + + if(file.getMimetype().equals("application/pdf") + ){ + BufferedImage thumbnailPdf = generateThumbnailFromPdf(fileByte); + //BufferedImage thumbnailPdf = generateThumbnailFromPdf(unzippedData); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(thumbnailPdf, "jpg", baos); + byte[] thumbnailBytes = baos.toByteArray(); + // Create an appropriate HTTP response with the thumbnail blob + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.IMAGE_JPEG); + + return ResponseEntity.ok() + .cacheControl(CacheControl.maxAge(10, TimeUnit.DAYS)) + .header("filename", file.getFilename()) + .body(new ByteArrayResource(thumbnailBytes)); + } + } + } + else{ + return null; + } + + return null; + } + + private BufferedImage generateThumbnailFromPdf(byte[] pdfBytes) throws IOException { + // Load the PDF document from the byte array + try (PDDocument document = Loader.loadPDF(pdfBytes)) { + // Create a PDF renderer + PDFRenderer pdfRenderer = new PDFRenderer(document); + + // Render the first page of the PDF to an image + BufferedImage pageImage = pdfRenderer.renderImageWithDPI(0, 300); // Adjust DPI as needed + + // Generate a thumbnail image by scaling down the page image + int thumbnailWidth = 200; // Adjust the thumbnail width as needed + int thumbnailHeight = (int) ((double) thumbnailWidth / pageImage.getWidth() * pageImage.getHeight()); + BufferedImage thumbnailImage = new BufferedImage(thumbnailWidth, thumbnailHeight, BufferedImage.TYPE_INT_RGB); + thumbnailImage.createGraphics().drawImage(pageImage.getScaledInstance(thumbnailWidth, thumbnailHeight, BufferedImage.SCALE_SMOOTH), 0, 0, null); + + return thumbnailImage; + } + } + + + static BufferedImage scale(BufferedImage originalImage, int w, int h) { + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + int x, y; + int ww = originalImage.getWidth(); + int hh = originalImage.getHeight(); + for (x = 0; x < w; x++) { + for (y = 0; y < h; y++) { + int col = originalImage.getRGB(x * ww / w, y * hh / h); + img.setRGB(x, y, col); + } + } + return img; + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/ReportController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/ReportController.java new file mode 100644 index 0000000..3be4051 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/ReportController.java @@ -0,0 +1,34 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.time.LocalDate; + +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.common.service.WordReportService; + +@RestController +@RequestMapping("/report") +public class ReportController { + + private WordReportService wordReportService; + + public ReportController(WordReportService wordReportService) { + this.wordReportService = wordReportService; + } + + @RequestMapping(value = "/xmlTest", method = RequestMethod.GET) + public ResponseEntity download(@RequestParam(defaultValue = "false") boolean modified) throws Exception { + + byte[] reportResult = wordReportService.generateTestXMLFile(); + + return ResponseEntity.ok() + .header("filename", "test_xml_report_" + LocalDate.now() + ".doc") + .body(new ByteArrayResource(reportResult)); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/SearchCriteriaTemplateController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/SearchCriteriaTemplateController.java new file mode 100644 index 0000000..5c71c5b --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/SearchCriteriaTemplateController.java @@ -0,0 +1,93 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.lioner.req.UpdateSearchCriteriaTemplateReq; +import com.ffii.lioner.modules.lioner.service.SearchCriteriaTemplateService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/template") +public class SearchCriteriaTemplateController{ + + private final Log logger = LogFactory.getLog(getClass()); + private SearchCriteriaTemplateService searchCriteriaTemplateService; + + public SearchCriteriaTemplateController( + SearchCriteriaTemplateService searchCriteriaTemplateService + ) { + this.searchCriteriaTemplateService = searchCriteriaTemplateService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateSearchCriteriaTemplateReq req) { + return new IdRes(searchCriteriaTemplateService.saveOrUpdate(req)); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + searchCriteriaTemplateService.markDeleteWithAuditLog(searchCriteriaTemplateService.find(id).get()); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(searchCriteriaTemplateService.search( + CriteriaArgsBuilder.withRequest(request) + .addStringLike("module") + .addStringLike("templateName") + .addInteger("userId") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> combo(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(searchCriteriaTemplateService.combo( + CriteriaArgsBuilder.withRequest(request) + .addString("module") + .addInteger("userId") + .build())); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, searchCriteriaTemplateService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping("/checkTemplateName") + public Map checkTemplateName(@RequestParam String name, @RequestParam Long userId, + @RequestParam(defaultValue = "-1") Long templateId, @RequestParam String module) { + boolean isTemplateNameTaken = searchCriteriaTemplateService.isTemplateNameTaken(name,userId,templateId,module); + return Map.of( + "isTaken", isTemplateNameTaken + ); + } +} + + + diff --git a/src/main/java/com/ffii/lioner/modules/lioner/web/TodoReminderController.java b/src/main/java/com/ffii/lioner/modules/lioner/web/TodoReminderController.java new file mode 100644 index 0000000..6cdd73d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/lioner/web/TodoReminderController.java @@ -0,0 +1,135 @@ +package com.ffii.lioner.modules.lioner.web; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.lioner.entity.TodoReminderEmailLog; +import com.ffii.lioner.modules.lioner.service.TodoReminderService; +import com.ffii.lioner.modules.common.LocalDateAdapter; +import com.ffii.lioner.modules.common.mail.pojo.MailRequest; +import com.ffii.lioner.modules.common.mail.service.MailService; +import com.ffii.lioner.modules.user.service.UserService; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import jakarta.servlet.http.HttpServletRequest; + +@RestController +@RequestMapping("/todo") +public class TodoReminderController{ + + private final Log logger = LogFactory.getLog(getClass()); + private TodoReminderService todoReminderService; + private MailService mailService; + + public TodoReminderController(TodoReminderService todoReminderService, MailService mailService) { + this.todoReminderService = todoReminderService; + this.mailService = mailService; + } + + @GetMapping("/application/{id}/{read}") + public RecordsRes> listApplication(@PathVariable Long id, @PathVariable Boolean read) throws ServletRequestBindingException { + return new RecordsRes<>(todoReminderService.list( + Map.of("userId", id, + //"reminderType", "application", + "read", read + ))); + } + + @GetMapping("/announcement/{id}/{read}") + public RecordsRes> listAnnouncement(@PathVariable Long id, @PathVariable Boolean read) throws ServletRequestBindingException { + return new RecordsRes<>(todoReminderService.list( + Map.of( + "userId", id, + "reminderType", "announcement", + "read", read + ))); + } + + @PostMapping("/markRead/{id}/{userId}") + public Map markRead(@PathVariable Long id, @PathVariable Long userId) { + return Map.of( + Params.DATA, todoReminderService.userRead(id,userId) + ); + } + + @PostMapping("/markSuppress/{id}/{userId}") + public Map markSuppress(@PathVariable Long id, @PathVariable Long userId) { + return Map.of( + Params.DATA, todoReminderService.suppressTodo(id,userId) + ); + } + + @GetMapping("/test") + public void test(HttpServletRequest request) throws UnsupportedEncodingException, ParseException, ServletRequestBindingException { + todoReminderService.sendTestEmail( + CriteriaArgsBuilder.withRequest(request) + .addString("templateName") + .addString("receiver") + .build() + ); + } + + @GetMapping("/testReminder") + public void testReminder(HttpServletRequest request) throws UnsupportedEncodingException, ParseException, ServletRequestBindingException { + todoReminderService.generateApplicationReminder(); + } + + @GetMapping("/testAnnouncement") + public void testAnnouncement(HttpServletRequest request) throws UnsupportedEncodingException, ParseException, ServletRequestBindingException { + todoReminderService.generateApplicationAnnouncement(); + } + + @GetMapping("/resendFailEmail") + public void resendFailEmail() throws ParseException{ + List list = todoReminderService.getFailEmailRecord(null); + logger.info("============= Resend Failed Email Start ============="); + logger.info("Fetched Total Number of Email: " + list.size()); + + for (TodoReminderEmailLog todoReminderEmailLog : list) { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(LocalDate.class, new LocalDateAdapter()); + Gson gson = gsonBuilder.create(); + MailRequest mailRequestObj = gson.fromJson(todoReminderEmailLog.getContent(), MailRequest.class); + logger.info("---------------------------------------------"); + logger.info("Try Resend for userId: " + todoReminderEmailLog.getUserId()); + logger.info("Try Resend for eventId: " + todoReminderEmailLog.getEventId()); + mailService.resendARS(mailRequestObj, todoReminderEmailLog.getId()); + } + logger.info("============= Resend Failed Email End ============="); + } + + /* + + //TEST USE ONLY START// + @GetMapping("/email") + public Map testEmail() throws Exception{ + todoReminderService.sendApplicationEmail(); + return Map.of("success", true); + } + + @GetMapping("/announceEmail") + public Map testEmail2() throws Exception{ + todoReminderService.sendAnnouncementEmail(); + return Map.of("success", true); + } + //TEST USE ONLY END// + + */ + +} diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/AppreciationCategory.java b/src/main/java/com/ffii/lioner/modules/master/entity/AppreciationCategory.java new file mode 100644 index 0000000..b4ab646 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/AppreciationCategory.java @@ -0,0 +1,29 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "apr_category") +public class AppreciationCategory extends BaseEntity{ + + @NotBlank + @Column + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/AppreciationCategoryRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/AppreciationCategoryRepository.java new file mode 100644 index 0000000..b141e4a --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/AppreciationCategoryRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface AppreciationCategoryRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/Category.java b/src/main/java/com/ffii/lioner/modules/master/entity/Category.java new file mode 100644 index 0000000..fc26dd4 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/Category.java @@ -0,0 +1,40 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "category") +public class Category extends BaseEntity{ + + @NotBlank + @Column + private String name; + + @Column + private String description; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + +} + + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/CategoryRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/CategoryRepository.java new file mode 100644 index 0000000..522ff00 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/CategoryRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface CategoryRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/ClientDepartment.java b/src/main/java/com/ffii/lioner/modules/master/entity/ClientDepartment.java new file mode 100644 index 0000000..1177be1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/ClientDepartment.java @@ -0,0 +1,29 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "apr_client_department") +public class ClientDepartment extends BaseEntity{ + + @NotBlank + @Column + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/ClientDepartmentRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/ClientDepartmentRepository.java new file mode 100644 index 0000000..d1eac24 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/ClientDepartmentRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface ClientDepartmentRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/Division.java b/src/main/java/com/ffii/lioner/modules/master/entity/Division.java new file mode 100644 index 0000000..4d95402 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/Division.java @@ -0,0 +1,40 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "division") +public class Division extends BaseEntity{ + + @NotBlank + @Column + private String name; + + @NotBlank + @Column + private String branch; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBranch() { + return this.branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/DivisionRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/DivisionRepository.java new file mode 100644 index 0000000..20f9b99 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/DivisionRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface DivisionRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/PromotionChannel.java b/src/main/java/com/ffii/lioner/modules/master/entity/PromotionChannel.java new file mode 100644 index 0000000..58abd27 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/PromotionChannel.java @@ -0,0 +1,29 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "promotion_channel") +public class PromotionChannel extends BaseEntity{ + + @NotBlank + @Column + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/PromotionChannelRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/PromotionChannelRepository.java new file mode 100644 index 0000000..370454d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/PromotionChannelRepository.java @@ -0,0 +1,7 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface PromotionChannelRepository extends AbstractRepository { +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/SBUDivision.java b/src/main/java/com/ffii/lioner/modules/master/entity/SBUDivision.java new file mode 100644 index 0000000..5c55f3d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/SBUDivision.java @@ -0,0 +1,29 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "apr_sbu") +public class SBUDivision extends BaseEntity{ + + @NotBlank + @Column + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/SBUDivisionRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/SBUDivisionRepository.java new file mode 100644 index 0000000..abca492 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/SBUDivisionRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface SBUDivisionRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/SubDivision.java b/src/main/java/com/ffii/lioner/modules/master/entity/SubDivision.java new file mode 100644 index 0000000..5359310 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/SubDivision.java @@ -0,0 +1,40 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import java.util.Objects; + +/** @author Jason Lam */ +@Entity +@Table(name = "sub_division") +public class SubDivision extends BaseEntity{ + + @NotBlank + @Column + private String name; + + @Column + private Long divisionId; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getDivisionId() { + return this.divisionId; + } + + public void setDivisionId(Long divisionId) { + this.divisionId = divisionId; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/SubDivisionRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/SubDivisionRepository.java new file mode 100644 index 0000000..5a8f9e3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/SubDivisionRepository.java @@ -0,0 +1,8 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface SubDivisionRepository extends AbstractRepository { +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/Tag.java b/src/main/java/com/ffii/lioner/modules/master/entity/Tag.java new file mode 100644 index 0000000..30ff134 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/Tag.java @@ -0,0 +1,29 @@ +package com.ffii.lioner.modules.master.entity; + +import com.ffii.core.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; + +/** @author Jason Lam */ +@Entity +@Table(name = "tag") +public class Tag extends BaseEntity{ + + @NotBlank + @Column + private String name; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } +} + + + diff --git a/src/main/java/com/ffii/lioner/modules/master/entity/TagRepository.java b/src/main/java/com/ffii/lioner/modules/master/entity/TagRepository.java new file mode 100644 index 0000000..c52b76d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/entity/TagRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.master.entity; +import com.ffii.core.support.AbstractRepository; + +public interface TagRepository extends AbstractRepository { +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/req/CheckDivisionReq.java b/src/main/java/com/ffii/lioner/modules/master/req/CheckDivisionReq.java new file mode 100644 index 0000000..15c509a --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/CheckDivisionReq.java @@ -0,0 +1,32 @@ +package com.ffii.lioner.modules.master.req; + +import java.util.List; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class CheckDivisionReq { + + @Size(max = 50) + private String division; + + @Size(max = 255) + private List subDivisionList; + + public String getDivision() { + return this.division; + } + + public void setDivision(String division) { + this.division = division; + } + + public List getSubDivisionList() { + return this.subDivisionList; + } + + public void setSubDivisionList(List subDivisionList) { + this.subDivisionList = subDivisionList; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateAppreciationCategoryReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateAppreciationCategoryReq.java new file mode 100644 index 0000000..a2f0949 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateAppreciationCategoryReq.java @@ -0,0 +1,31 @@ +package com.ffii.lioner.modules.master.req; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateAppreciationCategoryReq { + + private Long id; + + @Size(max = 255) + private String name; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateCategoryReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateCategoryReq.java new file mode 100644 index 0000000..fedc3e1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateCategoryReq.java @@ -0,0 +1,40 @@ +package com.ffii.lioner.modules.master.req; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateCategoryReq { + + private Long id; + + @Size(max = 50) + private String name; + + @Size(max = 255) + private String description; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateClientDepartmentReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateClientDepartmentReq.java new file mode 100644 index 0000000..2540140 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateClientDepartmentReq.java @@ -0,0 +1,31 @@ +package com.ffii.lioner.modules.master.req; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateClientDepartmentReq { + + private Long id; + + @Size(max = 255) + private String name; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateDivisionReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateDivisionReq.java new file mode 100644 index 0000000..d114909 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateDivisionReq.java @@ -0,0 +1,64 @@ +package com.ffii.lioner.modules.master.req; + +import java.util.List; + +import com.ffii.lioner.modules.master.entity.SubDivision; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateDivisionReq { + + private Long id; + + @Size(max = 255) + private String name; + + private String branch; + + private List subDivisionList; + + private List subDivisionDeleteList; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBranch() { + return this.branch; + } + + public void setBranch(String branch) { + this.branch = branch; + } + + public List getSubDivisionList() { + return this.subDivisionList; + } + + public void setSubDivisionList(List subDivisionList) { + this.subDivisionList = subDivisionList; + } + + public List getSubDivisionDeleteList() { + return this.subDivisionDeleteList; + } + + public void setSubDivisionDeleteList(List subDivisionDeleteList) { + this.subDivisionDeleteList = subDivisionDeleteList; + } + + +} diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdatePromotionChannelReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdatePromotionChannelReq.java new file mode 100644 index 0000000..3aaf62f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdatePromotionChannelReq.java @@ -0,0 +1,31 @@ +package com.ffii.lioner.modules.master.req; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdatePromotionChannelReq { + + private Long id; + + @Size(max = 30) + private String name; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateSBUDivisionReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateSBUDivisionReq.java new file mode 100644 index 0000000..821a924 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateSBUDivisionReq.java @@ -0,0 +1,31 @@ +package com.ffii.lioner.modules.master.req; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateSBUDivisionReq { + + private Long id; + + @Size(max = 255) + private String name; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateSubDivisionReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateSubDivisionReq.java new file mode 100644 index 0000000..35c101c --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateSubDivisionReq.java @@ -0,0 +1,35 @@ +package com.ffii.lioner.modules.master.req; +/** @author Jason Lam */ +public class UpdateSubDivisionReq { + + private Long id; + private String name; + private Long divisionId; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getDivisionId() { + return this.divisionId; + } + + public void setDivisionId(Long divisionId) { + this.divisionId = divisionId; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/req/UpdateTagReq.java b/src/main/java/com/ffii/lioner/modules/master/req/UpdateTagReq.java new file mode 100644 index 0000000..b1ad577 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/req/UpdateTagReq.java @@ -0,0 +1,32 @@ +package com.ffii.lioner.modules.master.req; + +import jakarta.validation.constraints.Size; + +/** @author Jason Lam */ +public class UpdateTagReq { + + private Long id; + + @Size(max = 50) + private String name; + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/service/AppreciationCategoryService.java b/src/main/java/com/ffii/lioner/modules/master/service/AppreciationCategoryService.java new file mode 100644 index 0000000..db438ef --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/AppreciationCategoryService.java @@ -0,0 +1,193 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.AppreciationCategory; +import com.ffii.lioner.modules.master.entity.AppreciationCategoryRepository; +import com.ffii.lioner.modules.master.req.UpdateAppreciationCategoryReq; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class AppreciationCategoryService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + + public AppreciationCategoryService(JdbcDao jdbcDao, AppreciationCategoryRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ac.id, " + + " ac.created, " + + " ac.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " ac.version, " + + " ac.modified, " + + " ac.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " ac.deleted, " + + " ac.name " + + " FROM apr_category ac " + + " LEFT JOIN `user` u1 ON u1.id = ac.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = ac.modifiedBy " + + " WHERE ac.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public void markDeleteWithAuditLog (AppreciationCategory instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public AppreciationCategory saveOrUpdate(UpdateAppreciationCategoryReq req) { + AppreciationCategory instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new AppreciationCategory(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req,instance); + instance = save(instance); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + return instance; + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " ac.id, " + + " ac.name " + + " FROM apr_category ac " + + " WHERE ac.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND ac.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND ac.name LIKE :name"); + } + sql.append(" ORDER BY ac.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listDepartmentCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " ac.id, " + + " ac.id as `key`, " + + " ac.name as label" + + " FROM apr_category ac " + + " WHERE ac.deleted = FALSE " + ); + + sql.append(" ORDER BY ac.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(ac.id) " + + " FROM apr_category ac " + + " WHERE ac.deleted = FALSE " + + " AND ac.name = :name " + + " AND ac.id != :id" + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " ac.id " + + " FROM apr_category ac " + + " WHERE ac.deleted = FALSE " + + " AND ac.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/service/CategoryService.java b/src/main/java/com/ffii/lioner/modules/master/service/CategoryService.java new file mode 100644 index 0000000..a82e236 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/CategoryService.java @@ -0,0 +1,209 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.Category; +import com.ffii.lioner.modules.master.entity.CategoryRepository; +import com.ffii.lioner.modules.master.entity.PromotionChannel; +import com.ffii.lioner.modules.master.req.UpdateCategoryReq; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class CategoryService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + public CategoryService(JdbcDao jdbcDao, CategoryRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " c.id, " + + " c.created, " + + " c.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " c.version, " + + " c.modified, " + + " c.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " c.deleted, " + + " c.name, " + + " c.description " + + " FROM category c " + + " LEFT JOIN `user` u1 ON u1.id = c.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = c.modifiedBy " + + " WHERE c.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List> getNameList(){ + StringBuilder sql = new StringBuilder("SELECT" + + " c.id, " + + " c.name " + + " FROM category c " + + " WHERE c.deleted = FALSE " + ); + + return jdbcDao.queryForList(sql.toString()); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " c.id " + + " FROM category c " + + " WHERE c.deleted = FALSE" + + " AND c.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } + + public Category saveOrUpdate(UpdateCategoryReq req) { + Category instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new Category(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + BeanUtils.copyProperties(req,instance); + instance = save(instance); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + return instance; + } + + public void markDeleteWithAuditLog (Category instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " c.id, " + + " c.name, " + + " c.description " + + " FROM category c " + + " WHERE c.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND c.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND c.name LIKE :name"); + if (args.containsKey("description")) sql.append(" AND c.description LIKE :description"); + + } + sql.append(" ORDER BY c.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listCategoryCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " c.id, " + + " c.id as `key`, " + + " c.name as label" + + " FROM category c " + + " WHERE c.deleted = FALSE " + ); + + sql.append(" ORDER BY c.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long categoryId) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(c.id) " + + " FROM category c " + + " WHERE c.deleted =FALSE " + + " AND c.name = :name " + + " AND c.id != :categoryId" + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "categoryId", categoryId)); + } +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/service/ClientDepartmentService.java b/src/main/java/com/ffii/lioner/modules/master/service/ClientDepartmentService.java new file mode 100644 index 0000000..b2b6064 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/ClientDepartmentService.java @@ -0,0 +1,193 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.ClientDepartment; +import com.ffii.lioner.modules.master.entity.ClientDepartmentRepository; +import com.ffii.lioner.modules.master.req.UpdateClientDepartmentReq; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class ClientDepartmentService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + + public ClientDepartmentService(JdbcDao jdbcDao, ClientDepartmentRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " acd.id, " + + " acd.created, " + + " acd.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " acd.version, " + + " acd.modified, " + + " acd.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " acd.deleted, " + + " acd.name " + + " FROM apr_client_department acd " + + " LEFT JOIN `user` u1 ON u1.id = acd.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = acd.modifiedBy " + + " WHERE acd.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public void markDeleteWithAuditLog (ClientDepartment instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public ClientDepartment saveOrUpdate(UpdateClientDepartmentReq req) { + ClientDepartment instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new ClientDepartment(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req,instance); + instance = save(instance); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + return instance; + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " acd.id, " + + " acd.name " + + " FROM apr_client_department acd " + + " WHERE acd.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND acd.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND acd.name LIKE :name"); + } + sql.append(" ORDER BY acd.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listDepartmentCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " acd.id, " + + " acd.id as `key`, " + + " acd.name as label" + + " FROM apr_client_department acd " + + " WHERE acd.deleted = FALSE " + ); + + sql.append(" ORDER BY acd.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(acd.id) " + + " FROM apr_client_department acd " + + " WHERE acd.deleted = FALSE " + + " AND acd.name = :name " + + " AND acd.id != :id" + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " acd.id " + + " FROM apr_client_department acd " + + " WHERE acd.deleted = FALSE " + + " AND acd.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/service/DivisionService.java b/src/main/java/com/ffii/lioner/modules/master/service/DivisionService.java new file mode 100644 index 0000000..7a8dd89 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/DivisionService.java @@ -0,0 +1,199 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.Division; +import com.ffii.lioner.modules.master.entity.DivisionRepository; +import com.ffii.lioner.modules.master.req.UpdateDivisionReq; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class DivisionService extends AbstractBaseEntityService { + + private SubDivisionService subDivisionService; + private AuditLogService auditLogService; + + public DivisionService(JdbcDao jdbcDao, DivisionRepository repository, SubDivisionService subDivisionService, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.subDivisionService = subDivisionService; + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " d.id, " + + " d.created, " + + " d.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " d.version, " + + " d.modified, " + + " d.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " d.deleted, " + + " d.name, " + + " d.branch " + + " FROM division d " + + " LEFT JOIN `user` u1 ON u1.id = d.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = d.modifiedBy " + + " WHERE d.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + + public Map saveOrUpdate(UpdateDivisionReq req) { + Division instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new Division(); + } + + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", subDivisionService.listSubdivisionName(input)); + logData.put("subDivisionIds", subDivisionService.listSubdivisionId(input)); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req,instance); + instance = save(instance); + List subDivisionIdList = subDivisionService.saveOrUpdateList(req.getSubDivisionList(), instance.getId()); + List deletedList = subDivisionService.deleteByIdList(req.getSubDivisionDeleteList()); + + Long id = instance.getId(); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", subDivisionService.listSubdivisionName(input)); + logData.put("subDivisionIds", subDivisionService.listSubdivisionId(input)); + newValueObject = logData; + } + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + id, + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + return Map.of( + "id", instance.getId(), + "subDivisionId", subDivisionIdList, + "deletedSubId", deletedList + ); + } + + public Map softDeleteWithSubDivision(Long id){ + Division instance = this.find(id).orElseThrow(NotFoundException::new); + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", subDivisionService.listSubdivisionName(input)); + logData.put("subDivisionIds", subDivisionService.listSubdivisionId(input)); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + this.markDelete(instance); + subDivisionService.deleteByDivision(id); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("subDivision", subDivisionService.listSubdivisionName(input)); + logData.put("subDivisionIds", subDivisionService.listSubdivisionId(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + id, + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + return Map.of( + "id", id + ); + } + + public List> listDivision(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " d.id, " + + " d.name, " + + " d.branch, " + + " '' as subDivision " + + " FROM division d " + + " WHERE d.deleted = FALSE " + ); + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND d.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND d.name LIKE :name"); + if (args.containsKey("branch")) sql.append(" AND d.branch LIKE :branch"); + } + sql.append(" ORDER BY d.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public String isNameTaken(Map arg) { + StringBuilder sql = new StringBuilder("SELECT" + + " d.name " + + " FROM division d " + + " WHERE d.deleted = FALSE " + + " AND d.name = :division " + + " AND d.id != :divisionId" + ); + + return jdbcDao.queryForString(sql.toString(), arg); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/master/service/PromotionChannelService.java b/src/main/java/com/ffii/lioner/modules/master/service/PromotionChannelService.java new file mode 100644 index 0000000..50ec987 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/PromotionChannelService.java @@ -0,0 +1,236 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.PromotionChannel; +import com.ffii.lioner.modules.master.entity.PromotionChannelRepository; +import com.ffii.lioner.modules.master.req.UpdatePromotionChannelReq; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class PromotionChannelService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + + public PromotionChannelService(JdbcDao jdbcDao, PromotionChannelRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " pc.id, " + + " pc.created, " + + " pc.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " pc.version, " + + " pc.modified, " + + " pc.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " pc.deleted, " + + " pc.name " + + " FROM promotion_channel pc " + + " LEFT JOIN `user` u1 ON u1.id = pc.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = pc.modifiedBy " + + " WHERE pc.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List> getNameList(){ + StringBuilder sql = new StringBuilder("SELECT" + + " pc.id, " + + " pc.name " + + " FROM promotion_channel pc " + + " WHERE pc.deleted = FALSE " + ); + + return jdbcDao.queryForList(sql.toString()); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " pc.id " + + " FROM promotion_channel pc " + + " WHERE pc.deleted = FALSE" + + " AND pc.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } + + public void markDeleteWithAuditLog (PromotionChannel instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public PromotionChannel saveOrUpdate(UpdatePromotionChannelReq req) { + PromotionChannel instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new PromotionChannel(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req,instance); + instance = save(instance); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + return instance; + } + + public List> listSubPromotionChannel(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id, " + + " sd.name " + + " FROM PromotionChannel d " + + " LEFT JOIN sub_PromotionChannel sd on sd.PromotionChannelId = d.id " + + " WHERE d.id = :id " + ); + + sql.append(" ORDER BY sd.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " pc.id, " + + " pc.name " + + " FROM promotion_channel pc " + + " WHERE pc.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND pc.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND pc.name LIKE :name"); + } + sql.append(" ORDER BY pc.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listChannelCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " pc.id, " + + " pc.id as `key`, " + + " pc.name as label" + + " FROM promotion_channel pc " + + " WHERE pc.deleted = FALSE " + ); + + sql.append(" ORDER BY pc.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public Map getChannelById(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " pc.id, " + + " pc.id as `key`, " + + " pc.name as label" + + " FROM promotion_channel pc " + + " WHERE pc.deleted = FALSE " + + " AND pc.id = :id" + ); + + sql.append(" ORDER BY pc.name asc"); + + return jdbcDao.queryForMap(sql.toString(), args).get(); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(pc.id) " + + " FROM promotion_channel pc " + + " WHERE pc.deleted =FALSE " + + " AND pc.name = :name " + + " AND pc.id != :id" + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/service/SBUDivisionService.java b/src/main/java/com/ffii/lioner/modules/master/service/SBUDivisionService.java new file mode 100644 index 0000000..af62e73 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/SBUDivisionService.java @@ -0,0 +1,204 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.SBUDivision; +import com.ffii.lioner.modules.master.entity.SBUDivisionRepository; +import com.ffii.lioner.modules.master.req.UpdateSBUDivisionReq; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class SBUDivisionService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + + public SBUDivisionService(JdbcDao jdbcDao, SBUDivisionRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " as2.id, " + + " as2.created, " + + " as2.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " as2.version, " + + " as2.modified, " + + " as2.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " as2.deleted, " + + " as2.name " + + " FROM apr_sbu as2 " + + " LEFT JOIN `user` u1 ON u1.id = as2.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = as2.modifiedBy " + + " WHERE as2.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public void markDeleteWithAuditLog (SBUDivision instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + + public SBUDivision saveOrUpdate(UpdateSBUDivisionReq req) { + SBUDivision instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new SBUDivision(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + + BeanUtils.copyProperties(req,instance); + instance = save(instance); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + + return instance; + } + + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " as2.id, " + + " as2.name " + + " FROM apr_sbu as2 " + + " WHERE as2.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND as2.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND as2.name LIKE :name"); + } + sql.append(" ORDER BY as2.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listDepartmentCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " as2.id, " + + " as2.id as `key`, " + + " as2.name as label" + + " FROM apr_sbu as2 " + + " WHERE as2.deleted = FALSE " + ); + + sql.append(" ORDER BY as2.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public String getDivisionNameById(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " as2.name " + + " FROM apr_sbu as2 " + + " WHERE as2.deleted = FALSE " + + " AND as2.id = :id " + ); + + return jdbcDao.queryForString(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(as2.id) " + + " FROM apr_sbu as2 " + + " WHERE as2.deleted = FALSE " + + " AND as2.name = :name " + + " AND as2.id != :id" + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " as2.id " + + " FROM apr_sbu as2 " + + " WHERE as2.deleted = FALSE" + + " AND as2.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } +} + diff --git a/src/main/java/com/ffii/lioner/modules/master/service/SubDivisionService.java b/src/main/java/com/ffii/lioner/modules/master/service/SubDivisionService.java new file mode 100644 index 0000000..9ff44a1 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/SubDivisionService.java @@ -0,0 +1,237 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.master.entity.SubDivision; +import com.ffii.lioner.modules.master.entity.SubDivisionRepository; +import com.ffii.lioner.modules.master.req.UpdateSubDivisionReq; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.Params; + +@Service +public class SubDivisionService extends AbstractBaseEntityService { + + public SubDivisionService(JdbcDao jdbcDao, SubDivisionRepository repository) { + super(jdbcDao, repository); + } + + public List saveOrUpdateList(List subDivisions, Long divisionId) { + SubDivision instance; + List subDivisionIdList = new ArrayList<>(); + + for(UpdateSubDivisionReq subDivision: subDivisions){ + if(subDivision.getId()>0){ + instance = find(subDivision.getId()).get(); + } + else{ + instance = new SubDivision(); + } + BeanUtils.copyProperties(subDivision,instance); + instance.setDivisionId(divisionId); + instance = save(instance); + subDivisionIdList.add(instance.getId()); + } + + return subDivisionIdList; + } + + public List deleteByIdList(List subDivisionDeleteList) { + SubDivision instance; + List subDivisionIdList = new ArrayList<>(); + for(Long subDivisionId: subDivisionDeleteList){ + instance = find(subDivisionId).get(); + instance.setDeleted(true); + instance = save(instance); + subDivisionIdList.add(instance.getId()); + } + + return subDivisionIdList; + } + + public List deleteByDivision(Long divisionId) { + List subDivisionList = this.listSubdivisionObject(Map.of("id", divisionId)); + //idList + + List idList = new ArrayList<>(); + + for(SubDivision subDivision: subDivisionList){ + SubDivision instance = this.find(subDivision.getId()).get(); + instance.setDeleted(true); + instance = save(instance); + idList.add(instance.getId()); + } + + return idList; + } + + public List> getNameList(){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id, " + + " sd.name " + + " FROM division d " + + " JOIN sub_division sd on sd.divisionId = d.id " + + " WHERE sd.deleted = FALSE " + ); + + return jdbcDao.queryForList(sql.toString()); + } + + public List> listSubdivision(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id, " + + " sd.name " + + " FROM division d " + + " JOIN sub_division sd on sd.divisionId = d.id " + + " WHERE sd.deleted = FALSE" + ); + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND sd.divisionId = :id"); + if (args.containsKey("subDivisionName")) sql.append(" AND sd.name LIKE :subDivisionName"); + } + sql.append(" ORDER BY sd.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List listSubdivisionId(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id " + + " FROM division d " + + " JOIN sub_division sd on sd.divisionId = d.id " + + " WHERE sd.deleted = FALSE" + ); + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND sd.divisionId = :id"); + } + sql.append(" ORDER BY sd.id"); + + return jdbcDao.queryForInts(sql.toString(), args); + } + + public List listSubdivisionName(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.name " + + " FROM division d " + + " JOIN sub_division sd on sd.divisionId = d.id " + + " WHERE sd.deleted = FALSE" + ); + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND sd.divisionId = :id"); + } + sql.append(" ORDER BY sd.id"); + + return jdbcDao.queryForStrings(sql.toString(), args); + } + + public List> listSubdivisionCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id, " + + " sd.id as `key`, " + + " sd.name as label, " + + " d.id as divisionId " + + " FROM division d " + + " JOIN sub_division sd on sd.divisionId = d.id " + + " WHERE sd.deleted = FALSE" + ); + + if (args != null) { + if (args.containsKey("divisionIds")) + sql.append(" AND sd.divisionId IN (:divisionIds)"); + } + + sql.append(" ORDER BY sd.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listSubdivisionComboForApplication(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id, " + + " sd.id as `key`, " + + " sd.name as label " + + " FROM event_sub_division esd " + + " JOIN sub_division sd on esd.subDivisionId = sd.id " + + " WHERE sd.deleted = FALSE " + + " AND esd.eventId = :eventId " + ); + + sql.append(" ORDER BY sd.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listSubdivisionComboForAward(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id, " + + " sd.id as `key`, " + + " sd.name as label " + + " FROM application_sub_division asd " + + " JOIN sub_division sd on asd.subDivisionId = sd.id " + + " WHERE sd.deleted = FALSE " + + " AND asd.applicationId = :applicationId " + ); + + sql.append(" ORDER BY sd.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + + public List> listDivisionCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " d.id, " + + " d.id as `key`, " + + " d.name as label " + + " FROM division d " + + " WHERE d.deleted = FALSE " + ); + sql.append(" ORDER BY d.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List listSubdivisionObject(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id " + + " FROM sub_division sd " + + " WHERE sd.deleted = FALSE " + + " AND sd.divisionId = :id " + ); + return jdbcDao.queryForList(sql.toString(), args, SubDivision.class); + } + + public String isNameTaken(Map args) { + if (args.containsKey("subDivisionList")){ + StringBuilder sql = new StringBuilder("SELECT" + + " GROUP_CONCAT(sd.name SEPARATOR ', ') " + + " FROM sub_division sd " + + " WHERE sd.deleted = FALSE " + + " AND sd.name in (:subDivisionList) " + + " AND sd.divisionId != :divisionId" + ); + + return jdbcDao.queryForString(sql.toString(), args); + } + else{ + return null; + } + + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " sd.id " + + " FROM sub_division sd " + + " WHERE sd.deleted = FALSE" + + " AND sd.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/master/service/TagService.java b/src/main/java/com/ffii/lioner/modules/master/service/TagService.java new file mode 100644 index 0000000..ff16788 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/service/TagService.java @@ -0,0 +1,204 @@ +package com.ffii.lioner.modules.master.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.master.entity.Tag; +import com.ffii.lioner.modules.master.entity.TagRepository; +import com.ffii.lioner.modules.master.req.UpdateTagReq; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; + +@Service +public class TagService extends AbstractBaseEntityService { + + private AuditLogService auditLogService; + public TagService(JdbcDao jdbcDao, TagRepository repository, AuditLogService auditLogService) { + super(jdbcDao, repository); + this.auditLogService = auditLogService; + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " t.id, " + + " t.created, " + + " t.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " t.version, " + + " t.modified, " + + " t.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " t.deleted, " + + " t.name " + + " FROM tag t " + + " LEFT JOIN `user` u1 ON u1.id = t.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = t.modifiedBy " + + " WHERE t.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List> getNameList(){ + StringBuilder sql = new StringBuilder("SELECT" + + " t.id, " + + " t.name " + + " FROM tag t " + + " WHERE t.deleted = FALSE " + ); + + return jdbcDao.queryForList(sql.toString()); + } + + public Integer getIdByName(String name) { + StringBuilder sql = new StringBuilder("SELECT" + + " t.id " + + " FROM tag t " + + " WHERE t.deleted = FALSE" + + " AND t.name = :name " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("name", name)); + } + + public Tag saveOrUpdate(UpdateTagReq req) { + Tag instance; + if(req.getId()>0){ + instance = find(req.getId()).get(); + } + else{ + instance = new Tag(); + } + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + BeanUtils.copyProperties(req,instance); + instance = save(instance); + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + return instance; + } + + public void markDeleteWithAuditLog(Tag instance){ + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + oldValueObject = logData; + } + //=====GET OLD AUDIT LOG=====// + markDelete(instance.getId()); + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + //=====GET NEW AUDIT LOG=====// + } + public List> list(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " t.id, " + + " t.name " + + " FROM tag t " + + " WHERE t.deleted = FALSE " + ); + + if (args != null) { + if (args.containsKey(Params.ID)) sql.append(" AND t.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND t.name LIKE :name"); + } + sql.append(" ORDER BY t.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listTagCombo(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " t.id, " + + " t.id as `key`, " + + " t.name as label" + + " FROM tag t " + + " WHERE t.deleted = FALSE " + ); + + sql.append(" ORDER BY t.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(t.id) " + + " FROM tag t " + + " WHERE t.deleted =FALSE " + + " AND t.name = :name " + + " AND t.id != :id " + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/web/AppreciationCategoryController.java b/src/main/java/com/ffii/lioner/modules/master/web/AppreciationCategoryController.java new file mode 100644 index 0000000..c69a30d --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/AppreciationCategoryController.java @@ -0,0 +1,88 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.req.UpdateAppreciationCategoryReq; +import com.ffii.lioner.modules.master.service.AppreciationCategoryService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/appreciationCategory") +public class AppreciationCategoryController{ + + private final Log logger = LogFactory.getLog(getClass()); + private AppreciationCategoryService appreciationCategoryService; + + public AppreciationCategoryController( + AppreciationCategoryService appreciationCategoryService + ) { + this.appreciationCategoryService = appreciationCategoryService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateAppreciationCategoryReq req) { + return new IdRes(appreciationCategoryService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + appreciationCategoryService.markDeleteWithAuditLog(appreciationCategoryService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, appreciationCategoryService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping + public RecordsRes> getChannelList(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(appreciationCategoryService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addStringLike("name") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> getChannelCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(appreciationCategoryService.listDepartmentCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = appreciationCategoryService.isNameTaken(name,id); + return Map.of( + "isTaken", isNameTaken + ); + } +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/web/CategoryController.java b/src/main/java/com/ffii/lioner/modules/master/web/CategoryController.java new file mode 100644 index 0000000..1d96141 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/CategoryController.java @@ -0,0 +1,90 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.req.UpdateCategoryReq; +import com.ffii.lioner.modules.master.service.CategoryService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/category") +public class CategoryController{ + + private final Log logger = LogFactory.getLog(getClass()); + private CategoryService categoryService; + + public CategoryController( + CategoryService categoryService + ) { + this.categoryService = categoryService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateCategoryReq req) { + return new IdRes(categoryService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + categoryService.markDeleteWithAuditLog(categoryService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, categoryService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping + public RecordsRes> getChannelList(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(categoryService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addStringLike("name") + .addStringLike("description") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> listCategoryCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(categoryService.listCategoryCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long categoryId) { + boolean isNameTaken = categoryService.isNameTaken(name,categoryId); + return Map.of( + "isTaken", isNameTaken + ); + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/web/ClientDepartmentController.java b/src/main/java/com/ffii/lioner/modules/master/web/ClientDepartmentController.java new file mode 100644 index 0000000..cd2b192 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/ClientDepartmentController.java @@ -0,0 +1,88 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.req.UpdateClientDepartmentReq; +import com.ffii.lioner.modules.master.service.ClientDepartmentService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/clientDepartment") +public class ClientDepartmentController{ + + private final Log logger = LogFactory.getLog(getClass()); + private ClientDepartmentService clientDepartmentService; + + public ClientDepartmentController( + ClientDepartmentService clientDepartmentService + ) { + this.clientDepartmentService = clientDepartmentService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateClientDepartmentReq req) { + return new IdRes(clientDepartmentService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + clientDepartmentService.markDeleteWithAuditLog(clientDepartmentService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, clientDepartmentService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping + public RecordsRes> getChannelList(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(clientDepartmentService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addStringLike("name") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> getChannelCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(clientDepartmentService.listDepartmentCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = clientDepartmentService.isNameTaken(name,id); + return Map.of( + "isTaken", isNameTaken + ); + } +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/web/DivisionController.java b/src/main/java/com/ffii/lioner/modules/master/web/DivisionController.java new file mode 100644 index 0000000..0ce8a9f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/DivisionController.java @@ -0,0 +1,195 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.entity.Division; +import com.ffii.lioner.modules.master.entity.SubDivision; +import com.ffii.lioner.modules.master.req.UpdateDivisionReq; +import com.ffii.lioner.modules.master.service.DivisionService; +import com.ffii.lioner.modules.master.service.SubDivisionService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/division") +public class DivisionController{ + + private final Log logger = LogFactory.getLog(getClass()); + private DivisionService divisionService; + private SubDivisionService subDivisionService; + + public DivisionController( + DivisionService divisionService, + SubDivisionService subDivisionService + ) { + this.divisionService = divisionService; + this.subDivisionService = subDivisionService; + } + + @PostMapping("/save") + public Map saveOrUpdate(@RequestBody @Valid UpdateDivisionReq req) { + return Map.of( + Params.DATA,divisionService.saveOrUpdate(req) + ); + } + + @DeleteMapping("/{id}") + //@ResponseStatus(HttpStatus.NO_CONTENT) + public Map delete(@PathVariable Long id) { + return Map.of( + Params.DATA, divisionService.softDeleteWithSubDivision(id) + ); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, divisionService.find(id).orElseThrow(NotFoundException::new), + "subDivision", subDivisionService.listSubdivision(Map.of("id", id)) + ); + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + List> temp = divisionService.listDivision( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addString("branch") + .addStringLike(Params.NAME) + .build() + ); + + Map subDivisionSearch = CriteriaArgsBuilder.withRequest(request) + .addStringLike("subDivisionName") .addStringLike("subDivisionName") + .build(); + if(subDivisionSearch.containsKey("subDivisionName")){ + //apply sub division search + List> finalResult = new ArrayList>(); + + for (int i=0 ; i < temp.size(); i++) { + Map record = temp.get(i); + List> subRecord = subDivisionService.listSubdivision( + Map.of( + "id", record.get("id"), + "subDivisionName", subDivisionSearch.get("subDivisionName") + ) + ); + if(subRecord.size() > 0){ + //sub division list have size + record.put("subDivision", subRecord); + finalResult.add(record); + } + } + return new RecordsRes<>(finalResult); + } + else{ + //get all record directly + for (int i=0 ; i < temp.size(); i++) { + Map record = temp.get(i); + record.put("subDivision", subDivisionService.listSubdivision(Map.of("id", record.get("id")))); + temp.set(i, record); + } + return new RecordsRes<>(temp); + + } + + } + + @GetMapping("/fromSubDiv") + public Map getDivisionBySubDivision(@RequestParam Long id) throws ServletRequestBindingException { + SubDivision temp = subDivisionService.find(id).get(); + Division output = divisionService.find(temp.getDivisionId()).get(); + return Map.of("data",output); + } + + @GetMapping("/combo") + public RecordsRes> getDivisionCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(subDivisionService.listDivisionCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/subdivision") + public RecordsRes> getSubDivisionById(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(subDivisionService.listSubdivision( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .build())); + } + + @GetMapping("/subdivision/combo") + public RecordsRes> getSubDivisionCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(subDivisionService.listSubdivisionCombo( + CriteriaArgsBuilder.withRequest(request) + .addIntegerList("divisionIds") + .build())); + } + + @GetMapping("/subdivision/event/combo") + public RecordsRes> listSubdivisionComboForApplication(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(subDivisionService.listSubdivisionComboForApplication( + CriteriaArgsBuilder.withRequest(request) + .addIntegerList("eventId") + .build())); + } + + @GetMapping("/subdivision/application/combo") + public RecordsRes> listSubdivisionComboForAward(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(subDivisionService.listSubdivisionComboForAward( + CriteriaArgsBuilder.withRequest(request) + .addIntegerList("applicationId") + .build())); + } + + + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(HttpServletRequest request) throws ServletRequestBindingException { + String isSubDivNameTaken = subDivisionService.isNameTaken( + CriteriaArgsBuilder.withRequest(request) + .addStringList("subDivisionList") + .addInteger("divisionId") + .build()); + + String isDivNameTaken = divisionService.isNameTaken( + CriteriaArgsBuilder.withRequest(request) + .addString("division") + .addInteger("divisionId") + .build()); + boolean valid = true; + + if(isDivNameTaken != "" || isSubDivNameTaken != null){ + valid = false; + } + return Map.of( + "valid", valid, + "duplicatedDivision", isDivNameTaken, + "duplicatedSubDivision", isSubDivNameTaken == null ? "" : isSubDivNameTaken + ); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/master/web/PromotionChannelController.java b/src/main/java/com/ffii/lioner/modules/master/web/PromotionChannelController.java new file mode 100644 index 0000000..0ba5531 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/PromotionChannelController.java @@ -0,0 +1,88 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.req.UpdatePromotionChannelReq; +import com.ffii.lioner.modules.master.service.PromotionChannelService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/channel") +public class PromotionChannelController{ + + private final Log logger = LogFactory.getLog(getClass()); + private PromotionChannelService promotionChannelService; + + public PromotionChannelController( + PromotionChannelService promotionChannelService + ) { + this.promotionChannelService = promotionChannelService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdatePromotionChannelReq req) { + return new IdRes(promotionChannelService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + promotionChannelService.markDeleteWithAuditLog(promotionChannelService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, promotionChannelService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping + public RecordsRes> getChannelList(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(promotionChannelService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addStringLike("name") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> getChannelCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(promotionChannelService.listChannelCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = promotionChannelService.isNameTaken(name,id); + return Map.of( + "isTaken", isNameTaken + ); + } +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/web/SBUDivisionController.java b/src/main/java/com/ffii/lioner/modules/master/web/SBUDivisionController.java new file mode 100644 index 0000000..fabbb0f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/SBUDivisionController.java @@ -0,0 +1,88 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.req.UpdateSBUDivisionReq; +import com.ffii.lioner.modules.master.service.SBUDivisionService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/sbuDivision") +public class SBUDivisionController{ + + private final Log logger = LogFactory.getLog(getClass()); + private SBUDivisionService sbuDivisionService; + + public SBUDivisionController( + SBUDivisionService sbuDivisionService + ) { + this.sbuDivisionService = sbuDivisionService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateSBUDivisionReq req) { + return new IdRes(sbuDivisionService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + sbuDivisionService.markDeleteWithAuditLog(sbuDivisionService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, sbuDivisionService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping + public RecordsRes> getChannelList(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(sbuDivisionService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addStringLike("name") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> getChannelCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(sbuDivisionService.listDepartmentCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = sbuDivisionService.isNameTaken(name,id); + return Map.of( + "isTaken", isNameTaken + ); + } +} + + diff --git a/src/main/java/com/ffii/lioner/modules/master/web/TagController.java b/src/main/java/com/ffii/lioner/modules/master/web/TagController.java new file mode 100644 index 0000000..518903b --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/master/web/TagController.java @@ -0,0 +1,89 @@ +package com.ffii.lioner.modules.master.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.master.req.UpdateTagReq; +import com.ffii.lioner.modules.master.service.TagService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/tag") +public class TagController{ + + private final Log logger = LogFactory.getLog(getClass()); + private TagService tagService; + + public TagController( + TagService tagService + ) { + this.tagService = tagService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid UpdateTagReq req) { + return new IdRes(tagService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + tagService.markDeleteWithAuditLog(tagService.find(id).get()); + } + + @GetMapping("/{id}") + public Map get(@PathVariable Long id) { + return Map.of( + Params.DATA, tagService.find(id).orElseThrow(NotFoundException::new) + ); + } + + @GetMapping + public RecordsRes> getChannelList(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(tagService.list( + CriteriaArgsBuilder.withRequest(request) + .addInteger("id") + .addStringLike("name") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> getSubDivisionCombo(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(tagService.listTagCombo( + CriteriaArgsBuilder.withRequest(request) + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = tagService.isNameTaken(name,id); + return Map.of( + "isTaken", isNameTaken + ); + } + +} + + diff --git a/src/main/java/com/ffii/lioner/modules/settings/entity/Settings.java b/src/main/java/com/ffii/lioner/modules/settings/entity/Settings.java new file mode 100644 index 0000000..84d2483 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/settings/entity/Settings.java @@ -0,0 +1,74 @@ +package com.ffii.lioner.modules.settings.entity; + +import com.ffii.core.entity.IdEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; + +@Entity +@Table(name = "settings") +public class Settings extends IdEntity { + public static String TYPE_STRING = "string"; + public static String TYPE_INT = "integer"; + public static String TYPE_FLOAT = "float"; + public static String TYPE_BOOLEAN = "boolean"; + public static String TYPE_DATE = "date"; + public static String TYPE_TIME = "time"; + public static String TYPE_DATETIME = "datetime"; + // other "A/B" value must "A" or "B" + + //lowercase + public static String VALUE_BOOLEAN_TRUE = "true"; + public static String VALUE_BOOLEAN_FALSE = "false"; + + // TODO: pattern?? + + @NotNull + @Column + private String name; + + @NotNull + @Column + private String value; + + @Column + private String category; + + @Column + private String type; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/settings/entity/SettingsRepository.java b/src/main/java/com/ffii/lioner/modules/settings/entity/SettingsRepository.java new file mode 100644 index 0000000..506f046 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/settings/entity/SettingsRepository.java @@ -0,0 +1,12 @@ +package com.ffii.lioner.modules.settings.entity; + +import java.util.Optional; + +import org.springframework.data.repository.query.Param; + +import com.ffii.core.support.AbstractRepository; + +public interface SettingsRepository extends AbstractRepository { + + Optional findByName(@Param("name") String name); +} diff --git a/src/main/java/com/ffii/lioner/modules/settings/service/SettingsService.java b/src/main/java/com/ffii/lioner/modules/settings/service/SettingsService.java new file mode 100644 index 0000000..65fb70a --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/settings/service/SettingsService.java @@ -0,0 +1,219 @@ +package com.ffii.lioner.modules.settings.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.lioner.modules.settings.entity.Settings; +import com.ffii.lioner.modules.settings.entity.SettingsRepository; +import com.ffii.core.exception.InternalServerErrorException; +import com.ffii.core.support.AbstractIdEntityService; +import com.ffii.core.support.JdbcDao; + + +@Service +public class SettingsService extends AbstractIdEntityService { + + public SettingsService(JdbcDao jdbcDao, SettingsRepository repository) { + super(jdbcDao, repository); + } + + public Optional findByName(String name) { + return this.repository.findByName(name); + } + + public boolean validateType(String type, String value) { + if (StringUtils.isBlank(type)) return true; + + if (Settings.TYPE_STRING.equals(type)) return true; + + if (Settings.TYPE_BOOLEAN.equals(type)) { + return Settings.VALUE_BOOLEAN_TRUE.equals(value) || Settings.VALUE_BOOLEAN_FALSE.equals(value); + } + + if (Settings.TYPE_INT.equals(type)) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } + if (Settings.TYPE_FLOAT.equals(type)) { + try { + Float.parseFloat(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } + if (Settings.TYPE_DATE.equals(type)) { + try { + LocalDate.parse(value, DateTimeFormatter.ISO_DATE); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + if (Settings.TYPE_TIME.equals(type)) { + try { + LocalTime.parse(value, DateTimeFormatter.ISO_TIME); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + if (Settings.TYPE_DATETIME.equals(type)) { + try { + LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME); + return true; + } catch (DateTimeParseException e) { + return false; + } + } + + if (StringUtils.indexOf(type, "/") >= 0) { + for (String t : type.split("/")) { + if (t.equals(value)) return true; + } + return false; + } + + return false; + } + + @Transactional(rollbackFor = Exception.class) + public void update(String name, String value) { + Settings settings = this.findByName(name) + .orElseThrow(InternalServerErrorException::new); + if (!validateType(settings.getType(), value)) { + throw new InternalServerErrorException(); + } + settings.setValue(value); + this.save(settings); + } + + @Transactional(rollbackFor = Exception.class) + public void update(String name, LocalDate date) { + this.update(name, date.format(DateTimeFormatter.ISO_DATE)); + } + + @Transactional(rollbackFor = Exception.class) + public void update(String name, LocalDateTime datetime) { + this.update(name, datetime.format(DateTimeFormatter.ISO_DATE_TIME)); + } + + @Transactional(rollbackFor = Exception.class) + public void update(String name, LocalTime time) { + this.update(name, time.format(DateTimeFormatter.ISO_TIME)); + } + + public String getString(String name) { + return this.findByName(name) + .map(Settings::getValue) + .orElseThrow(InternalServerErrorException::new); + } + + public int getInt(String name) { + return this.findByName(name) + .map(Settings::getValue) + .map(v -> { + try { + return Integer.parseInt(v); + } catch (final NumberFormatException nfe) { + return null; + } + }) + .orElseThrow(InternalServerErrorException::new); + } + + public double getDouble(String name) { + return this.findByName(name) + .map(Settings::getValue) + .map(v -> { + try { + return Double.parseDouble(v); + } catch (final NumberFormatException nfe) { + return null; + } + }) + .orElseThrow(InternalServerErrorException::new); + } + + public boolean getBoolean(String name) { + return this.findByName(name) + .map(Settings::getValue) + .map(Settings.VALUE_BOOLEAN_TRUE::equals) + .orElseThrow(InternalServerErrorException::new); + } + + public LocalDate getDate(String name) { + return this.getDate(name, DateTimeFormatter.ISO_DATE); + } + + private LocalDate getDate(String name, DateTimeFormatter formatter) { + return this.findByName(name) + .map(Settings::getValue) + .map(v -> { + try { + return LocalDate.parse(v, formatter); + } catch (DateTimeParseException e) { + return null; + } + }) + .orElseThrow(InternalServerErrorException::new); + } + + public LocalDateTime getDatetime(String name) { + return this.getDatetime(name, DateTimeFormatter.ISO_DATE_TIME); + } + + private LocalDateTime getDatetime(String name, DateTimeFormatter formatter) { + return this.findByName(name) + .map(Settings::getValue) + .map(v -> { + try { + return LocalDateTime.parse(v, formatter); + } catch (DateTimeParseException e) { + return null; + } + }) + .orElseThrow(InternalServerErrorException::new); + } + + public LocalTime getTime(String name) { + return this.getTime(name, DateTimeFormatter.ISO_TIME); + } + + private LocalTime getTime(String name, DateTimeFormatter formatter) { + return this.findByName(name) + .map(Settings::getValue) + .map(v -> { + try { + return LocalTime.parse(v, formatter); + } catch (DateTimeParseException e) { + return null; + } + }) + .orElseThrow(InternalServerErrorException::new); + } + + public List> listSetting(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " * " + + " FROM settings s " + + " WHERE s.category = :category " + ); + return jdbcDao.queryForList(sql.toString(), args); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/settings/web/SettingsController.java b/src/main/java/com/ffii/lioner/modules/settings/web/SettingsController.java new file mode 100644 index 0000000..3362ca7 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/settings/web/SettingsController.java @@ -0,0 +1,130 @@ +package com.ffii.lioner.modules.settings.web; + +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.settings.entity.Settings; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.core.exception.BadRequestException; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@RestController +@RequestMapping("/settings") +public class SettingsController{ + + private SettingsService settingsService; + + public SettingsController(SettingsService settingsService) { + this.settingsService = settingsService; + } + + // @Operation(summary = "list system settings") + @GetMapping + // @PreAuthorize("hasAuthority('ADMIN')") + public List listAll() { + return this.settingsService.listAll(); + } + + // @Operation(summary = "update system setting") + @PatchMapping("/{name}") + // @PreAuthorize("hasAuthority('ADMIN')") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@PathVariable String name, @RequestBody @Valid UpdateReq body) { + Settings entity = this.settingsService.findByName(name) + .orElseThrow(NotFoundException::new); + if (!this.settingsService.validateType(entity.getType(), body.value)) { + throw new BadRequestException(); + } + + entity.setValue(body.value); + this.settingsService.save(entity); + } + + @GetMapping("/{name}") + public Map getSetting(@PathVariable String name) { + Settings entity = this.settingsService.findByName(name) + .orElseThrow(NotFoundException::new); + + return Map.of( + Params.DATA, entity.getValue() + ); + } + + @PutMapping("/save") + // @PreAuthorize("hasAuthority('ADMIN')") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void update(@RequestBody @Valid UpdateListReq req) { + for (Map setting : req.getSettingList()) { + Settings entity = this.settingsService.findByName(setting.get("name")) + .orElseThrow(NotFoundException::new); + + if (!this.settingsService.validateType(entity.getType(), setting.get("value"))) { + throw new BadRequestException(); + } + + entity.setValue(setting.get("value")); + this.settingsService.save(entity); + } + } + + + @GetMapping("/config") + public RecordsRes> getSystemConfig(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(settingsService.listSetting( + Map.of("category","settings") + )); + } + + @GetMapping("/passwordPolicy") + public RecordsRes> getPasswordConfig(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(settingsService.listSetting( + Map.of("category","passwordPolicy") + )); + } + + public static class UpdateListReq { + @NotNull + private List> settingList; + + public List> getSettingList() { + return settingList; + } + + public void setSettingList(List> settingList) { + this.settingList = settingList; + } + } + + public static class UpdateReq { + @NotBlank + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/src/main/java/com/ffii/lioner/modules/user/entity/Group.java b/src/main/java/com/ffii/lioner/modules/user/entity/Group.java new file mode 100644 index 0000000..f5343dc --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/entity/Group.java @@ -0,0 +1,37 @@ +package com.ffii.lioner.modules.user.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; + +import com.ffii.core.entity.BaseEntity; + +@Entity +@Table(name = "`group`") +public class Group extends BaseEntity { + + @NotNull + @Column + private String name; + + @Column + private String description; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/user/entity/GroupRepository.java b/src/main/java/com/ffii/lioner/modules/user/entity/GroupRepository.java new file mode 100644 index 0000000..e62e148 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/entity/GroupRepository.java @@ -0,0 +1,6 @@ +package com.ffii.lioner.modules.user.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface GroupRepository extends AbstractRepository { +} diff --git a/src/main/java/com/ffii/lioner/modules/user/entity/User.java b/src/main/java/com/ffii/lioner/modules/user/entity/User.java new file mode 100644 index 0000000..3d8418f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/entity/User.java @@ -0,0 +1,291 @@ +package com.ffii.lioner.modules.user.entity; + +import java.time.LocalDate; +import java.util.Collection; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.ffii.core.entity.BaseEntity; + +/** @author Terence */ +@Entity +@Table(name = "user") +public class User extends BaseEntity implements UserDetails { + + @NotBlank + //@Column(unique = true) + @Column + private String username; + + @JsonIgnore + @Column + private String password; + + // @NotNull + @Column + private Boolean locked = Boolean.FALSE; + + @NotBlank + @Column + private String name; + + @Column + private LocalDate expiryDate; + + @JsonIgnore + @Transient + private Collection authorities; + + @Column + private String locale; + + @Column + private String fullname; + + @Column + private String firstname; + + @Column + private String lastname; + + @Column + private String department; + + @Column + private String title; + + @Column + private String email; + + @Column + private String phone1; + + @Column + private String phone2; + + @Column + private String remarks; + + @Column + private boolean lotusNotesUser = false; + + @Column + private String post; + + @Column + private Long subDivisionId; + + @Column + private boolean reminderFlag; + + public boolean isLocked() { + return this.locked == null ? false : this.locked; + } + + // getter & setter + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public Boolean getLocked() { + return locked; + } + + public void setLocked(Boolean locked) { + this.locked = locked; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDate getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(LocalDate expiryDate) { + this.expiryDate = expiryDate; + } + + public void setAuthorities(Collection authorities) { + this.authorities = authorities; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public String getFullname() { + return fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone1() { + return phone1; + } + + public void setPhone1(String phone1) { + this.phone1 = phone1; + } + + public String getPhone2() { + return phone2; + } + + public void setPhone2(String phone2) { + this.phone2 = phone2; + } + + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + // override + + @Override + public Collection getAuthorities() { + return this.authorities; + } + + @Override + public String getPassword() { + return this.password; + } + + @Override + public String getUsername() { + return this.username; + } + + @Override + public boolean isAccountNonExpired() { + return this.getExpiryDate() == null || this.getExpiryDate().isAfter(LocalDate.now()); + } + + @Override + public boolean isAccountNonLocked() { + return !this.isLocked(); + } + + @JsonIgnore + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @JsonIgnore + @Override + public boolean isEnabled() { + return true; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + + public boolean isLotusNotesUser() { + return this.lotusNotesUser; + } + + public boolean getLotusNotesUser() { + return this.lotusNotesUser; + } + + public void setLotusNotesUser(boolean lotusNotesUser) { + this.lotusNotesUser = lotusNotesUser; + } + + public String getPost() { + return this.post; + } + + public void setPost(String post) { + this.post = post; + } + + public Long getSubDivisionId() { + return this.subDivisionId; + } + + public void setSubDivisionId(Long subDivisionId) { + this.subDivisionId = subDivisionId; + } + + public boolean isReminderFlag() { + return this.reminderFlag; + } + + public boolean getReminderFlag() { + return this.reminderFlag; + } + + public void setReminderFlag(boolean reminderFlag) { + this.reminderFlag = reminderFlag; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/entity/UserPasswordHistory.java b/src/main/java/com/ffii/lioner/modules/user/entity/UserPasswordHistory.java new file mode 100644 index 0000000..4f3f7d8 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/entity/UserPasswordHistory.java @@ -0,0 +1,60 @@ +package com.ffii.lioner.modules.user.entity; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Collection; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.ffii.core.entity.BaseEntity; + +/** @author Jason Lam */ +@Entity +@Table(name = "user_password_history") +public class UserPasswordHistory extends BaseEntity { + + @Column + private Long userId; + + @Column + private LocalDateTime date; + + @JsonIgnore + @Column + private String password; + + public Long getUserId() { + return this.userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public LocalDateTime getDate() { + return this.date; + } + + public void setDate(LocalDateTime date) { + this.date = date; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/entity/UserPasswordHistoryRepository.java b/src/main/java/com/ffii/lioner/modules/user/entity/UserPasswordHistoryRepository.java new file mode 100644 index 0000000..f31cdf3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/entity/UserPasswordHistoryRepository.java @@ -0,0 +1,7 @@ +package com.ffii.lioner.modules.user.entity; + +import com.ffii.core.support.AbstractRepository; + +public interface UserPasswordHistoryRepository extends AbstractRepository { + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/entity/UserRepository.java b/src/main/java/com/ffii/lioner/modules/user/entity/UserRepository.java new file mode 100644 index 0000000..a1b5771 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/entity/UserRepository.java @@ -0,0 +1,15 @@ +package com.ffii.lioner.modules.user.entity; + +import java.util.List; +import java.util.Optional; + +import org.springframework.data.repository.query.Param; + +import com.ffii.core.support.AbstractRepository; + +public interface UserRepository extends AbstractRepository { + + List findByName(@Param("name") String name); + + Optional findByUsernameAndDeletedFalse(String username); +} diff --git a/src/main/java/com/ffii/lioner/modules/user/req/NewLionerUserReq.java b/src/main/java/com/ffii/lioner/modules/user/req/NewLionerUserReq.java new file mode 100644 index 0000000..39bafd3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/req/NewLionerUserReq.java @@ -0,0 +1,270 @@ +package com.ffii.lioner.modules.user.req; + +import java.time.LocalDate; +import java.util.List; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** @author Jason */ +public class NewLionerUserReq { + + private Long id; + + @NotNull + private Boolean locked; + + @Size(max = 30) + @NotBlank + @Pattern(regexp = "^[A-Za-z0-9]+$") + private String username; + + private String password; + + private String name; + + @NotBlank + private String fullname; + private String firstname; + private String lastname; + private String title; + private String department; + private String phone1; + private String phone2; + private String post; + @NotBlank + private Long subDivisionId; + private LocalDate expiryDate; + private String locale; + private String remarks; + private Boolean lotusNotesUser; + private Boolean reminderFlag; + + @NotBlank + @Email + private String email; + + // @NotNull + private List addGroupIds; + // @NotNull + private List removeGroupIds; + + // @NotNull + private List addAuthIds; + // @NotNull + private List removeAuthIds; + + public Boolean isLotusNotesUser() { + return this.lotusNotesUser; + } + + public Boolean getLotusNotesUser() { + return this.lotusNotesUser; + } + + public Boolean isReminderFlag() { + return this.reminderFlag; + } + + public Boolean getReminderFlag() { + return this.reminderFlag; + } + + public void setReminderFlag(Boolean reminderFlag) { + this.reminderFlag = reminderFlag; + } + + public void setLotusNotesUser(Boolean lotusNotesUser) { + this.lotusNotesUser = lotusNotesUser; + } + + public Long getId() { + return this.id; + } + + public void setId(Long id) { + this.id = id; + } + + public Boolean getLocked() { + return locked; + } + + public void setLocked(Boolean locked) { + this.locked = locked; + } + + public LocalDate getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(LocalDate expiryDate) { + this.expiryDate = expiryDate; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstName(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + + public List getAddGroupIds() { + return this.addGroupIds; + } + + public void setAddGroupIds(List addGroupIds) { + this.addGroupIds = addGroupIds; + } + + public List getRemoveGroupIds() { + return this.removeGroupIds; + } + + public void setRemoveGroupIds(List removeGroupIds) { + this.removeGroupIds = removeGroupIds; + } + + public List getAddAuthIds() { + return this.addAuthIds; + } + + public void setAddAuthIds(List addAuthIds) { + this.addAuthIds = addAuthIds; + } + + public List getRemoveAuthIds() { + return this.removeAuthIds; + } + + public void setRemoveAuthIds(List removeAuthIds) { + this.removeAuthIds = removeAuthIds; + } + + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + + public Boolean isLocked() { + return this.locked; + } + + public String getFullname() { + return this.fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getTitle() { + return this.title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getPhone1() { + return this.phone1; + } + + public void setPhone1(String phone1) { + this.phone1 = phone1; + } + + public String getPhone2() { + return this.phone2; + } + + public void setPhone2(String phone2) { + this.phone2 = phone2; + } + + public String getPost() { + return this.post; + } + + public void setPost(String post) { + this.post = post; + } + + public Long getSubDivisionId() { + return this.subDivisionId; + } + + public void setSubDivisionId(Long subDivisionId) { + this.subDivisionId = subDivisionId; + } + + public String getUsername() { + return this.username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + +} + diff --git a/src/main/java/com/ffii/lioner/modules/user/req/NewPublicUserReq.java b/src/main/java/com/ffii/lioner/modules/user/req/NewPublicUserReq.java new file mode 100644 index 0000000..9b6d577 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/req/NewPublicUserReq.java @@ -0,0 +1,29 @@ +package com.ffii.lioner.modules.user.req; + +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** @author Alex */ +public class NewPublicUserReq extends UpdateUserReq { + + @Size(max = 30) + @Pattern(regexp = "^[A-Za-z0-9]+$") + private String username; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/com/ffii/lioner/modules/user/req/NewUserReq.java b/src/main/java/com/ffii/lioner/modules/user/req/NewUserReq.java new file mode 100644 index 0000000..2ffbc84 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/req/NewUserReq.java @@ -0,0 +1,21 @@ +package com.ffii.lioner.modules.user.req; + +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + +/** @author Alex */ +public class NewUserReq extends UpdateUserReq { + + @Size(max = 30) + @Pattern(regexp = "^[A-Za-z0-9]+$") + private String username; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/req/SaveGroupReq.java b/src/main/java/com/ffii/lioner/modules/user/req/SaveGroupReq.java new file mode 100644 index 0000000..654294b --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/req/SaveGroupReq.java @@ -0,0 +1,80 @@ +package com.ffii.lioner.modules.user.req; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; + +public class SaveGroupReq { + private Long id; + + @NotNull + private String name; + private String description; + + @NotNull + private List addUserIds; + @NotNull + private List removeUserIds; + + @NotNull + private List addAuthIds; + @NotNull + private List removeAuthIds; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public List getAddUserIds() { + return addUserIds; + } + + public void setAddUserIds(List addUserIds) { + this.addUserIds = addUserIds; + } + + public List getRemoveUserIds() { + return removeUserIds; + } + + public void setRemoveUserIds(List removeUserIds) { + this.removeUserIds = removeUserIds; + } + + public List getAddAuthIds() { + return addAuthIds; + } + + public void setAddAuthIds(List addAuthIds) { + this.addAuthIds = addAuthIds; + } + + public List getRemoveAuthIds() { + return removeAuthIds; + } + + public void setRemoveAuthIds(List removeAuthIds) { + this.removeAuthIds = removeAuthIds; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/req/SearchUserReq.java b/src/main/java/com/ffii/lioner/modules/user/req/SearchUserReq.java new file mode 100644 index 0000000..74ca2d3 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/req/SearchUserReq.java @@ -0,0 +1,132 @@ +package com.ffii.lioner.modules.user.req; + +public class SearchUserReq { + private Integer id; + private Integer groupId; + private String username; + private String name; + private Boolean locked; + + private Integer start; + private Integer limit; + + private String fullname; + private String post; + private Integer subDivisionId; + private String email; + private String phone; + private Boolean isLotusNotesUser; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getStart() { + return start; + } + + public void setStart(Integer start) { + this.start = start; + } + + public Integer getLimit() { + return limit; + } + + public void setLimit(Integer limit) { + this.limit = limit; + } + + public Boolean getLocked() { + return locked; + } + + public void setLocked(Boolean locked) { + this.locked = locked; + } + + public Boolean isLocked() { + return this.locked; + } + + public String getFullname() { + return this.fullname; + } + + public void setFullname(String fullname) { + this.fullname = fullname; + } + + public String getPost() { + return this.post; + } + + public void setPost(String post) { + this.post = post; + } + + public Integer getSubDivisionId() { + return this.subDivisionId; + } + + public void setSubDivisionId(Integer subDivisionId) { + this.subDivisionId = subDivisionId; + } + + public String getEmail() { + return this.email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return this.phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public Boolean isIsLotusNotesUser() { + return this.isLotusNotesUser; + } + + public Boolean getIsLotusNotesUser() { + return this.isLotusNotesUser; + } + + public void setIsLotusNotesUser(Boolean isLotusNotesUser) { + this.isLotusNotesUser = isLotusNotesUser; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/req/UpdateUserReq.java b/src/main/java/com/ffii/lioner/modules/user/req/UpdateUserReq.java new file mode 100644 index 0000000..e132907 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/req/UpdateUserReq.java @@ -0,0 +1,151 @@ +package com.ffii.lioner.modules.user.req; + +import java.time.LocalDate; +import java.util.List; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** @author Alex */ +public class UpdateUserReq { + + @NotNull + private Boolean locked; + + @Size(max = 90) + @NotBlank + private String name; + + private String firstname; + private String lastname; + private LocalDate expiryDate; + private String locale; + private String remarks; + + @NotBlank + @Email + private String email; + @NotBlank + private String department; + + // @NotNull + private List addGroupIds; + // @NotNull + private List removeGroupIds; + + // @NotNull + private List addAuthIds; + // @NotNull + private List removeAuthIds; + + public Boolean getLocked() { + return locked; + } + + public void setLocked(Boolean locked) { + this.locked = locked; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public LocalDate getExpiryDate() { + return expiryDate; + } + + public void setExpiryDate(LocalDate expiryDate) { + this.expiryDate = expiryDate; + } + + public String getFirstname() { + return firstname; + } + + public void setFirstName(String firstname) { + this.firstname = firstname; + } + + public String getLastname() { + return lastname; + } + + public void setLastname(String lastname) { + this.lastname = lastname; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public void setFirstname(String firstname) { + this.firstname = firstname; + } + + public List getAddGroupIds() { + return addGroupIds; + } + + public void setAddGroupIds(List addGroupIds) { + this.addGroupIds = addGroupIds; + } + + public List getRemoveGroupIds() { + return removeGroupIds; + } + + public void setRemoveGroupIds(List removeGroupIds) { + this.removeGroupIds = removeGroupIds; + } + + public List getAddAuthIds() { + return addAuthIds; + } + + public void setAddAuthIds(List addAuthIds) { + this.addAuthIds = addAuthIds; + } + + public List getRemoveAuthIds() { + return removeAuthIds; + } + + public void setRemoveAuthIds(List removeAuthIds) { + this.removeAuthIds = removeAuthIds; + } + + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getDepartment() { + return department; + } + + public void setDepartment(String department) { + this.department = department; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/service/GroupService.java b/src/main/java/com/ffii/lioner/modules/user/service/GroupService.java new file mode 100644 index 0000000..36f2727 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/GroupService.java @@ -0,0 +1,365 @@ +package com.ffii.lioner.modules.user.service; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.ffii.core.utils.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.user.entity.Group; +import com.ffii.lioner.modules.user.entity.GroupRepository; +import com.ffii.lioner.modules.user.req.SaveGroupReq; +import com.ffii.core.exception.InternalServerErrorException; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; + +import jakarta.persistence.Table; +import jakarta.validation.Valid; + + +@Service +public class GroupService extends AbstractBaseEntityService { + + @Autowired + private AuditLogService auditLogService; + + public GroupService(JdbcDao jdbcDao, GroupRepository repository) { + super(jdbcDao, repository); + } + + public List> search(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " g.*" + + " FROM `group` g" + + " WHERE g.deleted = FALSE"); + + if (args != null) { + if (args.containsKey(Params.QUERY)) sql.append(" AND (g.name LIKE :query)"); + if (args.containsKey(Params.ID)) sql.append(" AND g.id = :id"); + if (args.containsKey(Params.NAME)) sql.append(" AND g.name LIKE :name"); + if (args.containsKey("description")) sql.append(" AND g.description LIKE :description"); + } + + sql.append(" ORDER BY g.name"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " g.id, " + + " g.created, " + + " g.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " g.version, " + + " g.modified, " + + " g.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " g.deleted, " + + " g.name, " + + " g.description " + + " FROM `group` g " + + " LEFT JOIN `user` u1 ON u1.id = g.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = g.modifiedBy " + + " WHERE g.id = :id; " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List getUserAuthIds(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ga.authId " + + " FROM group_authority ga " + + " WHERE ga.groupId = :id; " + ); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public List getUserAuthNames(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " a.name " + + " FROM group_authority ga " + + " LEFT JOIN authority a ON ga.authId = a.id " + + " WHERE ga.groupId = :id; " + ); + + return jdbcDao.queryForStrings(sql.toString(), req); + } + + public List getUserGroupIds(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ug.userId " + + " FROM user_group ug " + + " WHERE ug.groupId = :id; " + ); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public List getUserGroupNames(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " u.name " + + " FROM user_group ug " + + " LEFT JOIN `user` u ON u.id = ug.userId " + + " WHERE ug.groupId = :id; " + ); + return jdbcDao.queryForStrings(sql.toString(), req); + } + + public List> searchForCombo(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " g.id," + + " g.name" + + " FROM `group` g" + + " WHERE g.deleted = FALSE"); + + if (args != null) { + if (args.containsKey(Params.QUERY)) sql.append(" AND (g.name LIKE :query)"); + if (args.containsKey(Params.ID)) sql.append(" AND g.id = :id"); + } + + sql.append(" ORDER BY g.name asc"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + @Transactional(rollbackFor = Exception.class) + public void delete(Group instance) { + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + StringBuilder sql2 = new StringBuilder("SELECT authId FROM group_authority WHERE groupId = :id"); + + StringBuilder sql3 = new StringBuilder("SELECT userId FROM user_group WHERE groupId = :id"); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("group_auth", getUserAuthNames(input)); + logData.put("group_auth_id", getUserAuthIds(input)); + logData.put("group_user", getUserGroupNames(input)); + logData.put("group_user_id", getUserGroupIds(input)); + oldValueObject = logData; + } + + // =====GET OLD AUDIT LOG=====// + + Map args = Map.of("groupId", instance.getId()); + jdbcDao.executeUpdate("DELETE FROM user_group WHERE groupId = :groupId;", args); + jdbcDao.executeUpdate("DELETE FROM group_authority WHERE groupId = :groupId;", args); + this.markDelete(instance); + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("group_auth", getUserAuthNames(input)); + logData.put("group_auth_id", getUserAuthIds(input)); + logData.put("group_user", getUserGroupNames(input)); + logData.put("group_user_id", getUserGroupIds(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + // =====GET NEW AUDIT LOG=====// + } + + @Transactional(rollbackFor = Exception.class) + public Group saveOrUpdate(@Valid SaveGroupReq req ) { + Group instance; + + if (req.getId() != null) { + instance = find(req.getId()).orElseThrow(InternalServerErrorException::new); + } else { + instance = new Group(); + } + BeanUtils.copyProperties(req, instance); + + //=====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldValueObject = new HashMap(); + Map newValueObject = new HashMap(); + + StringBuilder sql2 = new StringBuilder("SELECT authId FROM group_authority WHERE groupId = :id"); + + StringBuilder sql3 = new StringBuilder("SELECT userId FROM user_group WHERE groupId = :id"); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("group_auth", getUserAuthNames(input)); + logData.put("group_auth_id", getUserAuthIds(input)); + logData.put("group_user", getUserGroupNames(input)); + logData.put("group_user_id", getUserGroupIds(input)); + oldValueObject = logData; + } + + //=====GET OLD AUDIT LOG=====// + + instance = saveAndFlush(instance); + Long id = instance.getId(); + + List> userBatchInsertValues = req.getAddUserIds().stream() + .map(userId -> Map.of("groupId", id, "userId", userId)) + .collect(Collectors.toList()); + List> userBatchDeleteValues = req.getRemoveUserIds().stream() + .map(userId -> Map.of("groupId", id, "userId", userId)) + .collect(Collectors.toList()); + + if (!userBatchInsertValues.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO user_group (groupId,userId)" + + " VALUES (:groupId, :userId)", + userBatchInsertValues); + } + if (!userBatchDeleteValues.isEmpty()) { + jdbcDao.batchUpdate( + "DELETE FROM user_group" + + " WHERE groupId = :groupId AND userId = :userId", + userBatchDeleteValues); + } + + List> authBatchInsertValues = req.getAddAuthIds().stream() + .map(authId -> Map.of("groupId", id, "authId", authId)) + .collect(Collectors.toList()); + List> authBatchDeleteValues = req.getRemoveAuthIds().stream() + .map(authId -> Map.of("groupId", id, "authId", authId)) + .collect(Collectors.toList()); + + if (!authBatchInsertValues.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO group_authority (groupId, authId)" + + " VALUES (:groupId, :authId)", + authBatchInsertValues); + } + if (!authBatchDeleteValues.isEmpty()) { + jdbcDao.batchUpdate( + "DELETE FROM group_authority" + + " WHERE groupId = :groupId AND authId = :authId", + authBatchDeleteValues); + } + + //=====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData= getAuditLogObject(input); + logData.put("group_auth", getUserAuthNames(input)); + logData.put("group_auth_id", getUserAuthIds(input)); + logData.put("group_user", getUserGroupNames(input)); + logData.put("group_user_id", getUserGroupIds(input)); + newValueObject = logData; + } + + + if(auditLogService.compareMaps(newValueObject,oldValueObject).size() != 0 || + auditLogService.compareMaps(oldValueObject,newValueObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newValueObject,oldValueObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldValueObject,newValueObject))); + } + return instance; + //=====GET NEW AUDIT LOG=====// + } + + public List listGroupAuthId(Long id) { + return jdbcDao.queryForInts( + "SELECT" + + " ga.authId" + + " FROM group_authority ga" + + " WHERE ga.groupId = :id", + Map.of(Params.ID, id)); + } + + public List listGroupUserId(Long id) { + return jdbcDao.queryForInts( + "SELECT" + + " gu.userId" + + " FROM user_group gu" + + " INNER JOIN user u ON u.deleted = FALSE AND gu.userId = u.id" + + " WHERE gu.groupId = :id", + Map.of(Params.ID, id)); + } + + public List> listGroupMember(Map args){ + StringBuilder sql = new StringBuilder("SELECT" + + " u.id, " + + " u.name, " + + " sd.name as subDivisionName " + + " FROM user_group ug " + + " LEFT JOIN `user` u on u.id = ug.userId " + + " left join sub_division sd on u.subDivisionId = sd.id " + + " WHERE ug.groupId = :id " + + " AND u.deleted = FALSE " + ); + + sql.append(" ORDER BY u.id"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + @Transactional(rollbackFor = Exception.class) + public List> listGroupAuth(Map args) { + + StringBuilder sql = new StringBuilder("SELECT" + + " a.id, " + + " a.module," + + " a.authority," + + " a.name," + + " a.description, "); + if (args.containsKey("userId")) + sql.append(" EXISTS(SELECT 1 FROM group_authority WHERE authority = a.authority AND groupId = :groupId) AS v"); + else + sql.append(" 0 AS v"); + sql.append(" FROM authority a" + + " ORDER BY a.module, a.name"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(g.id) " + + " FROM `group` g " + + " WHERE g.deleted =FALSE " + + " AND g.name = :name " + + " AND g.id != :id " + ); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/service/UserAuthorityService.java b/src/main/java/com/ffii/lioner/modules/user/service/UserAuthorityService.java new file mode 100644 index 0000000..5dbcd65 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/UserAuthorityService.java @@ -0,0 +1,48 @@ +package com.ffii.lioner.modules.user.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.ffii.lioner.modules.user.entity.User; +import com.ffii.core.support.AbstractService; +import com.ffii.core.support.JdbcDao; + +@Service +public class UserAuthorityService extends AbstractService { + private static final String USER_AUTH_SQL = "SELECT a.authority" + + " FROM `user` u" + + " JOIN user_authority ua ON ua.userId = u.id" + + " JOIN authority a ON a.id = ua.authId" + + " WHERE u.deleted = 0" + + " AND u.id = :userId"; + private static final String UNION_SQL = " UNION "; + private static final String GROUP_AUTH_SQL = "SELECT a.authority" + + " FROM `user` u" + + " JOIN user_group ug ON ug.userId = u.id" + + " JOIN `group` g ON g.deleted = 0 AND g.id = ug.groupId" + + " JOIN group_authority ga ON ga.groupId = g.id" + + " JOIN authority a ON a.id = ga.authId" + + " WHERE u.deleted = 0" + + " AND u.id = :userId"; + + public UserAuthorityService(JdbcDao jdbcDao) { + super(jdbcDao); + } + + @Transactional(rollbackFor = Exception.class) + public List>/*Set*/ getUserAuthority(User user) { + Set auths = new HashSet<>(); + List> records = jdbcDao.queryForList(USER_AUTH_SQL + UNION_SQL + GROUP_AUTH_SQL, + Map.of("userId", user.getId())); + + //records.forEach(item -> auths.add(new SimpleGrantedAuthority((String) item.get("authority")))); + return records; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/service/UserPasswordHistoryService.java b/src/main/java/com/ffii/lioner/modules/user/service/UserPasswordHistoryService.java new file mode 100644 index 0000000..07152d8 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/UserPasswordHistoryService.java @@ -0,0 +1,30 @@ +package com.ffii.lioner.modules.user.service; + +import java.util.List; +import java.util.Map; + +import org.springframework.stereotype.Service; + +import com.ffii.lioner.modules.common.SettingNames; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.lioner.modules.user.entity.UserPasswordHistory; +import com.ffii.lioner.modules.user.entity.UserPasswordHistoryRepository; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; + +@Service +public class UserPasswordHistoryService extends AbstractBaseEntityService { + private SettingsService settingsService; + + public UserPasswordHistoryService(JdbcDao jdbcDao, UserPasswordHistoryRepository repository, SettingsService settingsService) { + super(jdbcDao, repository); + this.settingsService = settingsService; + } + + public List getPasswordHistory(long userId) { + int passHist = settingsService.getInt(SettingNames.SYS_PASSWORD_RULE_HISTORY); + StringBuilder sql = new StringBuilder(" SELECT password FROM user_password_history WHERE deleted = 0 AND userId = :userId ORDER BY date DESC LIMIT " + passHist); + return jdbcDao.queryForStrings(sql.toString(), Map.of("userId", userId)); + } + +} \ No newline at end of file diff --git a/src/main/java/com/ffii/lioner/modules/user/service/UserService.java b/src/main/java/com/ffii/lioner/modules/user/service/UserService.java new file mode 100644 index 0000000..0dafce2 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/UserService.java @@ -0,0 +1,808 @@ +package com.ffii.lioner.modules.user.service; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import com.ffii.core.utils.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.MessageSource; +import org.springframework.context.NoSuchMessageException; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +import com.ffii.lioner.modules.common.ErrorCodes; +import com.ffii.lioner.modules.common.PasswordRule; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.lioner.modules.user.entity.User; +import com.ffii.lioner.modules.user.entity.UserPasswordHistory; +import com.ffii.lioner.modules.user.entity.UserRepository; +import com.ffii.lioner.modules.user.req.NewLionerUserReq; +import com.ffii.lioner.modules.user.req.NewPublicUserReq; +import com.ffii.lioner.modules.user.req.NewUserReq; +import com.ffii.lioner.modules.user.req.SearchUserReq; +import com.ffii.lioner.modules.user.req.UpdateUserReq; +import com.ffii.lioner.modules.user.service.pojo.UserRecord; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.exception.UnprocessableEntityException; +import com.ffii.core.support.AbstractBaseEntityService; +import com.ffii.core.support.JdbcDao; +import com.ffii.core.utils.JsonUtils; +import com.ffii.core.utils.Params; +import com.ffii.core.utils.PasswordUtils; + +import jakarta.persistence.Table; + +@Service +public class UserService extends AbstractBaseEntityService { + private static final String USER_AUTH_SQL = "SELECT a.authority" + + " FROM `user` u" + + " JOIN user_authority ua ON ua.userId = u.id" + + " JOIN authority a ON a.id = ua.authId" + + " WHERE u.deleted = 0" + + " AND u.id = :userId"; + private static final String UNION_SQL = " UNION "; + private static final String GROUP_AUTH_SQL = "SELECT a.authority" + + " FROM `user` u" + + " JOIN user_group ug ON ug.userId = u.id" + + " JOIN `group` g ON g.deleted = 0 AND g.id = ug.groupId" + + " JOIN group_authority ga ON ga.groupId = g.id" + + " JOIN authority a ON a.id = ga.authId" + + " WHERE u.deleted = 0" + + " AND u.id = :userId"; + + @Autowired + private SettingsService settingsService; + @Autowired + private PasswordEncoder passwordEncoder; + private AuditLogService auditLogService; + private UserPasswordHistoryService userPasswordHistoryService; + private MessageSource messageSource; + private final RestTemplate restTemplate; + + @Value("${emsd.webservice.urls}") + private String ldapWebServiceUrls; + + @Autowired + UserRepository userRepository; + + public UserService(JdbcDao jdbcDao, UserRepository userRepository, AuditLogService auditLogService, + UserPasswordHistoryService userPasswordHistoryService, + MessageSource messageSource, RestTemplate restTemplate) { + super(jdbcDao, userRepository); + this.auditLogService = auditLogService; + this.userPasswordHistoryService = userPasswordHistoryService; + this.messageSource = messageSource; + this.restTemplate = restTemplate; + } + + public Optional loadUserOptByUsername(String username) { + return findByUsername(username) + .map(user -> { + Set auths = new LinkedHashSet(); + auths.add(new SimpleGrantedAuthority("ROLE_USER")); + jdbcDao.queryForList(USER_AUTH_SQL + UNION_SQL + GROUP_AUTH_SQL, Map.of("userId", user.getId())) + .forEach(item -> auths.add(new SimpleGrantedAuthority((String) item.get("authority")))); + + user.setAuthorities(auths); + return user; + }); + } + + public Optional findByUsername(String username) { + return userRepository.findByUsernameAndDeletedFalse(username); + } + + public Map getAuditLogObject(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " u.id, " + + " u.created, " + + " u.createdBy AS createdById, " + + " u1.name AS createdBy, " + + " u.version, " + + " u.modified, " + + " u.modifiedBy AS modifiedById, " + + " u2.name AS modifiedBy, " + + " u.deleted, " + + " u.username, " + + " u.locked, " + + " u.name , " + + " u.fullname, " + + " u.email, " + + " u.phone1, " + + " u.lotusNotesUser, " + + " u.reminderFlag, " + + " u.post, " + + " sd.name " + + " FROM `user` u " + + " LEFT JOIN `user` u1 ON u1.id = u.createdBy " + + " LEFT JOIN `user` u2 ON u2.id = u.modifiedBy " + + " LEFT JOIN sub_division sd ON sd.id = u.subDivisionId " + + " WHERE u.id = :id " + ); + + return jdbcDao.queryForMap(sql.toString(), req).get(); + } + + public List getUserAuthIds(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ua.authId " + + " FROM user_authority ua " + + " WHERE userId = :id; " + ); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public List getUserAuthNames(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " a.name " + + " FROM user_authority ua " + + " LEFT JOIN authority a ON ua.authId = a.id " + + " WHERE userId = :id; " + ); + + return jdbcDao.queryForStrings(sql.toString(), req); + } + + public List getUserGroupIds(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " ug.groupId " + + " FROM user_group ug " + + " WHERE userId = :id; " + ); + + return jdbcDao.queryForInts(sql.toString(), req); + } + + public List getUserGroupNames(Map req){ + StringBuilder sql = new StringBuilder("SELECT" + + " g.name " + + " FROM user_group ug " + + " LEFT JOIN `group` g ON g.id = ug.groupId " + + " WHERE userId = :id; " + ); + return jdbcDao.queryForStrings(sql.toString(), req); + } + + + // @Transactional(rollbackFor = Exception.class) + public List search(SearchUserReq req) { + StringBuilder sql = new StringBuilder("SELECT" + + " DISTINCT u.id," + + " u.created," + + " u.createdBy," + + " u.version," + + " u.modified," + + " u.modifiedBy," + + " u.username," + + " u.locked," + + " u.name," + + " u.locale," + + " u.firstname," + + " u.lastname," + + " u.title," + + " u.department," + + " u.email," + + " u.phone1," + + " u.phone2," + + " u.remarks, " + + " u.fullname, " + + " u.post, " + + " u.lotusNotesUser, " + + " sd.name as subDivision" + + " FROM `user` u" + + " left join user_group ug on u.id = ug.userId" + + " left join sub_division sd on u.subDivisionId = sd.id " + + " where u.deleted = false"); + + if (req != null) { + if (req.getId() != null) + sql.append(" AND u.id = :id"); + + if (req.getGroupId() != null) + sql.append(" AND ug.groupId = :groupId"); + if (StringUtils.isNotBlank(req.getUsername())) { + req.setUsername("%" + req.getUsername() + "%"); + sql.append(" AND u.username LIKE :username"); + } + + if (StringUtils.isNotBlank(req.getFullname())) { + req.setFullname("%" + req.getFullname() + "%"); + sql.append(" AND u.fullname LIKE :fullname"); + } + + if (StringUtils.isNotBlank(req.getPost())) { + req.setPost("%" + req.getPost() + "%"); + sql.append(" AND u.post LIKE :post"); + } + + if (req.getSubDivisionId() != null) { + sql.append(" AND u.subDivisionId = :subDivisionId"); + } + + if (StringUtils.isNotBlank(req.getEmail())) { + req.setEmail("%" + req.getEmail() + "%"); + sql.append(" AND u.email LIKE :email"); + } + + if (StringUtils.isNotBlank(req.getPhone())) { + req.setPhone("%" + req.getPhone() + "%"); + sql.append(" AND u.phone1 LIKE :phone"); + } + + if (req.getLocked() != null) { + sql.append(" AND u.locked = :locked"); + } + + if (req.getIsLotusNotesUser() != null) { + sql.append(" AND u.lotusNotesUser = :isLotusNotesUser"); + } + + } + sql.append(" ORDER BY u.username asc"); + + if (req != null) { + if (req.getStart() != null && req.getLimit() != null) + sql.append(" LIMIT :start, :limit"); + } + + return jdbcDao.queryForList(sql.toString(), req, UserRecord.class); + } + + public List listUserAuthId(long id) { + return jdbcDao.queryForInts( + "SELECT" + + " ua.authId" + + " FROM user_authority ua" + + " WHERE ua.userId = :id", + Map.of(Params.ID, id)); + } + + public List listUserGroupId(long id) { + return jdbcDao.queryForInts( + "SELECT" + + " gu.groupId" + + " FROM user_group gu" + + " INNER JOIN `group` g ON g.deleted = FALSE AND g.id = gu.groupId" + + " WHERE gu.userId = :id", + Map.of(Params.ID, id)); + } + + private User saveOrUpdate(User instance, UpdateUserReq req) { + + if (instance.getId() == null) { + req.setLocked(false); + } + BeanUtils.copyProperties(req, instance); + instance = save(instance); + // long id = instance.getId(); + + // List> groupBatchInsertValues = + // req.getAddGroupIds().stream() + // .map(groupId -> Map.of("userId", (int) id, "groupId", groupId)) + // .collect(Collectors.toList()); + // List> groupBatchDeleteValues = + // req.getRemoveGroupIds().stream() + // .map(groupId -> Map.of("userId", (int) id, "groupId", groupId)) + // .collect(Collectors.toList()); + + // if (!groupBatchInsertValues.isEmpty()) { + // jdbcDao.batchUpdate( + // "INSERT IGNORE INTO user_group (groupId,userId)" + // + " VALUES (:groupId, :userId)", + // groupBatchInsertValues); + // } + // if (!groupBatchDeleteValues.isEmpty()) { + // jdbcDao.batchUpdate( + // "DELETE FROM user_group" + // + " WHERE groupId = :groupId AND userId = :userId", + // groupBatchDeleteValues); + // } + + // List> authBatchInsertValues = + // req.getAddAuthIds().stream() + // .map(authId -> Map.of("userId", (int)id, "authId", authId)) + // .collect(Collectors.toList()); + // List> authBatchDeleteValues = + // req.getRemoveAuthIds().stream() + // .map(authId -> Map.of("userId", (int)id, "authId", authId)) + // .collect(Collectors.toList()); + // if (!authBatchInsertValues.isEmpty()) { + // jdbcDao.batchUpdate( + // "INSERT IGNORE INTO user_authority (userId, authId)" + // + " VALUES (:userId, :authId)", + // authBatchInsertValues); + // } + + // if (!authBatchDeleteValues.isEmpty()) { + // jdbcDao.batchUpdate( + // "DELETE FROM user_authority" + // + " WHERE userId = :userId AND authId = :authId", + // authBatchDeleteValues); + // } + return instance; + } + + @Transactional(rollbackFor = Exception.class) + public void updateRecord(Integer id, UpdateUserReq req) { + saveOrUpdate( + find(id.longValue()).orElseThrow(NotFoundException::new), + req); + } + + private User saveOrUpdateLIONER(User instance, NewLionerUserReq req) { + + if (instance.getId() == null) { + req.setLocked(false); + } + BeanUtils.copyProperties(req, instance); + instance = saveAndFlush(instance); + return instance; + } + + @Transactional(rollbackFor = Exception.class) + public User newRecord(NewUserReq req) throws UnsupportedEncodingException { + if (findByUsername(req.getUsername()).isPresent()) { + throw new UnprocessableEntityException(ErrorCodes.USERNAME_NOT_AVAILABLE); + } + + // String randomPassword = PasswordUtils.genPwd(new + // PasswordRule(settingsService)); + // String pwdHash = passwordEncoder.encode(randomPassword); + + User instance = new User(); + // instance.setPassword(pwdHash); + instance = saveOrUpdate(instance, req); + return instance; + } + + @Transactional(rollbackFor = Exception.class) + public User newPublicUserRecord(NewPublicUserReq req) throws UnsupportedEncodingException { + if (findByUsername(req.getUsername()).isPresent()) { + throw new UnprocessableEntityException(ErrorCodes.USERNAME_NOT_AVAILABLE); + } + + String submitedPassword = req.getPassword(); + String pwdHash = passwordEncoder.encode(submitedPassword); + req.setPassword(pwdHash); + User instance = new User(); + + instance = saveOrUpdate(instance, req); + return instance; + } + + @Transactional(rollbackFor = Exception.class) + public User newUserRecord(NewLionerUserReq req) + throws UnsupportedEncodingException, NoSuchMessageException, ParseException, IllegalAccessException { + User instance = new User(); + + if (req.getId() > 0) { + instance = find(req.getId()).get(); + } else { + if (findByUsername(req.getUsername()).isPresent()) { + throw new UnprocessableEntityException(ErrorCodes.USERNAME_NOT_AVAILABLE); + } + instance = new User(); + } + + if (req.getPassword() != null && req.getPassword() != "" && req.getPassword().length() > 1) { + String submitedPassword = req.getPassword(); + + PasswordRule rule = new PasswordRule(settingsService); + if (!PasswordUtils.checkPwd(req.getPassword(), rule, req.getUsername())) { + throw new UnprocessableEntityException(rule.getWrongMsg()); + } + if (req.getId() > 0) { + List passHists = userPasswordHistoryService.getPasswordHistory(req.getId()); + passHists.stream().forEach(oldPass -> { + if (passwordEncoder.matches(req.getPassword(), oldPass)) { + throw new UnprocessableEntityException("This password is already in the password history."); + } + }); + } + + String pwdHash = passwordEncoder.encode(submitedPassword); + req.setPassword(pwdHash); + + } else { + req.setPassword(instance.getPassword()); + } + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldUserObject = new HashMap(); + Map newUserObject = new HashMap(); + + StringBuilder sql2 = new StringBuilder("SELECT authId FROM user_authority WHERE userId = :id"); + + StringBuilder sql3 = new StringBuilder("SELECT groupId FROM user_group WHERE userId = :id"); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("user_auth", getUserAuthNames(input)); + logData.put("user_auth_id", getUserAuthIds(input)); + logData.put("user_group", getUserGroupNames(input)); + logData.put("user_group_id", getUserGroupIds(input)); + oldUserObject = logData; + } + // =====GET OLD AUDIT LOG=====// + + instance = saveOrUpdateLIONER(instance, req); + Long id = instance.getId(); + + if (req.getPassword() != null && req.getPassword() != "" && req.getPassword().length() > 1) { + // if password updated + UserPasswordHistory record = new UserPasswordHistory(); + record.setUserId(id); + record.setPassword(req.getPassword()); + record.setDate(LocalDateTime.now()); + record = userPasswordHistoryService.save(record); + } + + List> authBatchInsertValues = req.getAddAuthIds().stream() + .map(authId -> Map.of("userId", id, "authId", authId)) + .collect(Collectors.toList()); + List> authBatchDeleteValues = req.getRemoveAuthIds().stream() + .map(authId -> Map.of("userId", id, "authId", authId)) + .collect(Collectors.toList()); + + if (!authBatchInsertValues.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO user_authority (userId, authId)" + + " VALUES (:userId, :authId)", + authBatchInsertValues); + } + if (!authBatchDeleteValues.isEmpty()) { + jdbcDao.batchUpdate( + "DELETE FROM user_authority" + + " WHERE userId = :userId AND authId = :authId", + authBatchDeleteValues); + } + + List> userBatchInsertValues = req.getAddGroupIds().stream() + .map(groupId -> Map.of("userId", id, "groupId", groupId)) + .collect(Collectors.toList()); + List> userBatchDeleteValues = req.getRemoveGroupIds().stream() + .map(groupId -> Map.of("userId", id, "groupId", groupId)) + .collect(Collectors.toList()); + + if (!userBatchInsertValues.isEmpty()) { + jdbcDao.batchUpdate( + "INSERT IGNORE INTO user_group (groupId,userId)" + + " VALUES (:groupId, :userId)", + userBatchInsertValues); + } + if (!userBatchDeleteValues.isEmpty()) { + jdbcDao.batchUpdate( + "DELETE FROM user_group" + + " WHERE groupId = :groupId AND userId = :userId", + userBatchDeleteValues); + } + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("user_auth", getUserAuthNames(input)); + logData.put("user_auth_id", getUserAuthIds(input)); + logData.put("user_group", getUserGroupNames(input)); + logData.put("user_group_id", getUserGroupIds(input)); + newUserObject = logData; + } + + + if(auditLogService.compareMaps(newUserObject,oldUserObject).size() != 0 || + auditLogService.compareMaps(oldUserObject,newUserObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newUserObject,oldUserObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldUserObject,newUserObject))); + } + // =====GET NEW AUDIT LOG=====// + return instance; + } + + + @Transactional(rollbackFor = Exception.class) + public void updateRecord(long id, UpdateUserReq req) { + saveOrUpdate( + find(id).orElseThrow(NotFoundException::new), + req); + } + + @Transactional(rollbackFor = Exception.class) + public String resetPassword(long id) throws UnsupportedEncodingException { + User instance = find(id).orElseThrow(NotFoundException::new); + // String randomPassword = PasswordUtils.genPwd(new + // PasswordRule(settingsService)); + + // instance.setPassword(passwordEncoder.encode(randomPassword)); + instance = save(instance); + return null; + } + + @Transactional(rollbackFor = Exception.class) + public List> listUserAuth(Map args) { + + StringBuilder sql = new StringBuilder("SELECT" + + " a.id, " + + " a.module," + + " a.authority," + + " a.name," + + " a.description, "); + if (args.containsKey("userId")) + sql.append(" EXISTS(SELECT 1 FROM user_authority WHERE authority = a.authority AND userId = :userId) AS v"); + else + sql.append(" 0 AS v"); + sql.append(" FROM authority a" + + " ORDER BY a.module, a.name"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> searchForCombo(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " u.id, " + + " u.id as `key`, " + + " u.name, " + + " u.name as label, " + + " sd.name as subDivisionName " + + " FROM `user` u " + + " LEFT JOIN sub_division sd on u.subDivisionId = sd.id " + + " WHERE u.deleted = FALSE" + ); + + if (args != null) { + if (args.containsKey(Params.QUERY)) + sql.append(" AND (u.name LIKE :query)"); + if (args.containsKey(Params.ID)) + sql.append(" AND u.id = :id"); + } + + sql.append(" ORDER BY u.name"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> searchForAuditCombo(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " u.id, " + + " u.id as `key`, " + + " u.name, " + + " u.name as label " + + " FROM `user` u " + ); + + sql.append(" ORDER BY u.name"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public List> listLoginLog(Map args) { + StringBuilder sql = new StringBuilder("SELECT" + + " ull.username, " + + " ull.ipAddr, " + + " ull.loginTime, " + + " ull.success " + + " FROM user_login_log ull " + + " WHERE TRUE "); + + if (args != null) { + if (args.containsKey("username")) + sql.append(" AND (ull.username LIKE :username)"); + if (args.containsKey("ipAddr")) + sql.append(" AND ull.ipAddr LIKE :ipAddr"); + + if (args.containsKey("fromDate")) + sql.append(" AND DATE(ull.loginTime) >= :fromDate"); + if (args.containsKey("toDate")) + sql.append(" AND DATE(ull.loginTime) < :toDate"); + + if (args.containsKey("success")) + sql.append(" AND ull.success = :success"); + } + + sql.append(" ORDER BY ull.loginTime DESC"); + + return jdbcDao.queryForList(sql.toString(), args); + } + + public Map softDelete(User user) { + User instance = user; + + // =====GET OLD AUDIT LOG=====// + String tableName = instance.getClass().getAnnotation(Table.class).name(); + StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = :id"); + Map oldUserObject = new HashMap(); + Map newUserObject = new HashMap(); + + StringBuilder sql2 = new StringBuilder("SELECT authId FROM user_authority WHERE userId = :id"); + + StringBuilder sql3 = new StringBuilder("SELECT groupId FROM user_group WHERE userId = :id"); + + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("user_auth", getUserAuthNames(input)); + logData.put("user_auth_id", getUserAuthIds(input)); + logData.put("user_group", getUserGroupNames(input)); + logData.put("user_group_id", getUserGroupIds(input)); + oldUserObject = logData; + } + // =====GET OLD AUDIT LOG=====// + this.markDelete(instance); + + // =====GET NEW AUDIT LOG=====// + if (instance != null && instance.getId() != null && instance.getId() > 0) { + Map input = Map.of("id", instance.getId()); + Map logData = getAuditLogObject(input); + logData.put("user_auth", getUserAuthNames(input)); + logData.put("user_auth_id", getUserAuthIds(input)); + logData.put("user_group", getUserGroupNames(input)); + logData.put("user_group_id", getUserGroupIds(input)); + newUserObject = logData; + } + + if(auditLogService.compareMaps(newUserObject,oldUserObject).size() != 0 || + auditLogService.compareMaps(oldUserObject,newUserObject).size() != 0 + ){ + auditLogService.save( + tableName, + instance.getId(), + instance.getName(), + SecurityUtils.getUser() != null ? SecurityUtils.getUser().get().getId() : null, + new Date(), + JsonUtils.toJsonString(auditLogService.compareMaps(newUserObject,oldUserObject)), + JsonUtils.toJsonString(auditLogService.compareMaps(oldUserObject,newUserObject))); + } + // =====GET NEW AUDIT LOG=====// + + return Map.of( + "id", instance.getId()); + } + + public String fetchDataFromApi(String EMSDlotus) { + String apiUrl = ldapWebServiceUrls; + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("AuthenticationCode", "arspass"); + params.add("EMSDlotus", EMSDlotus); + params.add("cn", ""); + params.add("EMSDpersonno", ""); + params.add("EMSDemploystatus", ""); + params.add("EMSDformaddr", ""); + params.add("EMSDlastname", ""); + params.add("EMSDfirstname", ""); + params.add("EMSDknownas", ""); + params.add("EMSDgender", ""); + params.add("EMSDposition", ""); + params.add("EMSDtitle", ""); + params.add("EMSDorgunit", ""); + params.add("EMSDorgdesc", ""); + params.add("EMSDmangid", ""); + params.add("EMSDgrpemploy", ""); + params.add("EMSDsgrpemploy", ""); + params.add("EMSDrankcode", ""); + params.add("EMSDrankdesc", ""); + params.add("EMSDEMSDemail", ""); + params.add("EMSDapmail", ""); + params.add("EMSDofftel", ""); + params.add("EMSDcc", ""); + params.add("EMSDccresp", ""); + params.add("EMSDdivision", ""); + params.add("EMSDstream1", ""); + params.add("EMSDstream2", ""); + params.add("EMSDactdesc1", ""); + params.add("EMSDactrank1", ""); + params.add("EMSDactldesc1", ""); + params.add("EMSDactdesc2", ""); + params.add("EMSDactrank2", ""); + params.add("EMSDactldesc2", ""); + params.add("EMSDactdesc3", ""); + params.add("EMSDactrank3", ""); + params.add("EMSDactldesc3", ""); + params.add("EMSDupddate", ""); + params.add("EMSDactpid1", ""); + params.add("EMSDactpost1", ""); + params.add("EMSDactpid2", ""); + params.add("EMSDactpost2", ""); + params.add("EMSDactpid3", ""); + params.add("EMSDactpost3", ""); + params.add("EMSDnickname", ""); + params.add("EMSDtitledescen", ""); + params.add("EMSDtitledescch", ""); + params.add("EMSDotherdeptemail", ""); + params.add("EMSDotherdeptapmail", ""); + params.add("EMSDgoamail", ""); + params.add("EMSDdpac", ""); + params.add("EMSDarrayorgobjid", ""); + params.add("EMSDarrayorgengna", ""); + params.add("EMSDarrayorgchina", ""); + + UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(apiUrl); + for (String key : params.keySet()) { + for (String value : params.get(key)) { + builder.queryParam(key, value); + } + } + + HttpEntity> requestEntity = new HttpEntity<>(params, headers); + ResponseEntity response = restTemplate.exchange(builder.toUriString(), HttpMethod.POST, requestEntity, + String.class); + + if (response.getStatusCode().is2xxSuccessful()) { + return response.getBody(); + } else { + throw new RuntimeException("Failed to fetch data from the API"); + } + } + + public boolean isNameTaken(String name, Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " Count(u.id) " + + " FROM `user` u " + + " WHERE u.deleted = FALSE " + + " AND u.name = :name " + + " AND u.id != :id "); + + return jdbcDao.queryForBoolean(sql.toString(), Map.of("name", name, "id", id)); + } + + public List listUserBySubDivision(Integer subDivisionId) { + return jdbcDao.queryForList("SELECT" + + " u.* " + + " FROM `user` u " + + " WHERE u.deleted = FALSE " + + " AND u.locked = FALSE " + + " AND u.subDivisionId = :id " + + " AND u.reminderFlag = TRUE ", Map.of(Params.ID, subDivisionId), User.class); + } + + public Integer getLastChangePasswordDate(Long id) { + StringBuilder sql = new StringBuilder("SELECT" + + " TIMESTAMPDIFF(DAY,uph.`date`, NOW()) " + + " FROM user_password_history uph " + + " WHERE uph.userId = :userId " + + " ORDER BY uph.id DESC " + + " LIMIT 1 " + ); + + return jdbcDao.queryForInt(sql.toString(), Map.of("userId", id)); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/service/pojo/AuthRecord.java b/src/main/java/com/ffii/lioner/modules/user/service/pojo/AuthRecord.java new file mode 100644 index 0000000..2af5932 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/pojo/AuthRecord.java @@ -0,0 +1,41 @@ +package com.ffii.lioner.modules.user.service.pojo; + +public class AuthRecord { + private Integer id; + private String module; + private String authority; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getModule() { + return module; + } + + public void setModule(String module) { + this.module = module; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/service/pojo/UserRecord.java b/src/main/java/com/ffii/lioner/modules/user/service/pojo/UserRecord.java new file mode 100644 index 0000000..cbb7b7f --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/pojo/UserRecord.java @@ -0,0 +1,188 @@ +package com.ffii.lioner.modules.user.service.pojo; + +import java.time.LocalDateTime; + +public class UserRecord { + private Integer id; + private LocalDateTime created; + private String createdBy; + private String modified; + private String modifiedBy; + private String username; + private Boolean locked; + private String name; + private Integer companyId; + private Integer customerId; + private String locale; + private String fullname; + private String firstname; + private String lastname; + private String title; + private String department; + private String deptId; + private String email; + private String phone1; + private String phone2; + private String remarks; + private Boolean lotusNotesUser; + private String post; + private String subDivision; + + public Boolean isLocked() { + return this.locked; + } + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public Boolean isLotusNotesUser() { + return this.lotusNotesUser; + } + + public String getPost() { + return this.post; + } + + public void setPost(String post) { + this.post = post; + } + + public Integer getId() { + return id; + } + public void setId(Integer id) { + this.id = id; + } + public LocalDateTime getCreated() { + return created; + } + public void setCreated(LocalDateTime created) { + this.created = created; + } + public String getCreatedBy() { + return createdBy; + } + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + public String getModified() { + return modified; + } + public void setModified(String modified) { + this.modified = modified; + } + public String getModifiedBy() { + return modifiedBy; + } + public void setModifiedBy(String modifiedBy) { + this.modifiedBy = modifiedBy; + } + public String getUsername() { + return username; + } + public void setUsername(String username) { + this.username = username; + } + public Boolean getLocked() { + return locked; + } + public void setLocked(Boolean locked) { + this.locked = locked; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public Integer getCompanyId() { + return companyId; + } + public void setCompanyId(Integer companyId) { + this.companyId = companyId; + } + public Integer getCustomerId() { + return customerId; + } + public void setCustomerId(Integer customerId) { + this.customerId = customerId; + } + public String getLocale() { + return locale; + } + public void setLocale(String locale) { + this.locale = locale; + } + public String getFullname() { + return fullname; + } + public void setFullname(String fullname) { + this.fullname = fullname; + } + public String getFirstname() { + return firstname; + } + public void setFirstname(String firstname) { + this.firstname = firstname; + } + public String getLastname() { + return lastname; + } + public void setLastname(String lastname) { + this.lastname = lastname; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public String getDepartment() { + return department; + } + public void setDepartment(String department) { + this.department = department; + } + public String getDeptId() { + return deptId; + } + public void setDeptId(String deptId) { + this.deptId = deptId; + } + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + public String getPhone1() { + return phone1; + } + public void setPhone1(String phone1) { + this.phone1 = phone1; + } + public String getPhone2() { + return phone2; + } + public void setPhone2(String phone2) { + this.phone2 = phone2; + } + public String getRemarks() { + return remarks; + } + public void setLotusNotesUser(Boolean lotusNotesUser) { + this.lotusNotesUser = lotusNotesUser; + } + public Boolean getLotusNotesUser() { + return lotusNotesUser; + } + + public String getSubDivision() { + return this.subDivision; + } + + public void setSubDivision(String subDivision) { + this.subDivision = subDivision; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/service/res/LoadUserRes.java b/src/main/java/com/ffii/lioner/modules/user/service/res/LoadUserRes.java new file mode 100644 index 0000000..5e8ddc9 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/service/res/LoadUserRes.java @@ -0,0 +1,45 @@ +package com.ffii.lioner.modules.user.service.res; + +import java.util.List; + +import com.ffii.lioner.modules.user.entity.User; + +public class LoadUserRes { + private User data; + private List authIds; + private List groupIds; + + public LoadUserRes() { + } + + public LoadUserRes(User data, List authIds, List groupIds) { + this.data = data; + this.authIds = authIds; + this.groupIds = groupIds; + } + + public User getData() { + return data; + } + + public void setData(User data) { + this.data = data; + } + + public List getAuthIds() { + return authIds; + } + + public void setAuthIds(List authIds) { + this.authIds = authIds; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/web/AuditLogController.java b/src/main/java/com/ffii/lioner/modules/user/web/AuditLogController.java new file mode 100644 index 0000000..1de0f81 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/web/AuditLogController.java @@ -0,0 +1,41 @@ +package com.ffii.lioner.modules.user.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.common.service.AuditLogService; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; + +import jakarta.servlet.http.HttpServletRequest; + +@RestController +@RequestMapping("/auditLog") +public class AuditLogController{ + + private final Log logger = LogFactory.getLog(getClass()); + private AuditLogService auditLogService; + + public AuditLogController(AuditLogService auditLogService) { + this.auditLogService = auditLogService; + } + + @GetMapping + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(auditLogService.arsSearch( + CriteriaArgsBuilder.withRequest(request) + .addInteger("modifiedBy") + .addString("tableName") + .addStringLike("recordName") + .addDate("fromDate") + .addDateTo("toDate") + .build())); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/web/GroupController.java b/src/main/java/com/ffii/lioner/modules/user/web/GroupController.java new file mode 100644 index 0000000..ab31332 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/web/GroupController.java @@ -0,0 +1,106 @@ +package com.ffii.lioner.modules.user.web; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.user.req.SaveGroupReq; +import com.ffii.lioner.modules.user.service.GroupService; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/group") +public class GroupController{ + + private final Log logger = LogFactory.getLog(getClass()); + private GroupService groupService; + + public GroupController( + GroupService groupService + ) { + this.groupService = groupService; + } + + @PostMapping("/save") + public IdRes saveOrUpdate(@RequestBody @Valid SaveGroupReq req) { + return new IdRes(groupService.saveOrUpdate(req).getId()); + } + + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + groupService.delete(groupService.find(id).orElseThrow(NotFoundException::new)); + } + + @GetMapping("/{id}") + public Map load(@PathVariable Long id) { + return Map.of( + Params.DATA, groupService.find(id).orElseThrow(NotFoundException::new), + "authIds", groupService.listGroupAuthId(id), + "userIds", groupService.listGroupUserId(id)); + } + + @GetMapping("/combo") + public RecordsRes> comboJson(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(groupService.searchForCombo( + CriteriaArgsBuilder.withRequest(request) + .addInteger(Params.ID) + .addStringLike(Params.QUERY) + .build())); + } + + @GetMapping + public RecordsRes> listJson(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(groupService.search( + CriteriaArgsBuilder.withRequest(request) + .addInteger(Params.ID) + .addStringLike(Params.NAME) + .addStringLike("description") + .addInteger("userId") + .build())); + } + + @GetMapping("/member") + public RecordsRes> listGroupUser(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(groupService.listGroupMember( + CriteriaArgsBuilder.withRequest(request) + .addInteger(Params.ID) + .build())); + } + + @GetMapping("/auth/combo") + public RecordsRes> authComboJson(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(groupService.listGroupAuth( + CriteriaArgsBuilder.withRequest(request) + .addInteger("groupId") + .build())); + } + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, @RequestParam Long id) { + boolean isNameTaken = groupService.isNameTaken(name, id); + return Map.of( + "isTaken", isNameTaken + ); + } +} diff --git a/src/main/java/com/ffii/lioner/modules/user/web/TestController.java b/src/main/java/com/ffii/lioner/modules/user/web/TestController.java new file mode 100644 index 0000000..476f49c --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/web/TestController.java @@ -0,0 +1,33 @@ +package com.ffii.lioner.modules.user.web; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + + private final Log logger = LogFactory.getLog(getClass()); + + @Autowired + private LdapTemplate ldapTemplate; + + @GetMapping("/test") + public ResponseEntity test(@RequestParam(defaultValue = "") String username) throws Exception { + List> searchUser = ldapTemplate.search("", "(&(objectClass=*)(cn=*" + username + "*))", + (AttributesMapper>) (attrs -> { + return Map.of("cn", attrs.get("cn").get().toString(), "mail", attrs.get("mail").get().toString()); + })); + return ResponseEntity.ok(searchUser); + } + +} diff --git a/src/main/java/com/ffii/lioner/modules/user/web/UserController.java b/src/main/java/com/ffii/lioner/modules/user/web/UserController.java new file mode 100644 index 0000000..20bec20 --- /dev/null +++ b/src/main/java/com/ffii/lioner/modules/user/web/UserController.java @@ -0,0 +1,352 @@ +package com.ffii.lioner.modules.user.web; + +import java.io.UnsupportedEncodingException; +import java.text.ParseException; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.NoSuchMessageException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.ldap.core.AttributesMapper; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.ServletRequestBindingException; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.ffii.lioner.modules.common.ErrorCodes; +import com.ffii.lioner.modules.common.PasswordRule; +import com.ffii.lioner.modules.common.SecurityUtils; +import com.ffii.lioner.modules.common.SettingNames; +import com.ffii.lioner.modules.common.TempConst; +import com.ffii.lioner.modules.settings.service.SettingsService; +import com.ffii.lioner.modules.user.entity.User; +import com.ffii.lioner.modules.user.entity.UserPasswordHistory; +import com.ffii.lioner.modules.user.req.NewLionerUserReq; +import com.ffii.lioner.modules.user.req.NewPublicUserReq; +import com.ffii.lioner.modules.user.req.NewUserReq; +import com.ffii.lioner.modules.user.req.SearchUserReq; +import com.ffii.lioner.modules.user.req.UpdateUserReq; +import com.ffii.lioner.modules.user.service.UserPasswordHistoryService; +import com.ffii.lioner.modules.user.service.UserService; +import com.ffii.lioner.modules.user.service.res.LoadUserRes; +import com.ffii.core.exception.BadRequestException; +import com.ffii.core.exception.NotFoundException; +import com.ffii.core.exception.UnprocessableEntityException; +import com.ffii.core.response.IdRes; +import com.ffii.core.response.RecordsRes; +import com.ffii.core.utils.CriteriaArgsBuilder; +import com.ffii.core.utils.Params; +import com.ffii.core.utils.PasswordUtils; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; + +@RestController +@RequestMapping("/user") +public class UserController { + + private final Log logger = LogFactory.getLog(getClass()); + private UserService userService; + private PasswordEncoder passwordEncoder; + private SettingsService settingsService; + private UserPasswordHistoryService userPasswordHistoryService; + + private Integer passwordHist; + + @Autowired + private LdapTemplate ldapTemplate; + + @Value("${emsd.webservice.required}") + private Boolean ldapWebServiceRequired; + + public UserController( + UserService userService, + PasswordEncoder passwordEncoder, + SettingsService settingsService, + UserPasswordHistoryService userPasswordHistoryService) { + this.userService = userService; + this.passwordEncoder = passwordEncoder; + this.settingsService = settingsService; + this.userPasswordHistoryService = userPasswordHistoryService; + this.passwordHist = settingsService.getInt(SettingNames.SYS_PASSWORD_RULE_HISTORY); + + } + + // @Operation(summary = "list user", responses = { @ApiResponse(responseCode = + // "200"), + // @ApiResponse(responseCode = "404", content = @Content) }) + @GetMapping + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public ResponseEntity list(@ModelAttribute @Valid SearchUserReq req) { + return ResponseEntity.ok(userService.search(req)); + } + + // @Operation(summary = "load user data", responses = { + // @ApiResponse(responseCode = "200"), + // @ApiResponse(responseCode = "404", content = @Content) }) + @GetMapping("/{id}") + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public LoadUserRes load(@PathVariable long id) { + LoadUserRes test = new LoadUserRes( + userService.find(id).orElseThrow(NotFoundException::new), + userService.listUserAuthId(id), + userService.listUserGroupId(id)); + return test; + } + + // @Operation(summary = "delete user", responses = { @ApiResponse(responseCode = + // "204"), + // @ApiResponse(responseCode = "404", content = @Content) }) + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public void delete(@PathVariable long id) { + userService.softDelete(userService.find(id).orElseThrow(NotFoundException::new)); + } + + // @Operation(summary = "new user") + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public IdRes newRecord(@RequestBody @Valid NewUserReq req) throws UnsupportedEncodingException { + return new IdRes(userService.newRecord(req).getId()); + } + + // @Operation(summary = "new user by public user") + @PostMapping("/registry") + @ResponseStatus(HttpStatus.CREATED) + // @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public ResponseEntity createPublicUserRecord(@RequestBody NewPublicUserReq req) + throws UnsupportedEncodingException { + logger.info("Create user request:"); + return ResponseEntity.ok(new IdRes(userService.newPublicUserRecord(req).getId())); + } + + @PostMapping("/admin/registry") + @ResponseStatus(HttpStatus.CREATED) + // @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public ResponseEntity createUserRecord(@RequestBody NewLionerUserReq req) + throws UnsupportedEncodingException, NoSuchMessageException, ParseException, IllegalAccessException { + return ResponseEntity.ok(new IdRes(userService.newUserRecord(req).getId())); + } + + // @Operation(summary = "update user", responses = { + // @ApiResponse(responseCode = "204"), + // @ApiResponse(responseCode = "400", content = @Content), + // @ApiResponse(responseCode = "404", content = @Content), + // }) + + @PutMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public void updateRecord(@PathVariable int id, @RequestBody @Valid UpdateUserReq req) { + userService.updateRecord(id, req); + } + + // @Operation(summary = "current user change password", description = "error: + // USER_WRONG_NEW_PWD = new password not available", responses = { + // @ApiResponse(responseCode = "204"), + // @ApiResponse(responseCode = "400", content = @Content), + // @ApiResponse(responseCode = "404", content = @Content), + // @ApiResponse(responseCode = "422", content = @Content(schema = + // @Schema(implementation = FailureRes.class))), + // }) + @PatchMapping("/change-password") + @ResponseStatus(HttpStatus.NO_CONTENT) + // @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public void changePassword(@RequestBody @Valid ChangePwdReq req) { + long id = SecurityUtils.getUser().get().getId(); + User instance = userService.find(id).orElseThrow(NotFoundException::new); + + logger.info("TEST req: " + req.getPassword()); + logger.info("TEST instance: " + instance.getPassword()); + + if (!passwordEncoder.matches(req.getPassword(), instance.getPassword())) { + throw new UnprocessableEntityException("Incorrect existing password"); + } + + PasswordRule rule = new PasswordRule(settingsService); + if (!PasswordUtils.checkPwd(req.getNewPassword(), rule, instance.getUsername())) { + throw new UnprocessableEntityException(rule.getWrongMsg()); + } + + List passHists = userPasswordHistoryService.getPasswordHistory(id); + passHists.stream().forEach(oldPass -> { + if (passwordEncoder.matches(req.getNewPassword(), oldPass)) { + throw new UnprocessableEntityException("The password already exists in the history of the past " + passwordHist + " times."); + } + }); + + instance.setPassword(passwordEncoder.encode(req.getNewPassword())); + userService.save(instance); + + // if password updated + UserPasswordHistory record = new UserPasswordHistory(); + record.setUserId(id); + record.setPassword(instance.getPassword()); + record.setDate(LocalDateTime.now()); + record = userPasswordHistoryService.save(record); + } + + // @Operation(summary = "reset password", responses = { + // @ApiResponse(responseCode = "204"), + // @ApiResponse(responseCode = "404", content = @Content), + // }) + @PostMapping("/{id}/reset-password") + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + @ResponseStatus(HttpStatus.NO_CONTENT) + public ResponseEntity resetPassword(@PathVariable long id) throws UnsupportedEncodingException { + String password = userService.resetPassword(id); + return ResponseEntity.ok(password); + } + + // @Operation(summary = "get password rules") + @GetMapping("/password-rule") + public PasswordRule passwordRule() { + return new PasswordRule(settingsService); + } + + public static class ChangePwdReq { + @NotBlank + private String password; + @NotBlank + private String newPassword; + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + + } + + @GetMapping("/auth/combo") + public RecordsRes> authComboJson(HttpServletRequest request) + throws ServletRequestBindingException { + System.out.println(request); + return new RecordsRes<>(userService.listUserAuth( + CriteriaArgsBuilder.withRequest(request) + .addInteger("userId") + .build())); + } + + @GetMapping("/combo") + public RecordsRes> comboJson(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(userService.searchForCombo( + CriteriaArgsBuilder.withRequest(request) + .addInteger(Params.ID) + .addStringLike(Params.QUERY) + .build())); + } + + @GetMapping("/audit/combo") + public RecordsRes> comboJsonWithDeletedUser(HttpServletRequest request) throws ServletRequestBindingException { + return new RecordsRes<>(userService.searchForAuditCombo( + CriteriaArgsBuilder.withRequest(request) + .addInteger(Params.ID) + .addStringLike(Params.QUERY) + .build())); + } + + @GetMapping("/loginLog") + @PreAuthorize("hasAuthority('MAINTAIN_USER')") + public RecordsRes> list(HttpServletRequest request) throws ServletRequestBindingException { + System.out.println(request); + List> temp = userService.listLoginLog( + CriteriaArgsBuilder.withRequest(request) + .addStringLike("username") + .addStringLike("ipAddr") + .addDate("fromDate") + .addDateTo("toDate") + .build()); + return new RecordsRes<>(temp); + } + + @GetMapping("/lotus") + public String getDataFromThirdPartyApi(HttpServletRequest request, @RequestParam String EMSDlotus) + throws Exception { + String xmlResponse = ""; + + if (ldapWebServiceRequired) { + xmlResponse = userService.fetchDataFromApi(EMSDlotus); + } else { + xmlResponse = "" + TempConst.LOTUS_TEMP_CONST; + } + return xmlResponse; + } + + @GetMapping("/lotusCombo") + public ResponseEntity lotusCombo(@RequestParam(defaultValue = "") String username) throws Exception { + AtomicInteger tempId = new AtomicInteger(0); + List> searchUser = ldapTemplate.search("", "(&(objectClass=*)(cn=*" + username + "*))", + (AttributesMapper>) (attrs -> { + Map user = new HashMap<>(); + user.put("id", tempId.getAndIncrement()); + user.put("label", attrs.get("cn").get().toString()); + user.put("key", attrs.get("cn").get().toString()); + user.put("mail", attrs.get("mail").get().toString()); + return user; + })); + return ResponseEntity.ok(Map.of("records",searchUser)); + } + + + @GetMapping("/checkDuplicate") + public Map checkDuplicate(@RequestParam String name, Long id) { + boolean isNameTaken = userService.isNameTaken(name, id); + return Map.of( + "isTaken", isNameTaken); + } + + @GetMapping("/getReminderFlag/{id}") + public Map getReminderFlag(@PathVariable Long id) { + User user = userService.find(id).get(); + return Map.of("isReminder", user.getReminderFlag()); + } + + @PostMapping("/setReminderFlag/{id}/{flag}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void setReminderFlag(@PathVariable Long id, @PathVariable Boolean flag) { + User user = userService.find(id).get(); + user.setReminderFlag(flag); + userService.save(user); + } + + @GetMapping("/isPasswordExpired") + public Map getIsPasswordExpired(@RequestParam Long id) { + Integer maximumDay = settingsService.getInt(SettingNames.SYS_PASSWORD_RULE_DURATION); + Integer dayDiff = userService.getLastChangePasswordDate(id); + return Map.of("expired", dayDiff >= maximumDay); + } +} diff --git a/src/main/resources/application-2fi-uat.yml b/src/main/resources/application-2fi-uat.yml new file mode 100644 index 0000000..6d2d203 --- /dev/null +++ b/src/main/resources/application-2fi-uat.yml @@ -0,0 +1,2 @@ +host: + url: http://192.168.1.82 \ No newline at end of file diff --git a/src/main/resources/application-db-2fi-prod.yml b/src/main/resources/application-db-2fi-prod.yml new file mode 100644 index 0000000..9c69c01 --- /dev/null +++ b/src/main/resources/application-db-2fi-prod.yml @@ -0,0 +1,5 @@ +spring: + datasource: + jdbc-url: jdbc:mysql://192.168.1.81:3306/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + username: root + password: secret \ No newline at end of file diff --git a/src/main/resources/application-db-2fi-uat.yml b/src/main/resources/application-db-2fi-uat.yml new file mode 100644 index 0000000..879df00 --- /dev/null +++ b/src/main/resources/application-db-2fi-uat.yml @@ -0,0 +1,5 @@ +spring: + datasource: + jdbc-url: jdbc:mysql://127.0.0.1:3306/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + username: root + password: secret \ No newline at end of file diff --git a/src/main/resources/application-db-emsd-prod.yml b/src/main/resources/application-db-emsd-prod.yml new file mode 100644 index 0000000..6dce3d1 --- /dev/null +++ b/src/main/resources/application-db-emsd-prod.yml @@ -0,0 +1,5 @@ +spring: + datasource: + jdbc-url: jdbc:mysql://10.16.11.77:3306/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + username: root + password: cFDp7988vc+$] \ No newline at end of file diff --git a/src/main/resources/application-db-emsd-uat.yml b/src/main/resources/application-db-emsd-uat.yml new file mode 100644 index 0000000..fc77468 --- /dev/null +++ b/src/main/resources/application-db-emsd-uat.yml @@ -0,0 +1,5 @@ +spring: + datasource: + jdbc-url: jdbc:mysql://10.16.11.78:3306/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + username: root + password: Tt603q1w2e3! \ No newline at end of file diff --git a/src/main/resources/application-db-local.yml b/src/main/resources/application-db-local.yml new file mode 100644 index 0000000..7116a02 --- /dev/null +++ b/src/main/resources/application-db-local.yml @@ -0,0 +1,5 @@ +spring: + datasource: + jdbc-url: jdbc:mysql://127.0.0.1:3308/lionerdb?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8 + username: root + password: secret \ No newline at end of file diff --git a/src/main/resources/application-emsd-prod.yml b/src/main/resources/application-emsd-prod.yml new file mode 100644 index 0000000..6a8b3fd --- /dev/null +++ b/src/main/resources/application-emsd-prod.yml @@ -0,0 +1,2 @@ +host: + url: https://ccsdawardrs.emsd.hksarg \ No newline at end of file diff --git a/src/main/resources/application-emsd-uat.yml b/src/main/resources/application-emsd-uat.yml new file mode 100644 index 0000000..9e37844 --- /dev/null +++ b/src/main/resources/application-emsd-uat.yml @@ -0,0 +1,2 @@ +host: + url: https://ccsdawardrs-uat.emsd.hksarg \ No newline at end of file diff --git a/src/main/resources/application-ldap-local.yml b/src/main/resources/application-ldap-local.yml new file mode 100644 index 0000000..c024404 --- /dev/null +++ b/src/main/resources/application-ldap-local.yml @@ -0,0 +1,14 @@ +spring: + ldap: + embedded: + port: 8389 + base-dn: dc=springframework,dc=org + ldif: classpath:ldap-test-users.ldif + validation: + enabled: false + urls: ldap://localhost:8389 + base: O=EMSD,dc=springframework,dc=org +emsd: + webservice: + required: false + urls: http://10.16.147.103/ldapwebserv/ldapwebserv.asmx/SearchEmployee \ No newline at end of file diff --git a/src/main/resources/application-ldap-prod.yml b/src/main/resources/application-ldap-prod.yml new file mode 100644 index 0000000..deb8520 --- /dev/null +++ b/src/main/resources/application-ldap-prod.yml @@ -0,0 +1,8 @@ +spring: + ldap: + urls: ldap://lnldap.emsd.hksarg + base: O=EMSD +emsd: + webservice: + required: true + urls: http://10.16.147.103/ldapwebserv/ldapwebserv.asmx/SearchEmployee diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..5997a49 --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,2 @@ +host: + url: http://localhost:3000 \ No newline at end of file diff --git a/src/main/resources/application-prod-linux.yml b/src/main/resources/application-prod-linux.yml new file mode 100644 index 0000000..41ac797 --- /dev/null +++ b/src/main/resources/application-prod-linux.yml @@ -0,0 +1,2 @@ +logging: + config: 'classpath:log4j2-prod-linux.yml' \ No newline at end of file diff --git a/src/main/resources/application-prod-win.yml b/src/main/resources/application-prod-win.yml new file mode 100644 index 0000000..b7b358c --- /dev/null +++ b/src/main/resources/application-prod-win.yml @@ -0,0 +1,2 @@ +logging: + config: 'classpath:log4j2-prod-win.yml' \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..5fef5d1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,33 @@ +server: + servlet: + contextPath: /api + encoding: + charset: UTF-8 + enabled: true + force: true + port: 8090 + error: + include-message: always + tomcat: + connection-timeout: 300000 +spring: + servlet: + multipart: + max-file-size: 500MB + max-request-size: 600MB + jpa: + hibernate: + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + database-platform: org.hibernate.dialect.MySQL8Dialect + properties: + hibernate: + dialect: + storage_engine: innodb + messages: + basename: i18n/messages + use-code-as-default-message: true + fallback-to-system-locale: false + +logging: + config: 'classpath:log4j2.yml' \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/00_system_base/01_create_table.sql b/src/main/resources/db/changelog/changes/00_system_base/01_create_table.sql new file mode 100644 index 0000000..c9ec7a2 --- /dev/null +++ b/src/main/resources/db/changelog/changes/00_system_base/01_create_table.sql @@ -0,0 +1,913 @@ +--liquibase formatted sql + +--changeset jason:create table +--comment: create table + +-- +-- Table structure for table `application` +-- + +DROP TABLE IF EXISTS `application`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `application` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `eventId` int NOT NULL, + `name` varchar(500) NOT NULL, + `description` varchar(500) DEFAULT NULL, + `responsibleOfficer` varchar(100) NOT NULL, + `status` varchar(50) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `application` +-- + +LOCK TABLES `application` WRITE; +/*!40000 ALTER TABLE `application` DISABLE KEYS */; +/*!40000 ALTER TABLE `application` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `application_sub_division` +-- + +DROP TABLE IF EXISTS `application_sub_division`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `application_sub_division` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `applicationId` int NOT NULL, + `subDivisionId` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `application_sub_division` +-- + +LOCK TABLES `application_sub_division` WRITE; +/*!40000 ALTER TABLE `application_sub_division` DISABLE KEYS */; +/*!40000 ALTER TABLE `application_sub_division` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `application_tag` +-- + +DROP TABLE IF EXISTS `application_tag`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `application_tag` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `applicationId` int NOT NULL, + `tagId` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `application_tag` +-- + +LOCK TABLES `application_tag` WRITE; +/*!40000 ALTER TABLE `application_tag` DISABLE KEYS */; +/*!40000 ALTER TABLE `application_tag` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `audit_log` +-- + +DROP TABLE IF EXISTS `audit_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `audit_log` ( + `tableName` varchar(30) NOT NULL, + `recordId` int NOT NULL, + `recordName` varchar(500) DEFAULT NULL, + `modifiedBy` int DEFAULT NULL, + `modified` datetime DEFAULT NULL, + `oldData` json DEFAULT NULL, + `newData` json DEFAULT NULL, + KEY `idx_tableName_recordId` (`tableName`,`recordId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `audit_log` +-- + +LOCK TABLES `audit_log` WRITE; +/*!40000 ALTER TABLE `audit_log` DISABLE KEYS */; +/*!40000 ALTER TABLE `audit_log` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `authority` +-- + +DROP TABLE IF EXISTS `authority`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `authority` ( + `id` int NOT NULL AUTO_INCREMENT, + `authority` varchar(255) NOT NULL, + `name` varchar(100) NOT NULL, + `module` varchar(50) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `authority` (`authority`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `authority` +-- + +LOCK TABLES `authority` WRITE; +/*!40000 ALTER TABLE `authority` DISABLE KEYS */; +/*!40000 ALTER TABLE `authority` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `award` +-- + +DROP TABLE IF EXISTS `award`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `award` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `applicationId` int NOT NULL, + `name` varchar(500) NOT NULL, + `nameCht` varchar(255) DEFAULT NULL, + `remarks` varchar(500) DEFAULT NULL, + `categoryId` int NOT NULL, + `receiveDate` datetime DEFAULT NULL, + `storageLocation` varchar(255) DEFAULT NULL, + `physicalAward` varchar(100) DEFAULT NULL, + `responsibleOfficer` varchar(100) NOT NULL, + `promotionChannel` json DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `award` +-- + +LOCK TABLES `award` WRITE; +/*!40000 ALTER TABLE `award` DISABLE KEYS */; +/*!40000 ALTER TABLE `award` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `award_sub_division` +-- + +DROP TABLE IF EXISTS `award_sub_division`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `award_sub_division` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `awardId` int NOT NULL, + `subDivisionId` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `award_sub_division` +-- + +LOCK TABLES `award_sub_division` WRITE; +/*!40000 ALTER TABLE `award_sub_division` DISABLE KEYS */; +/*!40000 ALTER TABLE `award_sub_division` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `category` +-- + +DROP TABLE IF EXISTS `category`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `category` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(50) NOT NULL, + `description` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `category` +-- + +LOCK TABLES `category` WRITE; +/*!40000 ALTER TABLE `category` DISABLE KEYS */; +/*!40000 ALTER TABLE `category` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `division` +-- + +DROP TABLE IF EXISTS `division`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `division` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(255) NOT NULL, + branch varchar(50) DEFAULT "Trading Services" NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `division` +-- + +LOCK TABLES `division` WRITE; +/*!40000 ALTER TABLE `division` DISABLE KEYS */; +/*!40000 ALTER TABLE `division` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `event` +-- + +DROP TABLE IF EXISTS `event`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `event` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(255) NOT NULL, + `nameCht` varchar(255) DEFAULT NULL, + `description` varchar(500) DEFAULT NULL, + `region` varchar(50) NOT NULL, + `organization` varchar(255) NOT NULL, + `eventType` varchar(50) NOT NULL, + `frequency` varchar(50) DEFAULT NULL, + `series` varchar(50) DEFAULT NULL, + `startDate` datetime NOT NULL, + `eventFrom` datetime DEFAULT NULL, + `eventTo` datetime DEFAULT NULL, + `applicationDeadline` datetime NOT NULL, + `nextApplicationDate` datetime DEFAULT NULL, + `announcementDate` datetime DEFAULT NULL, + `awardDate` datetime DEFAULT NULL, + `reminderFlag` tinyint(1) NOT NULL DEFAULT '0', + `reminderThreshold` int DEFAULT '0', + `reminderInterval` int DEFAULT '0', + `reminderLimit` int DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `event` +-- + +LOCK TABLES `event` WRITE; +/*!40000 ALTER TABLE `event` DISABLE KEYS */; +/*!40000 ALTER TABLE `event` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `event_sub_division` +-- + +DROP TABLE IF EXISTS `event_sub_division`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `event_sub_division` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `eventId` int NOT NULL, + `subDivisionId` int NOT NULL, + `oldReminderCount` int NOT NULL, + `suppressedOldReminder` tinyint(1) NOT NULL DEFAULT '0', + `newReminderCount` int NOT NULL, + `suppressedNewReminder` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `event_sub_division` +-- + +LOCK TABLES `event_sub_division` WRITE; +/*!40000 ALTER TABLE `event_sub_division` DISABLE KEYS */; +/*!40000 ALTER TABLE `event_sub_division` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `file` +-- + +DROP TABLE IF EXISTS `file`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `file` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `filename` varchar(255) NOT NULL, + `skey` varchar(50) NOT NULL, + `extension` varchar(10) DEFAULT NULL, + `mimetype` varchar(100) DEFAULT NULL, + `filesize` int DEFAULT NULL, + `remarks` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `file` +-- + +LOCK TABLES `file` WRITE; +/*!40000 ALTER TABLE `file` DISABLE KEYS */; +/*!40000 ALTER TABLE `file` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `file_blob` +-- + +DROP TABLE IF EXISTS `file_blob`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `file_blob` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `fileId` int NOT NULL, + `bytes` longblob NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `file_blob` +-- + +LOCK TABLES `file_blob` WRITE; +/*!40000 ALTER TABLE `file_blob` DISABLE KEYS */; +/*!40000 ALTER TABLE `file_blob` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `file_ref` +-- + +DROP TABLE IF EXISTS `file_ref`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `file_ref` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `fileId` int DEFAULT NULL, + `refCode` varchar(50) DEFAULT NULL, + `refId` int NOT NULL, + `refType` varchar(50) NOT NULL, + `remarks` varchar(255) DEFAULT NULL, + `thumbnailFileId` int DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `file_ref` +-- + +LOCK TABLES `file_ref` WRITE; +/*!40000 ALTER TABLE `file_ref` DISABLE KEYS */; +/*!40000 ALTER TABLE `file_ref` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `group` +-- + +DROP TABLE IF EXISTS `group`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `group` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(50) NOT NULL, + `description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `group` +-- + +LOCK TABLES `group` WRITE; +/*!40000 ALTER TABLE `group` DISABLE KEYS */; +/*!40000 ALTER TABLE `group` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `group_authority` +-- + +DROP TABLE IF EXISTS `group_authority`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `group_authority` ( + `groupId` int NOT NULL, + `authId` int NOT NULL, + PRIMARY KEY (`groupId`,`authId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `group_authority` +-- + +LOCK TABLES `group_authority` WRITE; +/*!40000 ALTER TABLE `group_authority` DISABLE KEYS */; +/*!40000 ALTER TABLE `group_authority` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `promotion_channel` +-- + +DROP TABLE IF EXISTS `promotion_channel`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `promotion_channel` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `promotion_channel` +-- + +LOCK TABLES `promotion_channel` WRITE; +/*!40000 ALTER TABLE `promotion_channel` DISABLE KEYS */; +/*!40000 ALTER TABLE `promotion_channel` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `search_criteria_template` +-- + +DROP TABLE IF EXISTS `search_criteria_template`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `search_criteria_template` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `userId` int NOT NULL, + `name` varchar(255) NOT NULL, + `module` varchar(50) NOT NULL, + `criteria` json NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `search_criteria_template` +-- + +LOCK TABLES `search_criteria_template` WRITE; +/*!40000 ALTER TABLE `search_criteria_template` DISABLE KEYS */; +/*!40000 ALTER TABLE `search_criteria_template` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `settings` +-- + +DROP TABLE IF EXISTS `settings`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `settings` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL, + `value` text NOT NULL, + `category` varchar(50) DEFAULT NULL, + `type` varchar(10) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `name_idx` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `settings` +-- + +LOCK TABLES `settings` WRITE; +/*!40000 ALTER TABLE `settings` DISABLE KEYS */; +/*!40000 ALTER TABLE `settings` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `sub_division` +-- + +DROP TABLE IF EXISTS `sub_division`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `sub_division` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(255) NOT NULL, + `divisionId` int NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `sub_division` +-- + +LOCK TABLES `sub_division` WRITE; +/*!40000 ALTER TABLE `sub_division` DISABLE KEYS */; +/*!40000 ALTER TABLE `sub_division` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `tag` +-- + +DROP TABLE IF EXISTS `tag`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `tag` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `name` varchar(100) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `tag` +-- + +LOCK TABLES `tag` WRITE; +/*!40000 ALTER TABLE `tag` DISABLE KEYS */; +/*!40000 ALTER TABLE `tag` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `todo_reminder` +-- + +DROP TABLE IF EXISTS `todo_reminder`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `todo_reminder` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `eventId` int NOT NULL, + `subDivisionId` int NOT NULL, + `reminderType` varchar(30) NOT NULL, + `title` varchar(255) NOT NULL, + `message` text NOT NULL, + `suppressedBy` int DEFAULT NULL, + `suppressedDate` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `todo_reminder` +-- + +LOCK TABLES `todo_reminder` WRITE; +/*!40000 ALTER TABLE `todo_reminder` DISABLE KEYS */; +/*!40000 ALTER TABLE `todo_reminder` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `todo_reminder_email_log` +-- + +DROP TABLE IF EXISTS `todo_reminder_email_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `todo_reminder_email_log` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `eventId` int NOT NULL, + `subDivisionId` int NOT NULL, + `reminderType` varchar(30) NOT NULL, + `userId` int NOT NULL, + `sendDate` datetime NOT NULL, + `success` tinyint(1) NOT NULL DEFAULT '0', + `response` text, + `content` text, + `resendSuccess` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `todo_reminder_email_log` +-- + +LOCK TABLES `todo_reminder_email_log` WRITE; +/*!40000 ALTER TABLE `todo_reminder_email_log` DISABLE KEYS */; +/*!40000 ALTER TABLE `todo_reminder_email_log` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `todo_reminder_noted` +-- + +DROP TABLE IF EXISTS `todo_reminder_noted`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `todo_reminder_noted` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `todoReminderId` int NOT NULL, + `userId` int NOT NULL, + `notedDate` datetime NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `todo_reminder_noted` +-- + +LOCK TABLES `todo_reminder_noted` WRITE; +/*!40000 ALTER TABLE `todo_reminder_noted` DISABLE KEYS */; +/*!40000 ALTER TABLE `todo_reminder_noted` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user` +-- + +DROP TABLE IF EXISTS `user`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `username` varchar(30) NOT NULL, + `password` varchar(60) DEFAULT NULL, + `locked` tinyint(1) NOT NULL DEFAULT '0', + `expiryDate` date DEFAULT NULL, + `name` varchar(50) NOT NULL, + `locale` varchar(5) DEFAULT NULL, + `fullname` varchar(90) DEFAULT NULL, + `firstname` varchar(45) DEFAULT NULL, + `lastname` varchar(30) DEFAULT NULL, + `title` varchar(60) DEFAULT NULL, + `department` varchar(60) DEFAULT NULL, + `email` varchar(120) DEFAULT NULL, + `phone1` varchar(30) DEFAULT NULL, + `phone2` varchar(30) DEFAULT NULL, + `remarks` varchar(600) DEFAULT NULL, + `lotusNotesUser` tinyint(1) NOT NULL DEFAULT '0', + `reminderFlag` tinyint(1) DEFAULT '1', + `post` varchar(50) DEFAULT NULL, + `subDivisionId` int DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user` +-- + +LOCK TABLES `user` WRITE; +/*!40000 ALTER TABLE `user` DISABLE KEYS */; +/*!40000 ALTER TABLE `user` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_authority` +-- + +DROP TABLE IF EXISTS `user_authority`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_authority` ( + `userId` int NOT NULL, + `authId` int NOT NULL, + PRIMARY KEY (`userId`,`authId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_authority` +-- + +LOCK TABLES `user_authority` WRITE; +/*!40000 ALTER TABLE `user_authority` DISABLE KEYS */; +/*!40000 ALTER TABLE `user_authority` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_group` +-- + +DROP TABLE IF EXISTS `user_group`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_group` ( + `groupId` int NOT NULL, + `userId` int NOT NULL, + PRIMARY KEY (`groupId`,`userId`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_group` +-- + +LOCK TABLES `user_group` WRITE; +/*!40000 ALTER TABLE `user_group` DISABLE KEYS */; +/*!40000 ALTER TABLE `user_group` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_login_log` +-- + +DROP TABLE IF EXISTS `user_login_log`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_login_log` ( + `username` varchar(32) NOT NULL, + `loginTime` datetime NOT NULL, + `ipAddr` varchar(45) NOT NULL, + `success` tinyint(1) NOT NULL, + PRIMARY KEY (`username`,`loginTime`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_login_log` +-- + +LOCK TABLES `user_login_log` WRITE; +/*!40000 ALTER TABLE `user_login_log` DISABLE KEYS */; +/*!40000 ALTER TABLE `user_login_log` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `user_password_history` +-- + +DROP TABLE IF EXISTS `user_password_history`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `user_password_history` ( + `id` int NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` int DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` int DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + `userId` int NOT NULL, + `date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `password` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Dumping data for table `user_password_history` +-- + +LOCK TABLES `user_password_history` WRITE; +/*!40000 ALTER TABLE `user_password_history` DISABLE KEYS */; +/*!40000 ALTER TABLE `user_password_history` ENABLE KEYS */; +UNLOCK TABLES; + diff --git a/src/main/resources/db/changelog/changes/00_system_base/02_insert_group_authority.sql b/src/main/resources/db/changelog/changes/00_system_base/02_insert_group_authority.sql new file mode 100644 index 0000000..0e4b5fd --- /dev/null +++ b/src/main/resources/db/changelog/changes/00_system_base/02_insert_group_authority.sql @@ -0,0 +1,35 @@ +--liquibase formatted sql + +--changeset jason:insert group authority +--comment: insert group authority + +LOCK TABLES `authority` WRITE; +/*!40000 ALTER TABLE `authority` DISABLE KEYS */; +INSERT INTO `authority` VALUES (1,'SUPPRESS_REMINDER','Suppress Reminder','Reminder','Allow to suppress reminder'),(2,'VIEW_EVENT','View Event','Event','Allow to view event'),(3,'EDIT_EVENT','Edit Event','Event','Allow to edit event'),(4,'DELETE_EVENT','Delete Event','Event','Allow to delete event'),(5,'VIEW_APPLICATION','View Application','Application','Allow to view application'),(6,'EDIT_APPLICATION','Edit Application','Application','Allow to edit application'),(7,'DELETE_APPLICATION','Delete Application','Application','Allow to delete application'),(8,'VIEW_AWARD','View Award','Award','Allow to view award'),(9,'EDIT_AWARD','Edit Award','Award','Allow to edit award'),(10,'DELETE_AWARD','Delete Award','Award','Allow to delete award'),(11,'MAINTAIN_CATEGORY','Maintain Category','System Administration','Allow to maintain category'),(12,'MAINTAIN_TAG','Maintain Tag','System Administration','Allow to maintain tag'),(13,'MAINTAIN_PROMOTION_CHANNEL','Maintain Promotion Channel','System Administration','Allow to maintain promotion channel'),(14,'MAINTAIN_DIVISION','Maintain Division','System Administration','Allow to maintain division'),(15,'MAINTAIN_USER_GROUP','Maintain User Group','System Administration','Allow to maintain user group'),(16,'MAINTAIN_USER','Maintain User','System Administration','Allow to maintain user'),(17,'VIEW_AUDIT_LOG','View Audit Log','System Administration','Allow to view audit log'),(18,'VIEW_LOGIN_LOG','View Login Log','System Administration','Allow to view login log'),(19,'MANAGE_PASSWORD_POLICY','Manage Password Policy','System Administration','Allow to manage password policy'),(20,'MANAGE_SYSTEM_CONFIGURATION','Manage System Configuration','System Administration','Allow to manage system configuration'),(21,'VIEW_ALL_RECORD','View All Records','Other','\"Allow to view all records in event, application and award\"'),(22,'EDIT_ALL_RECORD','Edit All Records','Other','\"Allow to view all records in event, application and award\"'); +/*!40000 ALTER TABLE `authority` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `group` +-- + +LOCK TABLES `group` WRITE; +/*!40000 ALTER TABLE `group` DISABLE KEYS */; +INSERT INTO `group` VALUES + (1,'2023-10-04 14:37:26',1,0,'2023-10-04 14:37:26',1,0,'Viewer','Viewer Group'), + (2,'2023-10-04 14:38:02',1,0,'2023-10-04 14:38:02',1,0,'Editor','Editor Group'), + (3,'2023-10-04 14:39:11',1,0,'2023-10-04 14:39:11',1,0,'System Administrator','System Administrator Group'), + (4,'2023-10-04 14:38:02',1,0,'2023-10-04 14:38:02',1,0,'Editor 2','Editor Group 2') + ; +/*!40000 ALTER TABLE `group` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Table structure for table `group_authority` +-- + +LOCK TABLES `group_authority` WRITE; +/*!40000 ALTER TABLE `group_authority` DISABLE KEYS */; +INSERT INTO `group_authority` VALUES (1,2),(1,5),(1,8),(2,1),(2,2),(2,3),(2,4),(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(3,1),(3,2),(3,3),(3,4),(3,5),(3,6),(3,7),(3,8),(3,9),(3,10),(3,11),(3,12),(3,13),(3,14),(3,15),(3,16),(3,17),(3,18),(3,19),(3,20),(3,21),(3,22); +/*!40000 ALTER TABLE `group_authority` ENABLE KEYS */; +UNLOCK TABLES; diff --git a/src/main/resources/db/changelog/changes/00_system_base/03_insert_setting.sql b/src/main/resources/db/changelog/changes/00_system_base/03_insert_setting.sql new file mode 100644 index 0000000..a2814b3 --- /dev/null +++ b/src/main/resources/db/changelog/changes/00_system_base/03_insert_setting.sql @@ -0,0 +1,10 @@ +--liquibase formatted sql + +--changeset jason:insert setting +--comment: insert setting + +LOCK TABLES `settings` WRITE; +/*!40000 ALTER TABLE `settings` DISABLE KEYS */; +INSERT INTO `settings` VALUES (1,'SYS.idleLogoutTime','60','settings','integer'),(2,'SYS.reminder.before','60','settings','integer'),(3,'SYS.reminder.interval','5','settings','integer'),(4,'SYS.reminder.limit','2','settings','integer'),(5,'SYS.reminder.limit.maxValue','2','settings','integer'),(6,'SYS.reminder.eventWithin','2','settings','integer'),(7,'MAIL.smtp.host','hq2-server.emsd.hksarg','settings','string'),(8,'MAIL.smtp.port','25','settings','integer'),(9,'MAIL.smtp.username','no_reply_csc@emsd.gov.hk','settings','string'),(10,'MAIL.smtp.password','','settings','string'),(11,'MAIL.smtp.auth','false','settings','boolean'),(13,'SYS.password.rule.length.min','10','passwordPolicy','integer'),(14,'SYS.password.rule.duration','180','passwordPolicy','integer'),(15,'SYS.password.rule.history','8','passwordPolicy','integer'),(16,'SYS.loginAttempt.limit','5','passwordPolicy','integer'),(17,'SYS.loginAttempt.penalityTime','5','passwordPolicy','integer'),(18,'SYS.password.rule.numberAndAlphabetic','true','passwordPolicy','boolean'),(19,'SYS.password.rule.specialCharacter','true','passwordPolicy','boolean'),(20,'SYS.password.rule.notContainUsername','true','passwordPolicy','boolean'),(21,'SYS.password.rule.notContainThreeConsecutiveCharacters','true','passwordPolicy','boolean'),(22,'SYS.email.template.announcement','\n\n \n \n \n \n \n \n <#assign arsLink>${arsLink}\n

\n
\n

Dear ${receiver},

\n\n

Friendly remind you the date of result announcement for below event. :

\n\n
\n
Event Name
\n
${eventName}
\n \n
\n
\n
Application
\n
${applicationDetail}
\n
\n
\n
Announcement Date
\n
${announcementDate}
\n
\n\n\n

You may click here to update your achievements at Award Registration System. Thank you for your participation.

\n
\n

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

\n

This is a system generated message, please do not reply to this email. For any enquiry, please contact your system administrator.

\n

Best Regards,

\n

System Administrator of Award Registration System

\n

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

\n
\n
\n \n\n','emailTemplate','string'),(23,'SYS.email.template.application.reminder.newEvent','\n\n \n \n \n \n \n \n <#assign arsLink>${arsLink}\n
\n
\n

Dear ${receiver},

\n\n

Please note below event will be opened for application:

\n\n
\n
Event Name
\n
${eventName}
\n \n
\n
\n
Application Deadline
\n
${applicationDeadLine}
\n
\n\n\n

Please click here to create application at Award Registration System. Thank You.

\n
\n

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

\n

This is a system generated message, please do not reply to this email. For any enquiry, please contact your system administrator.

\n

Best Regards,

\n

System Administrator of Award Registration System

\n

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

\n
\n \n
\n \n\n\n','emailTemplate','string'),(24,'SYS.email.template.application.reminder.oldEvent','\n\n \n \n \n \n \n \n <#assign arsLink>${arsLink}\n
\n
\n

Dear ${receiver},

\n\n

Please note below event will be opened for next application:

\n\n
\n
Past Event Name
\n
${eventName}
\n \n
\n
\n
Next Application Date
\n
${applicationDeadLine}
\n
\n\n\n

Please click here to create event and application at Award Registration System. Thank You.

\n
\n

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

\n

This is a system generated message, please do not reply to this email. For any enquiry, please contact your system administrator.

\n

Best Regards,

\n

System Administrator of Award Registration System

\n

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

\n
\n \n
\n \n\n','emailTemplate','string'); +/*!40000 ALTER TABLE `settings` ENABLE KEYS */; +UNLOCK TABLES; diff --git a/src/main/resources/db/changelog/changes/01_data_conversion/01_insert_data.sql b/src/main/resources/db/changelog/changes/01_data_conversion/01_insert_data.sql new file mode 100644 index 0000000..4956b30 --- /dev/null +++ b/src/main/resources/db/changelog/changes/01_data_conversion/01_insert_data.sql @@ -0,0 +1,3391 @@ +--liquibase formatted sql +--changeset system:data conversion + +--comment: tag +INSERT INTO tag (id,name) VALUES +(1, "Artificial Intelligence"), +(2, "Internet of things"), +(3, "Innovative Technology"), +(4, "Visionary Leadership"), +(5, "Effective HR policies, procedures and practices"), +(6, "Competent and Capable Workforce"), +(7, "Knowledge Management"), +(8, "Caring Culture"); + + +--comment: promotion_channel +INSERT INTO promotion_channel (id,name) VALUES +(1, "Group Voice"), +(2, "Director's Blog"), +(3, "Voice Link"), +(4, "Annual Report"), +(5, "Advertorial"), +(6, "Youtube"), +(7, "Facebook"), +(8, "Instagram"); + + +--comment: category +INSERT INTO category (id,name,description) VALUES +(1, "創新", null), +(2, "惠民", null), +(3, "傳承", null), +(4, "同心", null), +(5, "創新科技", null), +(6, "技能培訓", null), +(7, "公共服務", null), +(8, "專業技能", null), +(9, "NA", null); + + +--comment: division +INSERT INTO division (id,name,branch) VALUES +(1, "Accounting Services Unit", "Regulatory Services"), +(2, "Boundary Crossing Facilities and Transport Services Division", "Trading Services"), +(3, "Contract Advisory Unit", "Trading Services"), +(4, "Corporate Services Division", "Trading Services"), +(5, "Departmental Administration Division", "Regulatory Services"), +(6, "Digitalisation and Technology Division", "Trading Services"), +(7, "Directorate", "Regulatory Services"), +(8, "Electricity Legislation Division", "Regulatory Services"), +(9, "Electricity Team", "Regulatory Services"), +(10, "Energy Efficiency Division", "Regulatory Services"), +(11, "Finance Division", "Trading Services"), +(12, "Gas & General Legislation Branch", "Regulatory Services"), +(13, "General Engineering Services Division", "Trading Services"), +(14, "Health Sector Division", "Trading Services"), +(15, "Municipal Sector Division", "Trading Services"), +(16, "Railways Branch", "Regulatory Services"), +(17, "Security and Vehicle Services Division", "Trading Services"), +(18, "Staff Management Unit", "Regulatory Services"), +(19, "Staff Relations Unit", "Regulatory Services"), +(20, "Technical Secretariat", "Regulatory Services"), +(21, "Trading Departmental Control", "Trading Services"), +(22, "Electrical & Mechanical Services Department", "Regulatory Services"), +(23, "Other Department", "Regulatory Services"); + + +--comment: sub_division +INSERT INTO sub_division (id, divisionId, name ) VALUES +(1, (SELECT d.id FROM division d WHERE d.name = "Accounting Services Unit"), "Accounting Services Unit"), +(2, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Boundary Crossing Facilities Sub-division (Airport Services 1)"), +(3, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Boundary Crossing Facilities Sub-division (Airport Services 2)"), +(4, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Boundary Crossing Facilities Sub-division (Airport Services 3)"), +(5, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 1)"), +(6, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), +(7, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), +(8, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services A)"), +(9, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services B)"), +(10, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services C)"), +(11, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services D)"), +(12, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services E)"), +(13, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services F)"), +(14, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services G)"), +(15, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "Transport Services Sub-division (Transport Services H)"), +(16, (SELECT d.id FROM division d WHERE d.name = "Boundary Crossing Facilities and Transport Services Division"), "BTSD"), +(17, (SELECT d.id FROM division d WHERE d.name = "Contract Advisory Unit"), "Contract Advisory Unit 1"), +(18, (SELECT d.id FROM division d WHERE d.name = "Contract Advisory Unit"), "Contract Advisory Unit 2"), +(19, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Business Planning Sub-division"), +(20, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Corporate Communications Sub-division"), +(21, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Energy & Efficiency C6 Sub-division"), +(22, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Energy & Efficiency C8 Sub-division"), +(23, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Energy & Efficiency C9 Sub-division"), +(24, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Hydrogen Fuel Safety 1 Sub-division"), +(25, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Hydrogen Fuel Safety 2 Sub-division"), +(26, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Hydrogen Fuel Safety 3 Sub-division"), +(27, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Quality and Safety Sub-division"), +(28, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Supplies Sub-division"), +(29, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Training Unit 1"), +(30, (SELECT d.id FROM division d WHERE d.name = "Corporate Services Division"), "Training Unit 2"), +(31, (SELECT d.id FROM division d WHERE d.name = "Departmental Administration Division"), "Administration Section"), +(32, (SELECT d.id FROM division d WHERE d.name = "Departmental Administration Division"), "Personnel and Establishment Section"), +(33, (SELECT d.id FROM division d WHERE d.name = "Departmental Administration Division"), "Recruitment & Promotion Section"), +(34, (SELECT d.id FROM division d WHERE d.name = "Departmental Administration Division"), "Official Language Section"), +(35, (SELECT d.id FROM division d WHERE d.name = "Departmental Administration Division"), "Staff Welfare Section"), +(36, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Artificial Intelligence Sub-division"), +(37, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Building Information Modelling Sub-division"), +(38, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "E&M and BS Technology Development Sub-division"), +(39, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Electronic Technology Development Sub-division"), +(40, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Government-Wide Internet-of-Things Network"), +(41, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Green Technology Sub-Division"), +(42, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Information Technology Development Sub-division"), +(43, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Information Technology Strategic Support"), +(44, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "Inno-Office"), +(45, (SELECT d.id FROM division d WHERE d.name = "Digitalisation and Technology Division"), "DTD"), +(46, (SELECT d.id FROM division d WHERE d.name = "Directorate"), "Directorate"), +(47, (SELECT d.id FROM division d WHERE d.name = "Electricity Legislation Division"), "Consumer Installations Sub-division 1"), +(48, (SELECT d.id FROM division d WHERE d.name = "Electricity Legislation Division"), "Consumer Installations Sub-division 2"), +(49, (SELECT d.id FROM division d WHERE d.name = "Electricity Legislation Division"), "Electrical Products Sub-division"), +(50, (SELECT d.id FROM division d WHERE d.name = "Electricity Legislation Division"), "Nuclear and Utility Safety Sub-division"), +(51, (SELECT d.id FROM division d WHERE d.name = "Electricity Legislation Division"), "Publicity and Prosecution Sub-division"), +(52, (SELECT d.id FROM division d WHERE d.name = "Electricity Team"), "Electricity Market Development 1 Sub-division"), +(53, (SELECT d.id FROM division d WHERE d.name = "Electricity Team"), "Electricity Market Development 2 Sub-division"), +(54, (SELECT d.id FROM division d WHERE d.name = "Electricity Team"), "Utility Monitoring 1 Sub-division"), +(55, (SELECT d.id FROM division d WHERE d.name = "Electricity Team"), "Utility Monitoring 2 Sub-division"), +(56, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A1"), +(57, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A10"), +(58, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A11"), +(59, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A12"), +(60, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A13"), +(61, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A2"), +(62, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A3"), +(63, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A4"), +(64, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A5"), +(65, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A6"), +(66, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A7"), +(67, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A8"), +(68, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division A9"), +(69, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B1"), +(70, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B2"), +(71, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B3"), +(72, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B4"), +(73, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B5"), +(74, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B6"), +(75, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division B7"), +(76, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C1"), +(77, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C10"), +(78, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C2"), +(79, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C3"), +(80, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C4"), +(81, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C5"), +(82, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C6"), +(83, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C7"), +(84, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C8"), +(85, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "Energy Efficiency Sub-division C9"), +(86, (SELECT d.id FROM division d WHERE d.name = "Energy Efficiency Division"), "EEDC"), +(87, (SELECT d.id FROM division d WHERE d.name = "Finance Division"), "Financial Accounting Sub-division"), +(88, (SELECT d.id FROM division d WHERE d.name = "Finance Division"), "Internal Audit Sub-division"), +(89, (SELECT d.id FROM division d WHERE d.name = "Finance Division"), "Management Accounting Sub-division"), +(90, (SELECT d.id FROM division d WHERE d.name = "Finance Division"), "Special Duty Sub-division"), +(91, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards A1 Sub-division"), +(92, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards A2 Sub-division"), +(93, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards A3 Sub-division"), +(94, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards B1 Sub-division"), +(95, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards B2 Sub-division"), +(96, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards B3 Sub-division"), +(97, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Gas Standards B4 Sub-division"), +(98, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 1"), +(99, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 2"), +(100, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 3"), +(101, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 4"), +(102, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 5"), +(103, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 6"), +(104, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "General Legislation Sub-division 7"), +(105, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Vehicle Maintenance Registration Unit1"), +(106, (SELECT d.id FROM division d WHERE d.name = "Gas & General Legislation Branch"), "Vehicle Maintenance Registration Unit2"), +(107, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "Accommodation Design Group"), +(108, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services - (Kowloon) Maintenance Sub-division"), +(109, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services - (New Territories) Maintenance Sub-division"), +(110, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Building Services Sub-division A"), +(111, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Building Services Sub-division B"), +(112, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Building Services Sub-division C"), +(113, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Building Services Sub-division D"), +(114, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Building Services Sub-division E"), +(115, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Building Services Sub-division F"), +(116, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Project Management Branch 1 Sub-division"), +(117, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Project Management Branch 2 Sub-division"), +(118, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Project Management Branch 3 Sub-division"), +(119, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Project Sub-division"), +(120, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "General Engineering Services Special Duty Sub-division"), +(121, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "GES - (HK) Maintenance Sub-division 1"), +(122, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "GES - (HK) Maintenance Sub-division 2"), +(123, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "GES - (K) Maintenance Sub-division"), +(124, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "GES - (NT) Maintenance Sub-division"), +(125, (SELECT d.id FROM division d WHERE d.name = "General Engineering Services Division"), "GESD"), +(126, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (Hong Kong East) Sub-division"), +(127, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (Hong Kong West) Sub-division"), +(128, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (Kowloon Central) Sub-division"), +(129, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (Kowloon East) Sub-division"), +(130, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (Kowloon West) Sub-division"), +(131, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (New Territories East) Sub-division"), +(132, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services (New Territories West) Sub-division"), +(133, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services Project Sub-division 1"), +(134, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services Project Sub-division 2"), +(135, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services Project Sub-division 3"), +(136, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services Project Sub-division 4"), +(137, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector Services Project Sub-division 5"), +(138, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "Health Sector/Special Duty 1 Sub-division"), +(139, (SELECT d.id FROM division d WHERE d.name = "Health Sector Division"), "HSD"), +(140, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "Municipal Sector Cruise Terminal Sub-division"), +(141, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "Municipal Sector Project Sub-division"), +(142, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS (CS) Sub-division"), +(143, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS (HK) Sub-division"), +(144, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS (K) Sub-division"), +(145, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS (NT1) Sub-division"), +(146, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS (NT2) Sub-division"), +(147, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS Special Duties Sub-division"), +(148, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunS Vessel Traffic Services System Sub-division"), +(149, (SELECT d.id FROM division d WHERE d.name = "Municipal Sector Division"), "MunSD"), +(150, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Administration Team"), +(151, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 1"), +(152, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 2"), +(153, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 3"), +(154, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 4"), +(155, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 5"), +(156, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 6"), +(157, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 7"), +(158, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 8"), +(159, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 9"), +(160, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 10"), +(161, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 11"), +(162, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 12"), +(163, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 13"), +(164, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 14"), +(165, (SELECT d.id FROM division d WHERE d.name = "Railways Branch"), "Railways Sub-division 15"), +(166, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Professional Support Sub-division"), +(167, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/Central Services Sub-division"), +(168, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/Drainage Services Sub-division"), +(169, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/Electronic Project Sub-division"), +(170, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/Hong Kong Sub-division"), +(171, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/Kowloon Sub-division"), +(172, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/New Territories Sub-division"), +(173, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Security/Project Sub-division"), +(174, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), +(175, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "Vehicle Engineering S/D (Vehicle) Sub-division"), +(176, (SELECT d.id FROM division d WHERE d.name = "Security and Vehicle Services Division"), "SVSD"), +(177, (SELECT d.id FROM division d WHERE d.name = "Staff Management Unit"), "Staff Management Unit 1"), +(178, (SELECT d.id FROM division d WHERE d.name = "Staff Management Unit"), "Staff Management Unit 2"), +(179, (SELECT d.id FROM division d WHERE d.name = "Staff Management Unit"), "Staff Management Unit 3"), +(180, (SELECT d.id FROM division d WHERE d.name = "Staff Relations Unit"), "Staff Relations Unit"), +(181, (SELECT d.id FROM division d WHERE d.name = "Technical Secretariat"), "Technical Secretariat"), +(182, (SELECT d.id FROM division d WHERE d.name = "Trading Departmental Control"), "Trading Departmental Control"), +(183, (SELECT d.id FROM division d WHERE d.name = "Electrical & Mechanical Services Department"), "Electrical & Mechanical Services Department"), +(184, (SELECT d.id FROM division d WHERE d.name = "Other Department"), "Other Department"); + + +--comment: award_sub_division +INSERT INTO award_sub_division (awardId, subDivisionId) VALUES +(1, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(2, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(3, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(4, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(5, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(6, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(7, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(8, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(9, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(10, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(11, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(12, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(13, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(14, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(15, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(16, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(17, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(18, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(19, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(20, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(21, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(22, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(23, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(24, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(25, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(26, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(27, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(28, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(29, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(30, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(31, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(32, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(33, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(34, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(35, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(36, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(37, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(38, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(39, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(40, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(41, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(42, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(43, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(44, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(45, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(46, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(47, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(48, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(49, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(50, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(51, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(52, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(53, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(54, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(55, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(56, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(57, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(58, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(59, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(60, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(61, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(62, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(63, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(64, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(65, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(66, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(67, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(68, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(69, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(70, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(71, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(72, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(73, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(74, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(75, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(76, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(77, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(78, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(79, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(80, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(81, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(82, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(83, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(84, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(85, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(86, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(87, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(88, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(89, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(90, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(91, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(92, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(93, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(94, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(95, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(96, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(97, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(98, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(99, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(100, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(101, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(102, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(103, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(104, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(105, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(106, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(107, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(108, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(109, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(110, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(111, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(112, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(113, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(114, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(115, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(116, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(117, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(118, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(119, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(120, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(121, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(122, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(123, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(124, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(125, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(126, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(127, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(128, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(129, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(130, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(131, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(132, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(133, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(134, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(135, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(136, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(137, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(138, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(139, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(140, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(141, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(142, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(143, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(144, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(145, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(146, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(147, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(148, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(149, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(150, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(151, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(152, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(153, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(154, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(155, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(156, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(157, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(158, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(159, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(160, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(161, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(162, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(163, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(164, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(165, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(166, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(167, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(168, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(169, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(170, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(171, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(172, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(173, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(174, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(175, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(176, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(177, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(178, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(179, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(180, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(181, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(182, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(183, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(184, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(185, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(186, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(187, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(188, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(189, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(190, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(191, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(192, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(193, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(194, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(195, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(196, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(197, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(198, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(199, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(200, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(201, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(202, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(203, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(204, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(205, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(206, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(207, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(208, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(209, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(210, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(211, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(212, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(213, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(214, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(215, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(216, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(217, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(218, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(219, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(220, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(221, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(222, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(223, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(224, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(225, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(226, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(227, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(228, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(229, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(230, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(231, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(232, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(233, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(234, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )); + + +--comment: application_sub_division +INSERT INTO application_sub_division (applicationId, subDivisionId) VALUES +(1, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(2, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(3, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(4, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(5, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(6, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(7, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(8, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(9, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(10, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(11, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(12, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(13, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(14, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(15, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(16, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(17, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(18, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(19, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(20, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(21, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(22, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(23, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(24, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(25, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(26, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(27, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(28, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(29, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(30, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(31, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(32, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(33, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(34, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(35, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(36, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(37, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(38, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(39, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(40, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(41, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(42, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(43, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(44, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(45, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(46, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(47, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(48, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(49, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(50, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(51, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(52, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(53, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(54, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(55, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(56, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(57, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(58, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(59, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(60, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(61, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(62, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(63, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(64, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(65, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(66, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(67, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(68, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(69, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(70, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(71, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(72, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(73, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(74, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(75, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(76, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(77, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(78, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(79, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(80, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(81, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(82, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(83, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(84, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(85, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(86, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(87, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(88, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(89, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(90, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(91, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(92, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(93, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(94, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(95, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(96, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(97, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(98, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(99, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(100, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(101, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(102, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(103, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(104, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(105, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(106, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(107, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(108, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(109, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(110, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(111, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(112, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(113, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(114, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(115, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(116, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(117, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(118, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(119, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(120, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(121, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(122, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(123, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(124, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(125, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(126, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(127, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(128, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(129, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(130, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(131, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(132, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(133, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(134, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(135, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(136, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(137, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(138, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(139, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(140, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(141, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(142, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(143, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(144, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(145, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(146, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(147, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(148, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(149, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(150, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(151, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(152, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(153, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(154, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(155, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(156, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(157, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(158, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(159, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(160, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(161, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(162, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(163, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(164, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(165, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(166, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(167, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(168, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(169, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(170, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(171, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(172, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(173, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(174, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(175, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(176, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(177, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(178, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(179, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(180, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(181, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(182, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(183, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(184, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(185, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(186, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(187, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(188, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(189, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(190, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(191, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(192, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(193, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(194, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(195, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(196, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(197, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(198, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(199, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(200, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(201, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(202, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(203, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(204, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(205, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(206, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(207, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(208, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(209, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(210, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(211, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(212, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(213, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(214, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(215, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(216, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(217, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(218, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(219, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(220, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(221, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(222, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(223, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )), +(224, (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department" )); + + +--comment: event_sub_division +INSERT INTO event_sub_division (eventId, subDivisionId, oldReminderCount, suppressedOldReminder, newReminderCount, suppressedNewReminder) VALUES +(1, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(2, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(3, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(4, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(5, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(6, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(7, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(8, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(9, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(10, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(11, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(12, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(13, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(14, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(15, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(16, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(17, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(18, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(19, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(20, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(21, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(22, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(23, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(24, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(25, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(26, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(27, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(28, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(29, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(30, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(31, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(32, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(33, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(34, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(35, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(36, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(37, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(38, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(39, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(40, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(41, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(42, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(43, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(44, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(45, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(46, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(47, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(48, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(49, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(50, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(51, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(52, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(53, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(54, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(55, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(56, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(57, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(58, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(59, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(60, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(61, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(62, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(63, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(64, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(65, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(66, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(67, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(68, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(69, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(70, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(71, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(72, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(73, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(74, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(75, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(76, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(77, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(78, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(79, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(80, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(81, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(82, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(83, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(84, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(85, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(86, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(87, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(88, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(89, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(90, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(91, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(92, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(93, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(94, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(95, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(96, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(97, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(98, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(99, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(100, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(101, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(102, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(103, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(104, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(105, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(106, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0), +(107, ( SELECT sd.id FROM sub_division sd WHERE sd.name = "Electrical & Mechanical Services Department"), 0, 0, 0, 0); + + +--comment: application_tag +INSERT INTO application_tag (applicationId, tagId) VALUES +(203, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(203, (SELECT t.id FROM tag t WHERE t.name = "Internet of things" )), +(204, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(205, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(206, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(207, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(208, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(209, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(210, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(211, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(212, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(213, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(214, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(215, (SELECT t.id FROM tag t WHERE t.name = "Artificial Intelligence" )), +(216, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(217, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(218, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(219, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(220, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(221, (SELECT t.id FROM tag t WHERE t.name = "Innovative Technology" )), +(222, (SELECT t.id FROM tag t WHERE t.name = "Artificial Intelligence" )), +(223, (SELECT t.id FROM tag t WHERE t.name = "Internet of things" )), +(224, (SELECT t.id FROM tag t WHERE t.name = "Internet of things" )); + + +--comment: user_group +INSERT INTO user_group (userId, groupId ) VALUES +(1, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(2, (SELECT g.id FROM `group` g WHERE g.name = "System Administrator") ), +(3, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(4, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(5, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(6, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(7, (SELECT g.id FROM `group` g WHERE g.name = "System Administrator") ), +(8, (SELECT g.id FROM `group` g WHERE g.name = "System Administrator") ), +(9, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(10, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(11, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(12, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(13, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(14, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(15, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(16, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(17, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(18, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(19, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(20, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(21, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(22, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(23, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(24, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(25, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(26, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(27, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(28, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(29, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(30, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(31, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(32, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(33, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(34, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(35, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(36, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(37, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(38, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(39, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(40, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(41, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(42, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(43, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(44, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(45, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(46, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(47, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(48, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(49, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(50, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(51, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(52, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(53, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(54, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(55, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(56, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(57, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(58, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(59, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(60, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(61, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(62, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(63, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(64, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(65, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(66, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(67, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(68, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(69, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(70, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(71, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(72, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(73, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(74, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(75, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(76, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(77, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(78, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(79, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(80, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(81, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(82, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(83, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(84, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(85, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(86, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(87, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(88, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(89, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(90, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(91, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(92, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(93, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(94, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(95, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(96, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(97, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(98, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(99, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(100, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(101, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(102, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(103, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(104, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(105, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(106, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(107, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(108, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(109, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(110, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(111, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(112, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(113, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(114, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(115, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(116, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(117, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(118, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(119, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(120, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(121, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(122, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(123, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(124, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(125, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(126, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(127, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(128, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(129, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(130, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(131, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(132, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(133, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(134, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(135, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(136, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(137, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(138, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(139, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(140, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(141, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(142, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(143, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(144, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(145, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(146, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(147, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(148, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(149, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(150, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(151, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(152, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(153, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(154, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(155, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(156, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(157, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(158, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(159, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(160, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(161, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(162, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(163, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(164, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(165, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(166, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(167, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(168, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(169, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(170, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(171, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(172, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(173, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(174, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(175, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(176, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(177, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(178, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(179, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(180, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(181, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(182, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(183, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(184, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(185, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(186, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(187, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(188, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(189, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(190, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(191, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(192, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(193, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(194, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(195, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(196, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(197, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(198, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(199, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(200, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(201, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(202, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(203, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(204, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(205, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(206, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(207, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(208, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(209, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(210, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(211, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(212, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(213, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(214, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(215, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(216, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(217, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(218, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(219, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(220, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(221, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(222, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(223, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(224, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(225, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(226, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(227, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(228, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(229, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(230, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(231, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(232, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(233, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(234, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(235, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(236, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(237, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(238, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(239, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(240, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(241, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(242, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(243, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(244, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(245, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(246, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(247, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(248, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(249, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(250, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(251, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(252, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(253, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(254, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(255, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(256, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(257, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(258, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(259, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(260, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(261, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(262, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(263, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(264, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(265, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(266, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(267, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(268, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(269, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(270, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(271, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(272, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(273, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(274, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(275, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(276, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(277, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(278, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(279, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(280, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(281, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(282, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(283, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(284, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(285, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(286, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(287, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(288, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(289, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(290, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(291, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(292, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(293, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(294, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(295, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(296, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(297, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(298, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(299, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(300, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(301, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(302, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(303, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(304, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(305, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(306, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(307, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(308, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(309, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(310, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(311, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(312, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(313, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(314, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(315, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(316, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(317, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(318, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(319, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(320, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(321, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(322, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(323, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(324, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(325, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(326, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(327, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(328, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(329, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(330, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(331, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(332, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(333, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(334, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(335, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(336, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(337, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(338, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(339, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(340, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(341, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(342, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(343, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(344, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(345, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(346, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(347, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(348, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(349, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(350, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(351, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(352, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(353, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(354, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(355, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(356, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(357, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(358, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(359, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(360, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(361, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(362, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(363, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(364, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(365, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(366, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(367, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(368, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(369, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(370, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(371, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(372, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(373, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(374, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(375, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(376, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(377, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(378, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(379, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(380, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(381, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(382, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(383, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(384, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(385, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(386, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(387, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(388, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(389, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(390, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(391, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(392, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(393, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(394, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(395, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(396, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(397, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(398, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(399, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(400, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(401, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(402, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(403, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(404, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(405, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(406, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(407, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(408, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(409, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(410, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(411, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(412, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(413, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(414, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(415, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(416, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(417, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(418, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(419, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(420, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(421, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(422, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(423, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(424, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(425, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(426, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(427, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(428, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(429, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(430, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(431, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(432, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(433, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(434, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(435, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(436, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(437, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(438, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(439, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(440, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(441, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(442, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(443, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(444, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(445, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(446, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(447, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(448, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(449, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(450, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(451, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(452, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(453, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(454, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(455, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(456, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(457, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(458, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(459, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(460, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(461, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(462, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(463, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(464, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(465, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(466, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(467, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(468, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(469, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(470, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(471, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ), +(472, (SELECT g.id FROM `group` g WHERE g.name = "Editor 2") ); + + +--comment: user +INSERT INTO user (id, username, name, subDivisionId, fullname, post, email, phone1, reminderFlag, lotusNotesUser) VALUES +(1, "ECC1", "ECC1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Mr LAM Kin Cheung", "E/CC/1", "lamkincheung@emsd.gov.hk", null, false, true ), +(2, "ECC2", "ECC2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Ms CHENG Man Yee", "E/CC/2", "mycheng@emsd.gov.hk", null, false, true ), +(3, "SMSOCC", "SMSOCC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Ms MAN Kit Fong, Mandy", "SMSO/CC", "kfman@emsd.gov.hk", null, true, true ), +(4, "MSOCC", "MSOCC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Ms KO So Yee, Suki", "MSO/CC", "suki@emsd.gov.hk", null, true, true ), +(5, "TPBOCC2", "TPBOCC2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Ms YIP Ping Ki, Ice", "TPBOCC2", "pkyip@emsd.gov.hk", null, true, true ), +(6, "TPBOCC5", "TPBOCC5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Mr CHAN Ka Yun", "TPBOCC5", "chankayun@emsd.gov.hk", null, true, true ), +(7, "TCSACC2", "TCSACC2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Ms NG Mei Po, Mabel", "T/CSA/CC2", "mabelng@emsd.gov.hk", null, true, true ), +(8, "ITMCC1", "ITMCC1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Mr LEUNG Yat Sing", "ITM/CC/1", "leungys@emsd.gov.hk", null, true, true ), +(9, "CESV", "CESV", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr CHU Wan Fung", "CE/SV", "ryanchu@emsd.gov.hk", null, false, true ), +(10, "EGL71", "EGL71", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Professional Support Sub-division"), "Mr LEE Siu Man", "E/GL7/1", "ringolee@emsd.gov.hk", null, false, true ), +(11, "EGL72", "EGL72", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Professional Support Sub-division"), "Mr LAU King Fu", "E/GL7/2", "laukf@emsd.gov.hk", null, false, true ), +(12, "SERPSR1", "SERPSR1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Professional Support Sub-division"), "Mr MOK Chun Wing", "SE/R/PSR1", "frankmok@emsd.gov.hk", null, false, true ), +(13, "ESCS1", "ESCS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Central Services Sub-division"), "Ms KOT Yee Mei", "E/S/CS1", "ymkot@emsd.gov.hk", null, false, true ), +(14, "ESCS3", "ESCS3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Central Services Sub-division"), "Mr LAM Chi Hin", "E/S/CS3", "lamch@emsd.gov.hk", null, false, true ), +(15, "SESD", "SESD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Mr NG Kam Hon", "SE/S/D", "joekhng@emsd.gov.hk", null, false, true ), +(16, "ESD1", "ESD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Mr LIANG Ki Kin", "E/S/D1", "kkliang@emsd.gov.hk", null, false, true ), +(17, "ESD2", "ESD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Mr LAM Kwan Yin", "E/S/D2", "lamkwanyin@emsd.gov.hk", null, false, true ), +(18, "ESD3", "ESD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Mr POK Yuk Fu", "E/S/D3", "yfpok@emsd.gov.hk", null, false, true ), +(19, "ESD5", "ESD5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Mr LOO Sheung Fai", "E/S/D5", "rixloo@emsd.gov.hk", null, false, true ), +(20, "PESD1", "PESD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Mr CHAU Chuk Keung", "ConPE(En)/S/D1", "chauchukkeung@emsd.gov.hk", null, false, true ), +(21, "ITMSD1", "ITMSD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Drainage Services Sub-division"), "Ms KWAN Wing Yan", "ITM/S/D1", "wykwan@emsd.gov.hk", null, false, true ), +(22, "ESENP1", "ESENP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Electronic Project Sub-division"), "Ms KWOK Sin Ming", "E/S/ENP1", "kwoksm@emsd.gov.hk", null, false, true ), +(23, "ESENP2", "ESENP2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Electronic Project Sub-division"), "Miss LEUNG Yan Chi", "E/S/ENP2 (N)", "ycleung@emsd.gov.hk", null, false, true ), +(24, "ESENP3", "ESENP3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Electronic Project Sub-division"), "Miss HUI Wing Yin", "E/S/ENP3", "wyhui@emsd.gov.hk", null, false, true ), +(25, "ESENP4", "ESENP4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Electronic Project Sub-division"), "Mr NG Wai Nap", "E/S/ENP4", "wnng@emsd.gov.hk", null, false, true ), +(26, "ESENP6", "ESENP6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Electronic Project Sub-division"), "Miss LEUNG Yan Chi", "E/S/ENP6", "ycleung@emsd.gov.hk", null, false, true ), +(27, "SESHK", "SESHK", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Hong Kong Sub-division"), "Mr SHEK Lap Chi", "SE/S/HK", "lcshek@emsd.gov.hk", null, false, true ), +(28, "ESHK3", "ESHK3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Hong Kong Sub-division"), "Mr YICK Yuk Choi", "E/S/HK3", "ycyick@emsd.gov.hk", null, false, true ), +(29, "ESHK5", "ESHK5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Hong Kong Sub-division"), "Mr SZE Wai Ching", "E/S/HK5", "wcsze@emsd.gov.hk", null, false, true ), +(30, "ESK1", "ESK1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Kowloon Sub-division"), "Mr LAU Ching Sing, Jason", "E/S/K1", "jasonlau@emsd.gov.hk", null, false, true ), +(31, "ESK2", "ESK2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Kowloon Sub-division"), "Mr KONG Wing Shing", "E/S/K2", "wskong@emsd.gov.hk", null, false, true ), +(32, "EESK", "EESK", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Kowloon Sub-division"), "Mr TONG Kim Ming", "EE/S/K", "kmtong@emsd.gov.hk", null, false, true ), +(33, "SESK", "SESK", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Kowloon Sub-division"), "Mr HO Siu Ming", "SE/S/K", "jasonho@emsd.gov.hk", null, false, true ), +(34, "ESNT1", "ESNT1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/New Territories Sub-division"), "Mr OUYANG Yu", "E/S/NT1", "youyang@emsd.gov.hk", null, false, true ), +(35, "ESNT2", "ESNT2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/New Territories Sub-division"), "Miss PANG Ka Man", "E/S/NT2", "pangkm@emsd.gov.hk", null, false, true ), +(36, "ESNT3", "ESNT3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/New Territories Sub-division"), "Mr LAU Wang Yip", "E/S/NT3", "lauwangyip@emsd.gov.hk", null, false, true ), +(37, "ESNT4", "ESNT4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/New Territories Sub-division"), "Mr CHIO Yung An", "E/S/NT4", "yachio@emsd.gov.hk", null, false, true ), +(38, "ESNT6", "ESNT6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/New Territories Sub-division"), "Mr KWAN Ronvin", "E/S/NT6 (N)", "ronvinkwan@emsd.gov.hk", null, false, true ), +(39, "SESP", "SESP", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Project Sub-division"), "Mr LAW Chi Keung", "SE/S/P", "lawck@emsd.gov.hk", null, false, true ), +(40, "ESP1", "ESP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Project Sub-division"), "Miss CHAN Cheuk Shan", "E/S/P1", "cschan@emsd.gov.hk", null, false, true ), +(41, "ESP2", "ESP2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Project Sub-division"), "Ms CHAN Man Ting Ida", "E/S/P2", "chanmanting@emsd.gov.hk", null, false, true ), +(42, "ESP3", "ESP3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Project Sub-division"), "Mr KUNG Shu Lam", "E/S/P3", "slkung@emsd.gov.hk", null, false, true ), +(43, "ESP5", "ESP5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Project Sub-division"), "Mr CHENG King Ho", "E/S/P5 (N)", "khcheng@emsd.gov.hk", null, false, true ), +(44, "ESP6", "ESP6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Security/Project Sub-division"), "Mr HUNG Kwok Wai", "E/S/P6 (N)", "kwhung@emsd.gov.hk", null, false, true ), +(45, "SEV", "SEV", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle) Sub-division"), "Mr CHAN Cheuk Ming", "SE/V", "mingchan@emsd.gov.hk", null, false, true ), +(46, "EMEV1", "EMEV1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle) Sub-division"), "Mr PANG Hong Lun", "EME/V1", "panghl@emsd.gov.hk", null, false, true ), +(47, "EMEV2", "EMEV2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle) Sub-division"), "Mr CHEUNG Ka Yu, Gary", "EME/V2", "garycheung@emsd.gov.hk", null, false, true ), +(48, "EMEV3", "EMEV3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle) Sub-division"), "Mr HUI Ka Yiu", "EME/V3", "kyhui@emsd.gov.hk", null, false, true ), +(49, "SEVTS", "SEVTS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr LAU Wing Hang", "SE/VTS", "whlau@emsd.gov.hk", null, false, true ), +(50, "EMEVTS1", "EMEVTS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr LEE Yiu Fai", "EME/VTS1", "yflee@emsd.gov.hk", null, false, true ), +(51, "EMEVTS2", "EMEVTS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr NG Cheuk Yin", "EME/VTS2", "ngcheukyin@emsd.gov.hk", null, false, true ), +(52, "EMEVTS3", "EMEVTS3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr CHEUNG Wing Cheong", "EME/VTS3", "wccheung@emsd.gov.hk", null, false, true ), +(53, "EMEVTS4", "EMEVTS4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr CHAN Ting Long, Marco", "EME/VTS/4", "marcochan@emsd.gov.hk", null, false, true ), +(54, "EMEVTS5", "EMEVTS5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr WONG Yu Hong", "EME/VTS/5", "wongyh@emsd.gov.hk", null, false, true ), +(55, "EMEVTS6", "EMEVTS6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Vehicle Engineering S/D (Vehicle Technical Services) Sub-division"), "Mr LEUNG Kim Keung", "EME/VTS6", "dennisleung@emsd.gov.hk", null, false, true ), +(56, "AD2", "AD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr YEUNG Sau Kuen, Sammy", "AD/2", "skyeung@emsd.gov.hk", null, false, true ), +(57, "CEGES", "CEGES", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr LAM Sam Ching", "CE/GES", "sclam@emsd.gov.hk", null, false, true ), +(58, "SBSEADG", "SBSEADG", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Accommodation Design Group"), "Mr LEE Ka Chun", "SBSE/ADG", "chrislee@emsd.gov.hk", null, false, true ), +(59, "BSEADG1", "BSEADG1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Accommodation Design Group"), "Mr CHEUNG Chun Chung", "BSE/ADG/1", "cheungcc@emsd.gov.hk", null, false, true ), +(60, "BSEADG2", "BSEADG2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Accommodation Design Group"), "Mr CHAN Hon Hung", "BSE/ADG/2", "hhchan@emsd.gov.hk", null, false, true ), +(61, "SEGESHK1", "SEGESHK1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 1"), "Ms TING Pong Yau, Fanny", "SE/GES/HK1", "fannyting@emsd.gov.hk", null, false, true ), +(62, "EGESHK11", "EGESHK11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 1"), "Mr HO Chung Yeung, Jeremy", "E/GES/HK1/1", "jeremyho@emsd.gov.hk", null, false, true ), +(63, "EGESHK13", "EGESHK13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 1"), "Mr LEE Siu Cheong", "E/GES/HK1/3", "sclee@emsd.gov.hk", null, false, true ), +(64, "SEGESHK2", "SEGESHK2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr CHAN Kin Hong", "SE/GES/HK2", "chankh@emsd.gov.hk", null, false, true ), +(65, "EGESHK21", "EGESHK21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr LAI Hon Fung", "E/GES/HK2/1", "hflai@emsd.gov.hk", null, false, true ), +(66, "EGESHK22", "EGESHK22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr YIU Chu Hong", "E/GES/HK2/2", "chyiu@emsd.gov.hk", null, false, true ), +(67, "EGESHK23", "EGESHK23", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr YEUNG Chi Shing", "E/GES/HK2/3", "yeungcs@emsd.gov.hk", null, false, true ), +(68, "EGESHK24", "EGESHK24", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr AU Yiu Man", "E/GES/HK2/4", "ymau@emsd.gov.hk", null, false, true ), +(69, "PEGESHK27BS", "PEGESHK27BS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr LEUNG Ka Fu", "PE/GES/HK2/7 (BS)", "leungkafu@emsd.gov.hk", null, false, true ), +(70, "ITMGESHK21", "ITMGESHK21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr CHAN Enoch", "ITM/GES/HK2/1", "echan@emsd.gov.hk", null, false, true ), +(71, "ITMGESHK22", "ITMGESHK22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (HK) Maintenance Sub-division 2"), "Mr NG Kwok Man Gordon", "ITM/GES/HK2/2", "ngkwokman@emsd.gov.hk", null, false, true ), +(72, "SEGESK", "SEGESK", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (K) Maintenance Sub-division"), "Ms LAU Pui Ling, Iris", "SE/GES/K", "irislau@emsd.gov.hk", null, false, true ), +(73, "EGESK1", "EGESK1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (K) Maintenance Sub-division"), "Mr SIN Chun Ming", "E/GES/K1", "cmsin@emsd.gov.hk", null, false, true ), +(74, "EGESK3", "EGESK3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (K) Maintenance Sub-division"), "Mr LEE Yuk Leung", "E/GES/K3", "yllee@emsd.gov.hk", null, false, true ), +(75, "EGESK4", "EGESK4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (K) Maintenance Sub-division"), "Mr CHOW Tsz Him", "E/GES/K4", "thchow@emsd.gov.hk", null, false, true ), +(76, "EGESK5", "EGESK5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (K) Maintenance Sub-division"), "Ms YEUNG Wing Lam", "E/GES/K5", "wlyeung@emsd.gov.hk", null, false, true ), +(77, "EGESNT1", "EGESNT1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (NT) Maintenance Sub-division"), "Mr LAI Chun Yuen", "E/GES/NT1", "cylai@emsd.gov.hk", null, false, true ), +(78, "EGESNT2", "EGESNT2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (NT) Maintenance Sub-division"), "Mr KO Tsun Ho", "E/GES/NT2", "thko@emsd.gov.hk", null, false, true ), +(79, "EGESNT3", "EGESNT3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (NT) Maintenance Sub-division"), "Mr LO Tsz Lung", "E/GES/NT3", "tllo@emsd.gov.hk", null, false, true ), +(80, "EGESNT4", "EGESNT4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "GES - (NT) Maintenance Sub-division"), "Mr CHU Ho Yin", "E/GES/NT4", "hychu@emsd.gov.hk", null, false, true ), +(81, "BSEGESA8", "BSEGESA8", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division A"), "Mr CHEN Jia Xi, Kelvin", "BSE/GES/A8", "kelvinchen@emsd.gov.hk", null, false, true ), +(82, "BSEGESA4", "BSEGESA4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division A"), "Mr YIP Tsz Chiu", "BSE/GES/A4", "tcyip@emsd.gov.hk", null, false, true ), +(83, "BSEGESA7", "BSEGESA7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division A"), "Mr WO Kiu Kin", "BSE/GES/A7", "kkwo@emsd.gov.hk", null, false, true ), +(84, "BSEGESA3", "BSEGESA3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division A"), "Mr LAM Kwok Fai, Gary", "BSE/GES/A3", "kfglam@emsd.gov.hk", null, false, true ), +(85, "SBSEGESB", "SBSEGESB", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division B"), "Mr LEUNG Ka Fung", "SBSE/GES/B", "kfleung@emsd.gov.hk", null, false, true ), +(86, "BSEGESB4", "BSEGESB4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division B"), "Mr CHEUNG Kam Hang", "BSE/GES/B4", "cheungkamhang@emsd.gov.hk", null, false, true ), +(87, "BSEGESB7", "BSEGESB7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division B"), "Mr WONG Hon Wing", "BSE/GES/B7", "wonghonwing@emsd.gov.hk", null, false, true ), +(88, "BSEGESB2", "BSEGESB2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division B"), "Mr CHEUNG Man Kwong", "BSE/GES/B2", "cheungmankwong@emsd.gov.hk", null, false, true ), +(89, "BSEGESB3", "BSEGESB3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division B"), "Mr SZE-TO Ka Wa", "BSE/GES/B3", "kwszeto@emsd.gov.hk", null, false, true ), +(90, "SBSEGESC", "SBSEGESC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division C"), "Ms LO Kit Ying, Denise", "SBSE/GES/C", "deniselo@emsd.gov.hk", null, false, true ), +(91, "BSEGESC2", "BSEGESC2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division C"), "Miss CHI Ka Man", "BSE/GES/C2", "kmchi@emsd.gov.hk", null, false, true ), +(92, "BSEGESC3", "BSEGESC3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division C"), "Miss LI Shuk Man", "BSE/GES/C3", "jojoli@emsd.gov.hk", null, false, true ), +(93, "SBSEGESD", "SBSEGESD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division D"), "Ms CHEN Hau Nam", "SBSE/GES/D", "chenhn@emsd.gov.hk", null, false, true ), +(94, "BSEGESCS", "BSEGESCS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division D"), "Ms MO Yuen Yee", "BSE/GES/CS", "yymo@emsd.gov.hk", null, false, true ), +(95, "BSEGESC9", "BSEGESC9", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division D"), "Mr CHOW Ka Ming", "BSE/GES/IT", "kmchow@emsd.gov.hk", null, false, true ), +(96, "SBSEGESE", "SBSEGESE", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division E"), "Mr TSE Hung Yan", "SBSE/GES/E", "tsehy@emsd.gov.hk", null, false, true ), +(97, "BSEGESE3", "BSEGESE3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division E"), "Mr NG Ka Wing", "BSE/GES/E3", "kw_ng_1@emsd.gov.hk", null, false, true ), +(98, "BSEGESE4", "BSEGESE4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division E"), "Mr KWOK Siu Hei", "BSE/GES/E4", "siuheikwok@emsd.gov.hk", null, false, true ), +(99, "BSEGESE5", "BSEGESE5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division E"), "Mr LAU Sai Kit", "BSE/GES/E5", "lausk@emsd.gov.hk", null, false, true ), +(100, "BSEGESE2", "BSEGESE2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division E"), "Ms WONG Mei Sze", "BSE/GES/E2", "mswong@emsd.gov.hk", null, false, true ), +(101, "SBSEGESF", "SBSEGESF", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division F"), "Mr CHAN Kwok Keung, Jimmy", "SBSE/GES/F", "jimmychan@emsd.gov.hk", null, false, true ), +(102, "BSEGESF1", "BSEGESF1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division F"), "Miss WONG Tsz Kwan", "BSE/GES/F1", "wongtszkwan@emsd.gov.hk", null, false, true ), +(103, "BSEGESF4", "BSEGESF4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division F"), "Mr LO Ying Chi", "BSE/GES/F4", "loyc@emsd.gov.hk", null, false, true ), +(104, "BSEGESF2", "BSEGESF2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division F"), "Mr SIN Wai Cheung", "BSE/GES/F2", "wcsin@emsd.gov.hk", null, false, true ), +(105, "BSEGESF3", "BSEGESF3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Building Services Sub-division F"), "Mr AU Jason Junsing", "BSE/GES/F3", "jasonau@emsd.gov.hk", null, false, true ), +(106, "SEGESP1", "SEGESP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr WONG Chun Kit", "SE/GES/P1", "ckwong@emsd.gov.hk", null, false, true ), +(107, "EGESP11", "EGESP11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr HEUNG Siu Kei", "E/GES/P1/1", "tonyheung@emsd.gov.hk", null, false, true ), +(108, "ITMGESP11", "ITMGESP11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr KOO Ho Fai", "ITM/GES/P1/1", "hfkoo@emsd.gov.hk", null, false, true ), +(109, "ITMGESP12", "ITMGESP12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr CHENG Sze Man", "ITM/GES/P1/2", "smcheng@emsd.gov.hk", null, false, true ), +(110, "SEGESP2", "SEGESP2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr MAK Kim Kong", "SE/GES/P2", "kkmak@emsd.gov.hk", null, false, true ), +(111, "SBSEGESP", "SBSEGESP", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr MA Chun Yue", "SBSE/GES/P", "cyma@emsd.gov.hk", null, false, true ), +(112, "BSEGESP1", "BSEGESP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Miss LI Hoi Ching", "BSE/GES/P/1", "hcli@emsd.gov.hk", null, false, true ), +(113, "BSEGESP3", "BSEGESP3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Miss YU Ka Man", "BSE/GES/P3", "kmyu@emsd.gov.hk", null, false, true ), +(114, "SPEGES1", "SPEGES1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Project Sub-division"), "Mr CHUI Wai Sing", "SPE/GES/1", "wschui@emsd.gov.hk", null, false, true ), +(115, "SEGESSD", "SEGESSD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Special Duty Sub-division"), "Mr SO Pok Man", "SE/GES/SD", "pmso@emsd.gov.hk", null, false, true ), +(116, "EGESSD1", "EGESSD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Special Duty Sub-division"), "Mr YIP Ka Chuen", "E/GES/SD1", "yipkc@emsd.gov.hk", null, false, true ), +(117, "EGESSD3", "EGESSD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Special Duty Sub-division"), "Mr LEUNG Kin Fung", "E/GES/SD3", "leungkf@emsd.gov.hk", null, false, true ), +(118, "EGESSD4", "EGESSD4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "General Engineering Services Special Duty Sub-division"), "Mr LAU Ka Tai", "E/GES/SD4", "ktlau@emsd.gov.hk", null, false, true ), +(119, "CEHS1", "CEHS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr TSE Lok Him", "CE/HS1", "tselh@emsd.gov.hk", null, false, true ), +(120, "SEHSHKE", "SEHSHKE", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong East) Sub-division"), "Mr CHAN Lee Shing, Danny", "SE/HS/HKE", "dannychan@emsd.gov.hk", null, false, true ), +(121, "EHSHKE1", "EHSHKE1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong East) Sub-division"), "Mr POON Shing", "E/HS/HKE/1", "spoon@emsd.gov.hk", null, false, true ), +(122, "EHSHKE2", "EHSHKE2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong East) Sub-division"), "Mr AU Sigh Dong, Samitone", "E/HS/HKE/2", "samitoneau@emsd.gov.hk", null, false, true ), +(123, "EHSHKE4", "EHSHKE4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong East) Sub-division"), "Mr TANG Lok Yiu", "E/HS/HKE/4", "tangly@emsd.gov.hk", null, false, true ), +(124, "EHSHKE6", "EHSHKE6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong East) Sub-division"), "Mr CHAN Tsz Hong", "E/HS/HKE/6", "chantszhong@emsd.gov.hk", null, false, true ), +(125, "SEHSHKW", "SEHSHKW", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr YEUNG Chu Man", "SE/HS/HKW", "cmyeung@emsd.gov.hk", null, false, true ), +(126, "EHSHKW1", "EHSHKW1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr CHAN Po Nam", "E/HS/HKW/1", "pnchan@emsd.gov.hk", null, false, true ), +(127, "EHSHKW2", "EHSHKW2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr LAU Li Hin", "E/HS/HKW/2", "lhlau@emsd.gov.hk", null, false, true ), +(128, "EHSHKW3", "EHSHKW3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr SHUM Ka Yu", "E/HS/HKW/3", "kyshum@emsd.gov.hk", null, false, true ), +(129, "EHSHKW4", "EHSHKW4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr CHAN Cheuk Yiu", "E/HS/HKW/4", "chancheukyiu@emsd.gov.hk", null, false, true ), +(130, "EHSHKW5", "EHSHKW5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr PANG Cheuk Him", "E/HS/HKW/5", "pangch@emsd.gov.hk", null, false, true ), +(131, "EHSHKW6", "EHSHKW6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Hong Kong West) Sub-division"), "Mr TAM Chi Ho", "E/HS/HKW/6", "tamchiho@emsd.gov.hk", null, false, true ), +(132, "EHSKC1", "EHSKC1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Mr LEE Ka Yin", "E/HS/KC/1", "kylee@emsd.gov.hk", null, false, true ), +(133, "EHSKC2", "EHSKC2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Mr CHAN Wai Hung", "E/HS/KC/2", "whchan@emsd.gov.hk", null, false, true ), +(134, "EHSKC3", "EHSKC3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Mr LI Kwong Ming", "E/HS/KC/3", "kmli@emsd.gov.hk", null, false, true ), +(135, "EHSKC4", "EHSKC4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Miss SIN Ka Man", "E/HS/KC/4", "kmsin@emsd.gov.hk", null, false, true ), +(136, "EHSKC5", "EHSKC5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Miss POON Hiu Ching", "E/HS/KC/5", "hcpoon@emsd.gov.hk", null, false, true ), +(137, "EHSKC6", "EHSKC6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Miss YEUNG Hiu Fan", "E/HS/KC/6", "yeunghf@emsd.gov.hk", null, false, true ), +(138, "EHSKC8", "EHSKC8", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Miss LEUNG Sze Kun", "E/HS/KC/8", "leungsk@emsd.gov.hk", null, false, true ), +(139, "EHSKC7", "EHSKC7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon Central) Sub-division"), "Mr HUI Wing Fung", "E/HS/KC/7", "wfhui@emsd.gov.hk", null, false, true ), +(140, "SEHSKE", "SEHSKE", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon East) Sub-division"), "Mr CHIU Wai Leuk", "SE/HS/KE", "wlchiu@emsd.gov.hk", null, false, true ), +(141, "EHSKE1", "EHSKE1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon East) Sub-division"), "Miss LO Cho Hang", "E/HS/KE/1", "lochohang@emsd.gov.hk", null, false, true ), +(142, "EHSKE2", "EHSKE2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon East) Sub-division"), "Miss CHENG Yan Yi", "E/HS/KE/2", "yycheng@emsd.gov.hk", null, false, true ), +(143, "EHSKE3", "EHSKE3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon East) Sub-division"), "Mr YUEN Piu Hung", "E/HS/KE/3", "phyuen@emsd.gov.hk", null, false, true ), +(144, "EHSKE4", "EHSKE4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon East) Sub-division"), "Mr FUNG Chun Kwok", "E/HS/KE/4", "fungchunkwok@emsd.gov.hk", null, false, true ), +(145, "SEHSKW", "SEHSKW", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon West) Sub-division"), "Mr YU Wai Lee", "SE/HS/KW", "wlyu@emsd.gov.hk", null, false, true ), +(146, "EHSKW1", "EHSKW1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon West) Sub-division"), "Miss CHOI Azura Yuk Chin", "E/HS/KW/1", "azurachoi@emsd.gov.hk", null, false, true ), +(147, "EHSKW2", "EHSKW2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon West) Sub-division"), "Mr LO Wing Cheung", "E/HS/KW/2", "wclo@emsd.gov.hk", null, false, true ), +(148, "EHSKW3", "EHSKW3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon West) Sub-division"), "Mr HUNG Tsz On", "E/HS/KW/3", "tohung@emsd.gov.hk", null, false, true ), +(149, "EHSKW4", "EHSKW4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon West) Sub-division"), "Mr TANG Shun Wo", "E/HS/KW/4", "tangsw@emsd.gov.hk", null, false, true ), +(150, "EHSKW5", "EHSKW5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (Kowloon West) Sub-division"), "Miss LIU Pik Ki", "E/HS/KW/5", "pkliu@emsd.gov.hk", null, false, true ), +(151, "SEHSNTE", "SEHSNTE", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories East) Sub-division"), "Mr YIP Wai Tong", "SE/HS/NTE", "wtyip@emsd.gov.hk", null, false, true ), +(152, "EHSNTE1", "EHSNTE1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories East) Sub-division"), "Mr YUEN Wai Man", "E/HS/NTE/1", "wmyuen@emsd.gov.hk", null, false, true ), +(153, "EHSNTE2", "EHSNTE2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories East) Sub-division"), "Mr KWOK Mo Kan", "E/HS/NTE/2", "kwokmk@emsd.gov.hk", null, false, true ), +(154, "EHSNTE3", "EHSNTE3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories East) Sub-division"), "Mr LEE Ka Nin", "E/HS/NTE/3", "leekn@emsd.gov.hk", null, false, true ), +(155, "EHSNTE6", "EHSNTE6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories East) Sub-division"), "Miss POON Yun Man", "E/HS/NTE/6", "ympoon@emsd.gov.hk", null, false, true ), +(156, "EHSNTE7", "EHSNTE7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories East) Sub-division"), "Miss NG Wing Yan", "E/HS/NTE/7", "ngwingyan@emsd.gov.hk", null, false, true ), +(157, "SEHSNTW", "SEHSNTW", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr LO Fu Yin", "SE/HS/NTW", "fylo@emsd.gov.hk", null, false, true ), +(158, "EHSNTW1", "EHSNTW1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr YAU Kai Pui", "E/HS/NTW/1", "kpyau@emsd.gov.hk", null, false, true ), +(159, "EHSNTW2", "EHSNTW2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr NG Chin Hung", "E/HS/NTW/2", "ngchinhung@emsd.gov.hk", null, false, true ), +(160, "EHSNTW3", "EHSNTW3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr CHOW Pak Hong", "E/HS/NTW/3", "phchow@emsd.gov.hk", null, false, true ), +(161, "EHSNTW4", "EHSNTW4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Ms SUN Ka Yan", "E/HS/NTW/4", "kysun@emsd.gov.hk", null, false, true ), +(162, "EHSNTW5", "EHSNTW5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr AU Yiu Kwan", "E/HS/NTW/5", "ykau@emsd.gov.hk", null, false, true ), +(163, "EHSNTW7", "EHSNTW7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr TONG Hau Yin", "E/HS/NTW/7", "hytong@emsd.gov.hk", null, false, true ), +(164, "EHSNTW8", "EHSNTW8", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr LAW Yun Tak", "E/HS/NTW/8", "ytlaw@emsd.gov.hk", null, false, true ), +(165, "EHSNTW9", "EHSNTW9", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services (New Territories West) Sub-division"), "Mr MAK Chi Teng", "E/HS/NTW/9", "ctmak@emsd.gov.hk", null, false, true ), +(166, "CEHS2", "CEHS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr LAI Kam Fai", "CE/HS2", "kflai@emsd.gov.hk", null, false, true ), +(167, "SEHSP1", "SEHSP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 1"), "Mr LAM Wing Hung", "SE/HS/P1", "lamwh@emsd.gov.hk", null, false, true ), +(168, "EHSP11", "EHSP11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 1"), "Mr TIU Yat Ho", "E/HS/P1/1", "yhtiu@emsd.gov.hk", null, false, true ), +(169, "EHSP12", "EHSP12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 1"), "Miss IP Tsz Yan", "E/HS/P1/2", "tyip@emsd.gov.hk", null, false, true ), +(170, "SEHSP2", "SEHSP2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 2"), "Mr YOW Kin Fai", "SE/HS/P2", "yow@emsd.gov.hk", null, false, true ), +(171, "EHSP21", "EHSP21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 2"), "Ms FUNG Po Yee", "E/HS/P2/1", "pyfung@emsd.gov.hk", null, false, true ), +(172, "EHSP22", "EHSP22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 2"), "Mr WAN Wing Kit", "E/HS/P2/2", "wkwan@emsd.gov.hk", null, false, true ), +(173, "SEHSP3", "SEHSP3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 3"), "Miss CHENG Shuk Fong, Justine", "SE/HS/P3", "sfcheng@emsd.gov.hk", null, false, true ), +(174, "EHSP31", "EHSP31", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 3"), "Mr MAK Long Ning", "E/HS/P3/1", "lnmak@emsd.gov.hk", null, false, true ), +(175, "EHSP32", "EHSP32", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 3"), "Mr LAI Pak Cheung", "E/HS/P3/2", "pclai@emsd.gov.hk", null, false, true ), +(176, "EHSP33", "EHSP33", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 3"), "Mr NG Hei Man Herman", "E/HS/P3/3", "hermanng@emsd.gov.hk", null, false, true ), +(177, "EHSP34", "EHSP34", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 3"), "Mr LEE Vincent", "E/HS/P3/4", "vincentlee@emsd.gov.hk", null, false, true ), +(178, "SEHSP4", "SEHSP4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 4"), "Mr SZE Pui Hong", "SE/HS/P4", "sphong@emsd.gov.hk", null, false, true ), +(179, "EHSP41", "EHSP41", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 4"), "Mr SHAM Cheuk Kiu", "E/HS/P4/1", "cksham@emsd.gov.hk", null, false, true ), +(180, "EHSP42", "EHSP42", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 4"), "Miss MAN Yuen Yee", "E/HS/P4/2", "yyman@emsd.gov.hk", null, false, true ), +(181, "EHSP43", "EHSP43", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 4"), "Mr CHAN Chung Ki", "E/HS/P4/3", "chanchungki@emsd.gov.hk", null, false, true ), +(182, "SEHSP5", "SEHSP5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 5"), "Mr YIU Chi Wai", "SE/HS/P5", "yiuchiwai@emsd.gov.hk", null, false, true ), +(183, "EHSP51", "EHSP51", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 5"), "Mr TSOI Chun Lung", "E/HS/P5/1", "cltsoi@emsd.gov.hk", null, false, true ), +(184, "EHSP52", "EHSP52", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Health Sector Services Project Sub-division 5"), "Mr LAW Cheuk Yin", "E/HS/P5/2", "cylaw@emsd.gov.hk", null, false, true ), +(185, "CEMUN", "CEMUN", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr POON Sing Yue", "CE/Mun", "sypoon@emsd.gov.hk", null, false, true ), +(186, "PSEMUN1", "PSEMUN1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr WONG Siu Wah", "PSE/Mun/1", "solomon@emsd.gov.hk", null, false, true ), +(187, "SPEMUN", "SPEMUN", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr WONG Wai Chung, Kenny", "SPE/Mun", "kennywong@emsd.gov.hk", null, false, true ), +(188, "SEMUNP", "SEMUNP", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Municipal Sector Project Sub-division"), "Mr CHAN Ho Leung, Eric", "SE/Mun/P", "ehlchan@emsd.gov.hk", null, false, true ), +(189, "EMUNP1", "EMUNP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Municipal Sector Project Sub-division"), "Mr LAM Fuk Wah, Lawrence", "E/Mun/P/1", "fwlam@emsd.gov.hk", null, false, true ), +(190, "EMunP5", "EMunP5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Municipal Sector Project Sub-division"), "Miss KAM Yuk Kwan", "E/Mun/P5", "ykkam@emsd.gov.hk", null, false, true ), +(191, "EMUNCS1", "EMUNCS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (CS) Sub-division"), "Mr CHEUNG Lap Hung", "E/Mun/CS/1", "lhcheung@emsd.gov.hk", null, false, true ), +(192, "EMUNCS4", "EMUNCS4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (CS) Sub-division"), "Miss CHUNG Shuk Yee", "E/Mun/CS/4", "sychung@emsd.gov.hk", null, false, true ), +(193, "EMUNCS5", "EMUNCS5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (CS) Sub-division"), "Miss WU Kathy", "E/Mun/CS/5", "kathywu@emsd.gov.hk", null, false, true ), +(194, "SEMUNH", "SEMUNH", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (HK) Sub-division"), "Mr LAU Kai Chung", "SE/Mun/H", "edmundlau@emsd.gov.hk", null, false, true ), +(195, "EMUNH1", "EMUNH1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (HK) Sub-division"), "Mr LAM Tik Hang", "E/Mun/H1", "thlam@emsd.gov.hk", null, false, true ), +(196, "EMUNH2", "EMUNH2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (HK) Sub-division"), "Mr WONG Eugene Benjamin", "E/Mun/H2", "eugenewong@emsd.gov.hk", null, false, true ), +(197, "EMUNH3", "EMUNH3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (HK) Sub-division"), "Miss LEE Hin Yee", "E/Mun/H3", "leehinyee@emsd.gov.hk", null, false, true ), +(198, "EMUNH4", "EMUNH4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (HK) Sub-division"), "Miss KWAN Ho Yu, Lillian", "E/Mun/H4", "lilliankwan@emsd.gov.hk", null, false, true ), +(199, "EMUNH5", "EMUNH5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (HK) Sub-division"), "Mr PO Chi On", "E/Mun/H5", "copo@emsd.gov.hk", null, false, true ), +(200, "EMUNK1", "EMUNK1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (K) Sub-division"), "Mr LAM Chun Kit", "E/Mun/K1", "jameslam@emsd.gov.hk", null, false, true ), +(201, "EMUNK3", "EMUNK3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (K) Sub-division"), "Mr LEUNG Kwan Kit, Anthony", "E/Mun/K3", "anthonyleung@emsd.gov.hk", null, false, true ), +(202, "EMUNK4", "EMUNK4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (K) Sub-division"), "Ms KWAN Cheuk Hei", "E/Mun/K4", "kwanch@emsd.gov.hk", null, false, true ), +(203, "EMUNK5", "EMUNK5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (K) Sub-division"), "Mr WAN Lap Hang", "E/Mun/K5", "wanlh@emsd.gov.hk", null, false, true ), +(204, "EMUNK8", "EMUNK8", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (K) Sub-division"), "Mr WONG Man Chi Man Joao", "E/Mun/K8", "joaowong@emsd.gov.hk", null, false, true ), +(205, "SEMUNNT1", "SEMUNNT1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT1) Sub-division"), "Mr CHAU Sheung Chi, Joseph", "SE/Mun/NT1", "josephchau@emsd.gov.hk", null, false, true ), +(206, "EMUNNT11", "EMUNNT11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT1) Sub-division"), "Ms KWAN Sin Ki", "E/Mun/NT1/1", "kwansk@emsd.gov.hk", null, false, true ), +(207, "EMUNNT12", "EMUNNT12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT1) Sub-division"), "Mr LEUNG Chi To", "E/Mun/NT1/2", "vincentleung@emsd.gov.hk", null, false, true ), +(208, "EMUNNT13", "EMUNNT13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT1) Sub-division"), "Mr WAN Cheung Chun", "E/Mun/NT1/3", "ccwan@emsd.gov.hk", null, false, true ), +(209, "EMUNNT14", "EMUNNT14", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT1) Sub-division"), "Mr KAN Chun", "E/Mun/NT1/4", "ylli@emsd.gov.hk", null, false, true ), +(210, "EMUNNT15", "EMUNNT15", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT1) Sub-division"), "Mr CHAN Ho Yi", "E/Mun/NT1/5", "chanhoyi@emsd.gov.hk", null, false, true ), +(211, "SEMUNNT2", "SEMUNNT2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT2) Sub-division"), "Miss YEUNG Yuet Wa", "SE/Mun/NT2", "ywyeung@emsd.gov.hk", null, false, true ), +(212, "EMUNNT21", "EMUNNT21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT2) Sub-division"), "Ms WONG Kwan Ting", "E/Mun/NT2/1", "karenwong@emsd.gov.hk", null, false, true ), +(213, "EMUNNT22", "EMUNNT22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT2) Sub-division"), "Mr LAM Hoi Man", "E/Mun/NT2/2", "hmlam@emsd.gov.hk", null, false, true ), +(214, "EMunNT25", "EMunNT25", (SELECT sd.id FROM sub_division sd WHERE sd.name = "MunS (NT2) Sub-division"), "Mr LAM Wai Ho", "E/Mun/NT2/5", "lamwaiho@emsd.gov.hk", null, false, true ), +(215, "AD3", "AD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr LEE Hok Yin", "AD/3", "hylee@emsd.gov.hk", null, false, true ), +(216, "CECS", "CECS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr CHEUNG Chin King", "CE/CS", "thomascheung@emsd.gov.hk", null, false, true ), +(217, "SEBP", "SEBP", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Business Planning Sub-division"), "Mr LAW Kim Ming", "SE/BP", "lawkm@emsd.gov.hk", null, false, true ), +(218, "EBP1", "EBP1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Business Planning Sub-division"), "Ms WONG Yuen Tung, Ingrid", "E/BP/1", "ingridwong@emsd.gov.hk", null, false, true ), +(219, "SECC", "SECC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Corporate Communications Sub-division"), "Ms FONG Nga Lai", "SE/CC", "terrifong@emsd.gov.hk", null, false, true ), +(220, "SEEEC8", "SEEEC8", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Energy & Efficiency C8 Sub-division"), "Mr NGAI Kwok Leung", "SE/EEC8", "klngai@emsd.gov.hk", null, false, true ), +(221, "SEEEC9", "SEEEC9", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Energy & Efficiency C9 Sub-division"), "Ms LAM Ka Man", "SE/EEC9", "kmlam@emsd.gov.hk", null, false, true ), +(222, "EEEC92", "EEEC92", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Energy & Efficiency C9 Sub-division"), "Mr CHAN Kin Yip", "E/EEC9/2", "chankinyip@emsd.gov.hk", null, false, true ), +(223, "SEHFS1", "SEHFS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 1 Sub-division"), "Mr NG Kar Wai", "SE/HFS1", "gordonng@emsd.gov.hk", null, false, true ), +(224, "EHFS11", "EHFS11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 1 Sub-division"), "Mr TSANG Hing Wai", "E/HFS1/1", "tsanghingwai@emsd.gov.hk", null, false, true ), +(225, "EHFS12", "EHFS12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 1 Sub-division"), "Mr WONG Chun Yin, Anson", "E/HFS1/2", "ansonwong@emsd.gov.hk", null, false, true ), +(226, "SEHFS2", "SEHFS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 2 Sub-division"), "Mr CHAN Chung Yan", "SE/HFS2", "cychan@emsd.gov.hk", null, false, true ), +(227, "EHFS21", "EHFS21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 2 Sub-division"), "Mr LAM Heung Chung", "E/HFS2/1", "lamheungchung@emsd.gov.hk", null, false, true ), +(228, "EHFS22", "EHFS22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 2 Sub-division"), "Mr CHEUNG Wai Tong", "E/HFS2/2", "wtcheung@emsd.gov.hk", null, false, true ), +(229, "SEQS", "SEQS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Quality and Safety Sub-division"), "Mr KWONG Chiu fan", "SE/QS", "cfkwong@emsd.gov.hk", null, false, true ), +(230, "EQS1", "EQS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Quality and Safety Sub-division"), "Mr WONG Wing Shing", "E/QS/1", "wongwingshing@emsd.gov.hk", null, false, true ), +(231, "EQS2", "EQS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Quality and Safety Sub-division"), "Mr LOK Chun Yin", "E/QS/2", "cylok@emsd.gov.hk", null, false, true ), +(232, "SET1", "SET1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Ms CHAN Ying Ying", "SE/T1", "dorothychan@emsd.gov.hk", null, false, true ), +(233, "ET11", "ET11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHAN Wing Shun", "E/T1/1", "chanwingshun@emsd.gov.hk", null, false, true ), +(234, "ET12", "ET12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Ms HO Sze Wing", "E/T1/2", "swho@emsd.gov.hk", null, false, true ), +(235, "BioEG2201", "BioEG2201", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss HO Wai Yin", "BioEG2201", "howaiyin@emsd.gov.hk", null, false, true ), +(236, "BioEG2202", "BioEG2202", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr MAN Cheuk Hin, Rico", "BioEG2202", "ricoman@emsd.gov.hk", null, false, true ), +(237, "BioEG2301", "BioEG2301", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss LEUNG Lai Sze", "BioEG2301", "lsleung@emsd.gov.hk", null, false, true ), +(238, "BioEG2302", "BioEG2302", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LI Yuk Yin", "BioEG2302", "yyli@emsd.gov.hk", null, false, true ), +(239, "BSEG2201", "BSEG2201", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHAN Tak Sum", "BSEG2201", "chants@emsd.gov.hk", null, false, true ), +(240, "BSEG2202", "BSEG2202", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHUI Chung Long", "BSEG2202", "clchui@emsd.gov.hk", null, false, true ), +(241, "BSEG2203", "BSEG2203", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr FUNG Jia Jun, Sunny", "BSEG2203", "sunnyfung@emsd.gov.hk", null, false, true ), +(242, "BSEG2204", "BSEG2204", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr WONG Chak Hin", "BSEG2204", "wongchakhin@emsd.gov.hk", null, false, true ), +(243, "BSEG2205", "BSEG2205", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr YU Cheuk Hang", "BSEG2205", "chyu@emsd.gov.hk", null, false, true ), +(244, "BSEG2301", "BSEG2301", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHEUNG Ho Chi", "BSEG2301", "hochicheung@emsd.gov.hk", null, false, true ), +(245, "BSEG2302", "BSEG2302", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr HO Cheuk Him", "BSEG2302", "hoch@emsd.gov.hk", null, false, true ), +(246, "BSEG2303", "BSEG2303", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss LAI Hau Hin", "BSEG2303", "hhlai@emsd.gov.hk", null, false, true ), +(247, "BSEG2304", "BSEG2304", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr TAM Yat Yung", "BSEG2304", "yytam@emsd.gov.hk", null, false, true ), +(248, "BSEG2305", "BSEG2305", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss YEO Lai Ki", "BSEG2305", "lkyeo@emsd.gov.hk", null, false, true ), +(249, "EEG2201", "EEG2201", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss CHOI Lok Ching", "EEG2201", "lcchoi@emsd.gov.hk", null, false, true ), +(250, "EEG2202", "EEG2202", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr HUNG Ka Chun", "EEG2202", "hungkc@emsd.gov.hk", null, false, true ), +(251, "EEG2203", "EEG2203", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr KEI Tung Hon", "EEG2203", "thkei@emsd.gov.hk", null, false, true ), +(252, "EEG2204", "EEG2204", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LAW Fong Sui", "EEG2204", "fslaw@emsd.gov.hk", null, false, true ), +(253, "EEG2205", "EEG2205", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr POON Tsun Hei", "EEG2205", "thpoon@emsd.gov.hk", null, false, true ), +(254, "EEG2206", "EEG2206", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr YAU Ho Tat", "EEG2206", "htyau@emsd.gov.hk", null, false, true ), +(255, "EEG2207", "EEG2207", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr YIP Man Hiu", "EEG2207", "yipmanhiu@emsd.gov.hk", null, false, true ), +(256, "EEG2301", "EEG2301", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHAN Janzen", "EEG2301", "jchan@emsd.gov.hk", null, false, true ), +(257, "EEG2302", "EEG2302", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHEUNG Wai Yeung", "EEG2302", "cheungwaiyeung@emsd.gov.hk", null, false, true ), +(258, "EEG2303", "EEG2303", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHAN Wing Hang", "EEG2303", "chanwinghang@emsd.gov.hk", null, false, true ), +(259, "EEG2304", "EEG2304", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LEUNG Kit Wo David", "EEG2304", "davidkwleung@emsd.gov.hk", null, false, true ), +(260, "EEG2305", "EEG2305", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LIU Pak Hei", "EEG2305", "phliu@emsd.gov.hk", null, false, true ), +(261, "EEG2306", "EEG2306", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr MAN Ou Sze", "EEG2306", "osman@emsd.gov.hk", null, false, true ), +(262, "EEG2307", "EEG2307", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss TSUI Emma", "EEG2307", "etsui@emsd.gov.hk", null, false, true ), +(263, "EnEG2201", "EnEG2201", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss LAM Po Ying", "EnEG2201", "pylam@emsd.gov.hk", null, false, true ), +(264, "EnEG2202", "EnEG2202", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr WONG Chi Yui", "EnEG2202", "wongchiyui@emsd.gov.hk", null, false, true ), +(265, "EnEG2203", "EnEG2203", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LAU Tsz Fung", "EnEG2203", "tflau@emsd.gov.hk", null, false, true ), +(266, "EnEG2301", "EnEG2301", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LO Yat Hei", "EnEG2301", "yhlo@emsd.gov.hk", null, false, true ), +(267, "InfEG2201", "InfEG2201", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss WONG Yuk Ting, Elsie", "InfEG2201", "elsiewong@emsd.gov.hk", null, false, true ), +(268, "MEG2201", "MEG2201", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHUI Mang Yin", "MEG2201", "mychui@emsd.gov.hk", null, false, true ), +(269, "MEG2202", "MEG2202", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LAW Chun Hei", "MEG2202", "lawchunhei@emsd.gov.hk", null, false, true ), +(270, "MEG2203", "MEG2203", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr LEE Tsz Wai Boris", "MEG2203", "borislee@emsd.gov.hk", null, false, true ), +(271, "MEG2204", "MEG2204", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr SUM Ho Yin", "MEG2204", "sumhy@emsd.gov.hk", null, false, true ), +(272, "MEG2206", "MEG2206", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr YUNG Ho Lam", "MEG2206", "hlyung@emsd.gov.hk", null, false, true ), +(273, "MEG2302", "MEG2302", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHENG Yan Lung", "MEG2302", "ylcheng@emsd.gov.hk", null, false, true ), +(274, "MEG2303", "MEG2303", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr CHU Ho Man", "MEG2303", "hmchu@emsd.gov.hk", null, false, true ), +(275, "MEG2304", "MEG2304", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss LI Po Yan", "MEG2304", "pyli@emsd.gov.hk", null, false, true ), +(276, "MEG2305", "MEG2305", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Mr WONG Tsz Fung", "MEG2305", "wongtf@emsd.gov.hk", null, false, true ), +(277, "MEG2306", "MEG2306", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss WONG Yuk Ching", "MEG2306", "wongyukching@emsd.gov.hk", null, false, true ), +(278, "MEG2307", "MEG2307", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 1"), "Miss YUEN Man Sha", "MEG2307", "msyuen@emsd.gov.hk", null, false, true ), +(279, "SET2", "SET2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 2"), "Mr LEE Wing Keung", "SE/T2", "leewk@emsd.gov.hk", null, false, true ), +(280, "ET21", "ET21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Training Unit 2"), "Mr HO Ming Tung", "E/T2/1", "homt@emsd.gov.hk", null, false, true ), +(281, "CEDT", "CEDT", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr CHAN Hor Yin", "CE/DT", "stevehyc@emsd.gov.hk", null, false, true ), +(282, "SEAI", "SEAI", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Artificial Intelligence Sub-division"), "Mr WONG Wai Tat", "SE/AI", "wtwong@emsd.gov.hk", null, false, true ), +(283, "EEAI", "EEAI", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Artificial Intelligence Sub-division"), "Mr LEE Nicholas", "EE/AI", "nicholaslee@emsd.gov.hk", null, false, true ), +(284, "ITMAI1", "ITMAI1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Artificial Intelligence Sub-division"), "Mr NGAI Chun Kei", "ITM/AI/1", "ckngai@emsd.gov.hk", null, false, true ), +(285, "EEBIM1", "EEBIM1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Building Information Modelling Sub-division"), "Mr YEUNG Hong Kiu", "EE/BIM/1", "hkyeung@emsd.gov.hk", null, false, true ), +(286, "EEBIM2", "EEBIM2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Building Information Modelling Sub-division"), "Mr CHAN Tze Chun", "EE/BIM/2", "chantzechun@emsd.gov.hk", null, false, true ), +(287, "EEBIM4", "EEBIM4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Building Information Modelling Sub-division"), "Mr YEUNG Ming Lui", "EE/BIM/4", "mlyeung@emsd.gov.hk", null, false, true ), +(288, "EEBIM5", "EEBIM5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Building Information Modelling Sub-division"), "Miss CHING Yuen Yuen", "EE/BIM/5", "yyching@emsd.gov.hk", null, false, true ), +(289, "EEBIM6", "EEBIM6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Building Information Modelling Sub-division"), "Mr SHEK Kar Long", "EE/BIM/6", "klshek@emsd.gov.hk", null, false, true ), +(290, "SEEMBSTD", "SEEMBSTD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Ms YAU Chi Ying", "SE/EMBSTD", "vanessayau@emsd.gov.hk", null, false, true ), +(291, "BSEEMBSTD4", "BSEEMBSTD4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Mr HON Chun Keung", "BSE/EMBSTD/4", "ckhon@emsd.gov.hk", null, false, true ), +(292, "EMEEMBSTD1", "EMEEMBSTD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Mr LEE Ming Bun", "EME/EMBSTD/1", "mblee@emsd.gov.hk", null, false, true ), +(293, "EMEEMBSTD2", "EMEEMBSTD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Mr NG Ka Lok", "EME/EMBSTD/2", "michaelng@emsd.gov.hk", null, false, true ), +(294, "EMEEMBSTD3", "EMEEMBSTD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Mr JIANG Weier", "EME/EMBSTD/3", "wjiang@emsd.gov.hk", null, false, true ), +(295, "POEMBSTD1", "POEMBSTD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Mr TANG Chak Long", "PO/EMBSTD/1(N)", "cltang@emsd.gov.hk", null, false, true ), +(296, "POEMBSTD2", "POEMBSTD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "E&M and BS Technology Development Sub-division"), "Mr WONG Cheuk San", "PO/EMBSTD/2(N)", "cswong@emsd.gov.hk", null, false, true ), +(297, "SEENTD", "SEENTD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electronic Technology Development Sub-division"), "Mr FAN Chi Wing", "SE/ENTD", "ericfan@emsd.gov.hk", null, false, true ), +(298, "EEENTD1", "EEENTD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electronic Technology Development Sub-division"), "Mr CHENG Cheuk Tak", "EE/ENTD/1", "chengct@emsd.gov.hk", null, false, true ), +(299, "EEENTD2", "EEENTD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Electronic Technology Development Sub-division"), "Mr TSEUNG Chung Long", "EE/ENTD/2", "cltseung@emsd.gov.hk", null, false, true ), +(300, "SEGWIN", "SEGWIN", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Government-Wide Internet-of-Things Network"), "Mr TAM Manon", "SE/GWIN", "mtam@emsd.gov.hk", null, false, true ), +(301, "BSEGWIN2", "BSEGWIN2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Government-Wide Internet-of-Things Network"), "Miss HEUNG Mei Chun", "BSE/GWIN/2", "mcheung@emsd.gov.hk", null, false, true ), +(302, "EEGWIN1", "EEGWIN1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Government-Wide Internet-of-Things Network"), "Mr SIN Chun Ho, Leon", "EE/GWIN/1", "chsin@emsd.gov.hk", null, false, true ), +(303, "SEITD", "SEITD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr CHENG Tung Kit, Dennis", "SE/ITD", "tkcheng@emsd.gov.hk", null, false, true ), +(304, "EEITD1", "EEITD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr LI Kam Keung", "EE/ITD/1", "tonyli@emsd.gov.hk", null, false, true ), +(305, "EEITD2", "EEITD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr HO Chun Wo", "EE/ITD/2", "hocw@emsd.gov.hk", null, false, true ), +(306, "EEITD3", "EEITD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr FUNG Kin Shing", "EE/ITD/3", "fungkinshing@emsd.gov.hk", null, false, true ), +(307, "EEITD4", "EEITD4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr WAN Hiu Tung", "EE/ITD/4", "htwan@emsd.gov.hk", null, false, true ), +(308, "EEITD5", "EEITD5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Miss YEUNG Po Ching", "EE/ITD/5", "pcyeung@emsd.gov.hk", null, false, true ), +(309, "ITMITD1", "ITMITD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr WONG Siu Kei, Adonis", "ITM/ITD/1", "skwong@emsd.gov.hk", null, false, true ), +(310, "ITMITD2", "ITMITD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr CHAN Chun Ki, Anderson", "ITM/ITD/2", "andersonchan@emsd.gov.hk", null, false, true ), +(311, "ITMITD3", "ITMITD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Development Sub-division"), "Mr KWAI Hei Ah", "ITM/ITD/3", "hakwai@emsd.gov.hk", null, false, true ), +(312, "SEITSS", "SEITSS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr CHAN Hei Yim, Leo", "SE/ITSS", "leochan@emsd.gov.hk", null, false, true ), +(313, "EITSS1", "EITSS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr LEUNG Yan Sing", "E/ITSS/1", "ysleung@emsd.gov.hk", null, false, true ), +(314, "ITMITSS1", "ITMITSS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Ms LEUNG Mei Chong", "ITM/ITSS/1", "winnielmc@emsd.gov.hk", null, false, true ), +(315, "EITSS3", "EITSS3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr CHEUNG Ka Ming", "E/ITSS/3", "cheungkaming@emsd.gov.hk", null, false, true ), +(316, "ITMITSS4", "ITMITSS4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Ms LAU Man Mei", "ITM/ITSS/4", "mmlau@emsd.gov.hk", null, false, true ), +(317, "ITMITSS3", "ITMITSS3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr CHAN Chun Hong", "ITM/ITSS/3", "alanchan@emsd.gov.hk", null, false, true ), +(318, "EITSS5", "EITSS5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr LEUNG Ling Hang", "E/ITSS/5", "lhleung@emsd.gov.hk", null, false, true ), +(319, "EITSS2", "EITSS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr YUNG Ka Kit", "E/ITSS/2", "kkyung@emsd.gov.hk", null, false, true ), +(320, "EITSS7", "EITSS7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Information Technology Strategic Support"), "Mr SIN Bo Chi", "E/ITSS/7", "bcsin@emsd.gov.hk", null, false, true ), +(321, "SEINNO", "SEINNO", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LAI Chun Fai", "SE/Inno", "cflai@emsd.gov.hk", null, false, true ), +(322, "EINNO1", "EINNO1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr HUI Ka Wai", "E/Inno/1", "huikw@emsd.gov.hk", null, false, true ), +(323, "EINNO2", "EINNO2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr YUEN Ka Hing", "E/Inno/2", "yuenkh@emsd.gov.hk", null, false, true ), +(324, "EINNO3", "EINNO3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms SO Tsz Kwan", "E/Inno/3", "tkso@emsd.gov.hk", null, false, true ), +(325, "POInno1", "POInno1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms CHAU Yi Wai", "PO/Inno/1(N)", "ywchau@emsd.gov.hk", null, false, true ), +(326, "POINNO10", "POINNO10", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr MA Wai Hung", "PO/Inno/10(N)", "whma@emsd.gov.hk", null, false, true ), +(327, "POINNO11", "POINNO11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr HUNG Wan Man", "PO/Inno/11(N)", "wmhung@emsd.gov.hk", null, false, true ), +(328, "POINNO12", "POINNO12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr WU Wan Lung", "PO/Inno/12(N)", "wlwu@emsd.gov.hk", null, false, true ), +(329, "POINNO13", "POINNO13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr YAN Wing Keung", "PO/Inno/13(N)", "wkyan@emsd.gov.hk", null, false, true ), +(330, "POINNO17", "POINNO17", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms CHEUNG Him Wing, Shirley", "PO/Inno/17(N)", "shirleycheung@emsd.gov.hk", null, false, true ), +(331, "POINNO18", "POINNO18", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LO Wing Sing", "PO/Inno/18(N)", "wslo@emsd.gov.hk", null, false, true ), +(332, "POINNO19", "POINNO19", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LAI Dickson", "PO/Inno/19(N)", "dicksonlai@emsd.gov.hk", null, false, true ), +(333, "POINNO20", "POINNO20", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr KWOK Tsz Fai", "PO/Inno/20(N)", "tfkwok@emsd.gov.hk", null, false, true ), +(334, "POINNO21", "POINNO21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr YIP Kim Ming", "PO/Inno/21(N)", "yipkimming@emsd.gov.hk", null, false, true ), +(335, "POINNO22", "POINNO22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LAI Chi Keung, Eric", "PO/Inno/22(N)", "cklai@emsd.gov.hk", null, false, true ), +(336, "POINNO23", "POINNO23", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LIU Kai Wang", "PO/Inno/23(N)", "liukw@emsd.gov.hk", null, false, true ), +(337, "POINNO24", "POINNO24", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr CHUNG Chung Wah, Benjamin", "PO/Inno/24(N)", "benjamin@emsd.gov.hk", null, false, true ), +(338, "POINNO25", "POINNO25", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr CHAN Ho Wai", "PO/Inno/25(N)", "hwchan@emsd.gov.hk", null, false, true ), +(339, "POInno26", "POInno26", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr NG Cheuk Yan", "PO/Inno/26(N)", "ngcy@emsd.gov.hk", null, false, true ), +(340, "POINNO27", "POINNO27", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LEE Chi Hong", "PO/Inno/27(N)", "leechihong@emsd.gov.hk", null, false, true ), +(341, "POINNO28", "POINNO28", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr SING Hin Wing", "PO/Inno/28(N)", "hwsing@emsd.gov.hk", null, false, true ), +(342, "POINNO29", "POINNO29", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms WONG Sung Ming", "PO/Inno/29(N)", "wongsm@emsd.gov.hk", null, false, true ), +(343, "POInno31", "POInno31", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms WAN Chiu Ling", "PO/Inno/31(N)", "clwan@emsd.gov.hk", null, false, true ), +(344, "POINNO32", "POINNO32", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr CHAN Lung Wai, Chris", "PO/Inno/32(N)", "chrischan@emsd.gov.hk", null, false, true ), +(345, "POINNO4", "POINNO4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr TSANG Wa Sing", "PO/Inno/4(N)", "wstsang@emsd.gov.hk", null, false, true ), +(346, "POINNO5", "POINNO5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms LI Wing Man", "PO/Inno/5(N)", "liwm@emsd.gov.hk", null, false, true ), +(347, "POInno9", "POInno9", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms HOU Yabing", "PO/Inno/9(N)", "yhou@emsd.gov.hk", null, false, true ), +(348, "POInno33", "POInno33", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr PAN Wai Wong", "PO/Inno/33(N)", "panww@emsd.gov.hk", null, false, true ), +(349, "POINNO34", "POINNO34", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr CHENG Chun Kit", "PO/Inno/34(N)", "chengck@emsd.gov.hk", null, false, true ), +(350, "POINNO36", "POINNO36", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr TAM Kwok Kwan", "PO/Inno/36(N)", "kktam@emsd.gov.hk", null, false, true ), +(351, "POINNO37", "POINNO37", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr WONG Tin Ho", "PO/Inno/37(N)", "wongtinho@emsd.gov.hk", null, false, true ), +(352, "POINNO40", "POINNO40", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LEUNG Sze Ming", "PO/Inno/40(N)", "leungsm@emsd.gov.hk", null, false, true ), +(353, "POINNO42", "POINNO42", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Ms YAU Wan Kan", "PO/Inno/42(N)", "wkyau@emsd.gov.hk", null, false, true ), +(354, "POINNO47", "POINNO47", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr WONG Wai Lam", "PO/Inno/47(N)", "wongwailam@emsd.gov.hk", null, false, true ), +(355, "POINNO50", "POINNO50", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr CHAN Chung Ki Louis", "PO/Inno/50(N)", "louischan@emsd.gov.hk", null, false, true ), +(356, "POINNO51", "POINNO51", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr CHENG Chi Man", "PO/Inno/51(N)", "cmcheng@emsd.gov.hk", null, false, true ), +(357, "POINNO53", "POINNO53", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Inno-Office"), "Mr LAM Chak Kui", "PO/Inno/53(N)", "lamchakkui@emsd.gov.hk", null, false, true ), +(358, "CAD1", "CAD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Contract Advisory Unit 1"), "Mr YEUNG Hong Yu", "CAd1", "hyyeung@emsd.gov.hk", null, false, true ), +(359, "ECAU11", "ECAU11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Contract Advisory Unit 1"), "Mr MOK Man Wing, Stanley", "E/CAU1/1", "stanleymok@emsd.gov.hk", null, false, true ), +(360, "ECAU12", "ECAU12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Contract Advisory Unit 1"), "Mr NG Ka Pui", "E/CAU1/2", "kpng@emsd.gov.hk", null, false, true ), +(361, "ECAU13", "ECAU13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Contract Advisory Unit 1"), "Ms WONG Sze Ki, Suki", "E/CAU1/3", "sukiwong@emsd.gov.hk", null, false, true ), +(362, "CAD2", "CAD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Contract Advisory Unit 2"), "Ms TAM Wing Yee", "CAd2", "wytam@emsd.gov.hk", null, false, true ), +(363, "ECAU21", "ECAU21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Contract Advisory Unit 2"), "Mr WONG Ka Wing, Paul", "E/CAU2/1", "paulwong@emsd.gov.hk", null, false, true ), +(364, "DDTS", "DDTS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr CHAN Chi Wai, Richard", "DD/TS", "richardc@emsd.gov.hk", null, false, true ), +(365, "SETS", "SETS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Ms WONG Ying Ying, Regina", "SE/TS", "regina@emsd.gov.hk", null, false, true ), +(366, "ETS1", "ETS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Directorate"), "Mr SIU Hiu Fai", "E/TS/1", "hfsiu@emsd.gov.hk", null, false, true ), +(367, "SC_HKFSD_ABSI13", "SC_HKFSD_ABSI13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr YIM Cheuk Fung, Vincent", "ABSI(AEP)", "yimcf@emsd.gov.hk", null, false, true ), +(368, "SC_HKFSD_ABSI1", "SC_HKFSD_ABSI1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHAN Kai Wah, Wally", "ABSI(RDS)1", "chankw@emsd.gov.hk", null, false, true ), +(369, "SC_HKFSD_ABSI18", "SC_HKFSD_ABSI18", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LEE Wai Ping", "ABSI(RDS)4", "leewp@emsd.gov.hk", null, false, true ), +(370, "SC_HKFSD_ABSI14", "SC_HKFSD_ABSI14", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LUN Kwok Fung", "ABSI/BI/103/FSC", "kflun@emsd.gov.hk", null, false, true ), +(371, "SC_HKFSD_ABSI11", "SC_HKFSD_ABSI11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHONG Kin Hing", "ABSI/BI/105/FSC", "khchong@emsd.gov.hk", null, false, true ), +(372, "SC_HKFSD_ABSI12", "SC_HKFSD_ABSI12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHAN Yan", "ABSI/BI/106/FSC", "ychan@emsd.gov.hk", null, false, true ), +(373, "SC_HKFSD_ABSI15", "SC_HKFSD_ABSI15", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr YAU Kwan Lam, Bennett", "ABSI/BI/110/FSC", "bennettyau@emsd.gov.hk", null, false, true ), +(374, "SC_HKFSD_ABSI16", "SC_HKFSD_ABSI16", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LIU Pok Man", "ABSI/BI/111/FSC", "pmliu@emsd.gov.hk", null, false, true ), +(375, "SC_HKFSD_ABSI2", "SC_HKFSD_ABSI2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr TANG Man Kit", "ABSI/BI/201/FSC", "mktang@emsd.gov.hk", null, false, true ), +(376, "SC_HKFSD_ABSI8", "SC_HKFSD_ABSI8", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LEE Ka Kit, Kinson", "ABSI/BI/202/FSC", "kinsonlee@emsd.gov.hk", null, false, true ), +(377, "SC_HKFSD_ABSI7", "SC_HKFSD_ABSI7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LAW Man Fai", "ABSI/BI/206/FSC", "mflaw@emsd.gov.hk", null, false, true ), +(378, "SC_HKFSD_ABSI10", "SC_HKFSD_ABSI10", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr WONG Chun Yin", "ABSI/BI/208/FSC", "wongchunyin@emsd.gov.hk", null, false, true ), +(379, "SC_HKFSD_BSI5", "SC_HKFSD_BSI5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LAM Yue Man", "BSI(AEP)", "lamym@emsd.gov.hk", null, false, true ), +(380, "SC_HKFSD_BSI3", "SC_HKFSD_BSI3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr WONG Wai Ho", "BSI(RDS)2", "waihowong@emsd.gov.hk", null, false, true ), +(381, "SC_HKFSD_BSI2", "SC_HKFSD_BSI2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHAN Wing Kin", "BSI(RDS)3", "wingkinchan@emsd.gov.hk", null, false, true ), +(382, "SC_HKFSD_BSI4", "SC_HKFSD_BSI4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LEUNG Chun Wah", "BSI(RDS)4", "leungchunwah@emsd.gov.hk", null, false, true ), +(383, "EIIRC", "EIIRC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHENG Wing Yip, Ringo", "EI(IRC)", "ringocheng@emsd.gov.hk", null, false, true ), +(384, "SEEES", "SEEES", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr KONG Ho, Felix", "SEE/E&S", "felixkong@emsd.gov.hk", null, false, true ), +(385, "WS1ACIRC", "WS1ACIRC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr TAM Shu Sing", "WSI(AC)(IRC)", "sstam@emsd.gov.hk", null, false, true ), +(386, "ADBS", "ADBS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHAN Ming Yee", "Ag. AD/BS", "my_chan@emsd.hksarg", null, false, true ), +(387, "BSISS2", "BSISS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LAU Chung Wai", "BSI/B/5", "cwlau@emsd.gov.hk", null, false, true ), +(388, "CBSE1", "CBSE1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr YEUNG Chor Kee", "CBSE/1", "yeungck@emsd.gov.hk", null, false, true ), +(389, "CBSE2", "CBSE2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LAM Man Tim, John", "CBSE/2", "johnlam@emsd.gov.hk", null, false, true ), +(390, "CBSE3", "CBSE3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr NGAN Kwok Hip", "CBSE/3", "ngankh@archsd.gov.hk", null, false, true ), +(391, "CBSE4", "CBSE4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Ms HO Kit Yee, Kitty", "CBSE/4", "hokitty@emsd.gov.hk", null, false, true ), +(392, "ADEM", "ADEM", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr CHAN Yiu Hon", "AD/E&M", "yhchan@emsd.gov.hk", null, false, true ), +(393, "EMEP31", "EMEP31", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Miss CHU Sui Ki", "EME/P3/1", "skchu@dsd.gov.hk", null, false, true ), +(394, "SPM337", "SPM337", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Other Department"), "Mr LOH Yew Leong", "SPM337", "ylloh@emsd.gov.hk", null, false, true ), +(395, "SETSA", "SETSA", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services A)"), "Mr YUEN Chi Lap", "SE/TSA", "clyuen@emsd.gov.hk", null, false, true ), +(396, "ETSA1", "ETSA1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services A)"), "Mr NG Man To", "E/TSA/1", "mtng@emsd.gov.hk", null, false, true ), +(397, "ETSA2", "ETSA2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services A)"), "Mr TAM Chi Fung, Simon", "E/TSA/2", "simontam@emsd.gov.hk", null, false, true ), +(398, "ETSA3", "ETSA3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services A)"), "Mr LAU Kin Tung", "E/TSA/3", "marklau@emsd.gov.hk", null, false, true ), +(399, "SETSB", "SETSB", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services B)"), "Mr HON Ming, Billy", "SE/TSB", "billyhon@emsd.gov.hk", null, false, true ), +(400, "ETSB1", "ETSB1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services B)"), "Mr HO Ka Cheung", "E/TSB/1", "kho@emsd.gov.hk", null, false, true ), +(401, "ETSB2", "ETSB2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services B)"), "Mr CHAN Chun Fung", "E/TSB/2", "chancf@emsd.gov.hk", null, false, true ), +(402, "ETSB3", "ETSB3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services B)"), "Mr CHEUNG Ka Yau", "E/TSB/3", "cheungkayau@emsd.gov.hk", null, false, true ), +(403, "SETSC", "SETSC", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Mr IP Hok Shan", "SE/TSC", "iphs@emsd.gov.hk", null, false, true ), +(404, "ETSC2", "ETSC2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Miss SO Pak Yee, Peony", "E/TSC/2", "peonyso@emsd.gov.hk", null, false, true ), +(405, "ETSC3", "ETSC3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Mr KWONG Chi Hang", "E/TSC/3", "chkwong@emsd.gov.hk", null, false, true ), +(406, "ETSC5", "ETSC5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Mr LEUNG Ka Chun", "E/TSC/5", "kcleung_3@emsd.gov.hk", null, false, true ), +(407, "ETSC6", "ETSC6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Ms WONG Cheuk Man", "E/TSC/6", "wongcheukman@emsd.gov.hk", null, false, true ), +(408, "ETSC7", "ETSC7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Mr LAM Tony", "E/TSC/7", "tonylam@emsd.gov.hk", null, false, true ), +(409, "PSETSC1", "PSETSC1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Mr CHAN Yau Man", "PSE/TSC/1", "ymchan@emsd.gov.hk", null, false, true ), +(410, "PETSC1", "PETSC1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services C)"), "Mr MA Kwong Hung", "PE/TSC/1", "khma@emsd.gov.hk", null, false, true ), +(411, "SETSD", "SETSD", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services D)"), "Mr WONG Fuk Ling", "SE/TSD", "chriswong@emsd.gov.hk", null, false, true ), +(412, "ETSD1", "ETSD1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services D)"), "Mr LEUNG Ka Chun", "E/TSD/1", "leungkachun@emsd.gov.hk", null, false, true ), +(413, "ETSD2", "ETSD2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services D)"), "Miss LAU Hoi Shuk, Virginia", "E/TSD/2", "virginialau@emsd.gov.hk", null, false, true ), +(414, "ETSD3", "ETSD3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services D)"), "Mr CHAN Siu Chung", "E/TSD/3", "chansc@emsd.gov.hk", null, false, true ), +(415, "SETSE", "SETSE", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services E)"), "Mr KWAN Siu Kin", "SE/TSE", "skkwan@emsd.gov.hk", null, false, true ), +(416, "ETSE1", "ETSE1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services E)"), "Mr LEE Chun Pong, Patrick", "E/TSE/1", "patricklee@emsd.gov.hk", null, false, true ), +(417, "ETSE2", "ETSE2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services E)"), "Mr HO Tung Ip", "E/TSE/2", "tiho@emsd.gov.hk", null, false, true ), +(418, "ETSE3", "ETSE3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services E)"), "Mr CHU Yu For", "E/TSE/3", "yfchu@emsd.gov.hk", null, false, true ), +(419, "ETSE4", "ETSE4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services E)"), "Mr LAW Tin Chung", "E/TSE/4", "lawtc@emsd.gov.hk", null, false, true ), +(420, "SETSF", "SETSF", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Mr LING Chun Fung", "SE/TSF", "kevinling@emsd.gov.hk", null, false, true ), +(421, "ETSF1", "ETSF1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Miss CHAN Wing Yee", "E/TSF/1", "chanwingyee@emsd.gov.hk", null, false, true ), +(422, "ETSF2", "ETSF2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Mr LAW Man Kit", "E/TSF/2", "mklaw@emsd.gov.hk", null, false, true ), +(423, "ETSF3", "ETSF3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Mr LAM Hoi Pang", "E/TSF/3", "hplam@emsd.gov.hk", null, false, true ), +(424, "ETSF4", "ETSF4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Miss CHAN Ka Ching", "E/TSF/4", "kcchan@emsd.gov.hk", null, false, true ), +(425, "ETSFA", "ETSFA", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Mr CHEN Tsun Yin", "E/TSF/A", "tychen@emsd.gov.hk", null, false, true ), +(426, "ETSFB", "ETSFB", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services F)"), "Mr HO Wing Yiu", "E/TSF/B", "peterho@emsd.gov.hk", null, false, true ), +(427, "SETSG", "SETSG", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr WONG Chi Wah", "SE/TSG", "wongchiwah@emsd.gov.hk", null, false, true ), +(428, "ETSG1", "ETSG1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Ms CHAN Sze Kit", "E/TSG/1", "skchan@emsd.gov.hk", null, false, true ), +(429, "ETSG2", "ETSG2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr LEE Chi Wa", "E/TSG/2", "leecw@emsd.gov.hk", null, false, true ), +(430, "ETSG3", "ETSG3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr LEUNG Wai Ho", "E/TSG/3", "leungwaiho@emsd.gov.hk", null, false, true ), +(431, "ETSG4", "ETSG4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr LEUNG Ka Ho", "E/TSG/4", "leungkaho@emsd.gov.hk", null, false, true ), +(432, "ETSG5", "ETSG5", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr CHENG Sze Ming", "E/TSG/5", "chengszeming@emsd.gov.hk", null, false, true ), +(433, "ETSG6", "ETSG6", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr LAM Chak Fung", "E/TSG/6", "lamcf@emsd.gov.hk", null, false, true ), +(434, "ETSG7", "ETSG7", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services G)"), "Mr WAN Chak Sam", "E/TSG/7", "cswan@emsd.gov.hk", null, false, true ), +(435, "SETSH", "SETSH", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services H)"), "Mr NGAN Hong Yiu", "SE/TSH", "hyngan@emsd.gov.hk", null, false, true ), +(436, "ETSH1", "ETSH1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services H)"), "Mr LAM Chak Ching", "E/TSH/1", "cclam@emsd.gov.hk", null, false, true ), +(437, "ETSH2", "ETSH2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services H)"), "Mr WANG Kai", "E/TSH/2", "kwang@emsd.gov.hk", null, false, true ), +(438, "ETSH3", "ETSH3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services H)"), "Mr LI Bun", "E/TSH/3", "bli@emsd.gov.hk", null, false, true ), +(439, "ETSH4", "ETSH4", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services H)"), "Mr CHAN Hiu Fung", "E/TSH/4", "chanhf@emsd.gov.hk", null, false, true ), +(440, "PETSH1", "PETSH1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Transport Services Sub-division (Transport Services H)"), "Mr KWOK Yu Fat", "PE/TSH/1", "yfkwok@emsd.gov.hk", null, false, true ), +(441, "EBCF11", "EBCF11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 1)"), "Mr CHEUNG Man Hon", "E/BCF1/1", "mhcheung@emsd.gov.hk", null, false, true ), +(442, "EBCF12", "EBCF12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 1)"), "Mr KAM Kwok Hang, Dave", "E/BCF1/2", "davekam@emsd.gov.hk", null, false, true ), +(443, "EBCF13", "EBCF13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 1)"), "Mr POON Chun Ho, Henry", "E/BCF1/3", "henrypoon@emsd.gov.hk", null, false, true ), +(444, "EBCF23", "EBCF23", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Mr LO Lok Kin", "E/BCF2/3", "lklo@emsd.gov.hk", null, false, true ), +(445, "EBCF24", "EBCF24", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Mr CHOU Yuk Wah", "E/BCF2/4", "wilsonchou@emsd.gov.hk", null, false, true ), +(446, "SEBCF2", "SEBCF2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Mr FU Ming Sun", "SE/BCF2", "msfu@emsd.gov.hk", null, false, true ), +(447, "EBCF21", "EBCF21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Ms CHEUNG Pui Yi", "E/BCF2/1", "pycheung@emsd.gov.hk", null, false, true ), +(448, "EBCF22", "EBCF22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Mr CHU Man Tat", "E/BCF2/2", "mtchu@emsd.gov.hk", null, false, true ), +(449, "EBCF25", "EBCF25", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Mr CHAN Ka Leong", "E/BCF2/5", "klchan@emsd.gov.hk", null, false, true ), +(450, "EBCF26", "EBCF26", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 2)"), "Ms SE Mei King", "E/BCF2/6", "mkse@emsd.gov.hk", null, false, true ), +(451, "EBCF36", "EBCF36", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), "Mr CHAN Hin Cheung", "E/BCF3/6", "chanhc@emsd.gov.hk", null, false, true ), +(452, "EBCF37", "EBCF37", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), "Mr HO Chun Ho", "E/BCF3/7", "hochunho@emsd.gov.hk", null, false, true ), +(453, "SEBCF3", "SEBCF3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), "Mr LAM Kwok Yin", "SE/BCF3", "lamky@emsd.gov.hk", null, false, true ), +(454, "EBCF31", "EBCF31", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), "Mr CHU Kam Hon", "E/BCF3/1", "khchu@emsd.gov.hk", null, false, true ), +(455, "EBCF32", "EBCF32", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), "Mr KUNG Hing", "E/BCF3/2", "hkung@emsd.gov.hk", null, false, true ), +(456, "EAS14", "EAS14", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 1)"), "Mr IP Chun Woon, Vincent", "E/AS1/4", "vincentip@emsd.gov.hk", null, false, true ), +(457, "SEAS1", "SEAS1", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 1)"), "Mr LEUNG Pak Kin", "SE/AS1", "pkleung@emsd.gov.hk", null, false, true ), +(458, "EAS11", "EAS11", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 1)"), "Mr FONG Kiu On", "E/AS1/1", "kofong@emsd.gov.hk", null, false, true ), +(459, "EAS13", "EAS13", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 1)"), "Mr WAN Kwun Chiu", "E/AS1/3", "jollywan@emsd.gov.hk", null, false, true ), +(460, "EAS12", "EAS12", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 1)"), "Miss YAO Cassie Hung", "E/AS1/2", "chyao@emsd.gov.hk", null, false, true ), +(461, "SEAS2", "SEAS2", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 2)"), "Miss LUK Pui Kwan, Clare", "SE/AS2", "clareluk@emsd.gov.hk", null, false, true ), +(462, "EAS21", "EAS21", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 2)"), "Mr HUI Lok", "E/AS2/1", "lhui@emsd.gov.hk", null, false, true ), +(463, "EAS22", "EAS22", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 2)"), "Mr LI Kin Pong", "E/AS2/2", "kpli@emsd.gov.hk", null, false, true ), +(464, "EAS23", "EAS23", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 2)"), "Miss LI Ngai Man", "E/AS2/3", "nmli@emsd.gov.hk", null, false, true ), +(465, "EAS31", "EAS31", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 3)"), "Mr NG Hon Fung", "E/AS3/1", "hfng@emsd.gov.hk", null, false, true ), +(466, "EAS32", "EAS32", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 3)"), "Ms CHIK Kar Lai", "E/AS3/2", "klchik@emsd.gov.hk", null, false, true ), +(467, "EAS34", "EAS34", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Airport Services 3)"), "Miss KUO Fei Ha", "E/AS3/4", "fhkuo@emsd.gov.hk", null, false, true ), +(468, "EBCF35", "EBCF35", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Boundary Crossing Facilities Sub-division (Boundary Crossing Facilities 3)"), "Ms CHAN Siu Wai", "E/BCF3/5", "chansiuwai@emsd.gov.hk", null, false, true ), +(469, "SEHFS3", "SEHFS3", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 3 Sub-division"), "Mr CHU Wa Wing", "SE/HFS3", "wwchu@emsd.gov.hk", null, false, true ), +(470, "EHFS31", "EHFS31", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 3 Sub-division"), "Miss LI Siu Ying", "E/HFS3/1", "syli@emsd.gov.hk", null, false, true ), +(471, "EHFS32", "EHFS32", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Hydrogen Fuel Safety 3 Sub-division"), "Mr HAU Ho Chau", "E/HFS3/2", "hchau@emsd.gov.hk", null, false, true ), +(472, "DDRS", "DDRS", (SELECT sd.id FROM sub_division sd WHERE sd.name = "Trading Departmental Control"), "Mr CHAN Pak Cheung", "Ag. DD/RS", "pcchan@emsd.gov.hk", null, false, true ); + + +--comment: user_authority +INSERT INTO user_authority (userId, authId) VALUES +(1, 21) +,(1, 22) +,(3, 21) +,(3, 22) +,(4, 21) +,(4, 22) +,(5, 21) +,(5, 22) +,(6, 21) +,(6, 22) +,(9, 21) +,(9, 22) +,(10, 21) +,(10, 22) +,(11, 21) +,(11, 22) +,(12, 21) +,(12, 22) +,(13, 21) +,(13, 22) +,(14, 21) +,(14, 22) +,(15, 21) +,(15, 22) +,(16, 21) +,(16, 22) +,(17, 21) +,(17, 22) +,(18, 21) +,(18, 22) +,(19, 21) +,(19, 22) +,(20, 21) +,(20, 22) +,(21, 21) +,(21, 22) +,(22, 21) +,(22, 22) +,(23, 21) +,(23, 22) +,(24, 21) +,(24, 22) +,(25, 21) +,(25, 22) +,(26, 21) +,(26, 22) +,(27, 21) +,(27, 22) +,(28, 21) +,(28, 22) +,(29, 21) +,(29, 22) +,(30, 21) +,(30, 22) +,(31, 21) +,(31, 22) +,(32, 21) +,(32, 22) +,(33, 21) +,(33, 22) +,(34, 21) +,(34, 22) +,(35, 21) +,(35, 22) +,(36, 21) +,(36, 22) +,(37, 21) +,(37, 22) +,(38, 21) +,(38, 22) +,(39, 21) +,(39, 22) +,(40, 21) +,(40, 22) +,(41, 21) +,(41, 22) +,(42, 21) +,(42, 22) +,(43, 21) +,(43, 22) +,(44, 21) +,(44, 22) +,(45, 21) +,(45, 22) +,(46, 21) +,(46, 22) +,(47, 21) +,(47, 22) +,(48, 21) +,(48, 22) +,(49, 21) +,(49, 22) +,(50, 21) +,(50, 22) +,(51, 21) +,(51, 22) +,(52, 21) +,(52, 22) +,(53, 21) +,(53, 22) +,(54, 21) +,(54, 22) +,(55, 21) +,(55, 22) +,(56, 21) +,(56, 22) +,(57, 21) +,(57, 22) +,(58, 21) +,(58, 22) +,(59, 21) +,(59, 22) +,(60, 21) +,(60, 22) +,(61, 21) +,(61, 22) +,(62, 21) +,(62, 22) +,(63, 21) +,(63, 22) +,(64, 21) +,(64, 22) +,(65, 21) +,(65, 22) +,(66, 21) +,(66, 22) +,(67, 21) +,(67, 22) +,(68, 21) +,(68, 22) +,(69, 21) +,(69, 22) +,(70, 21) +,(70, 22) +,(71, 21) +,(71, 22) +,(72, 21) +,(72, 22) +,(73, 21) +,(73, 22) +,(74, 21) +,(74, 22) +,(75, 21) +,(75, 22) +,(76, 21) +,(76, 22) +,(77, 21) +,(77, 22) +,(78, 21) +,(78, 22) +,(79, 21) +,(79, 22) +,(80, 21) +,(80, 22) +,(81, 21) +,(81, 22) +,(82, 21) +,(82, 22) +,(83, 21) +,(83, 22) +,(84, 21) +,(84, 22) +,(85, 21) +,(85, 22) +,(86, 21) +,(86, 22) +,(87, 21) +,(87, 22) +,(88, 21) +,(88, 22) +,(89, 21) +,(89, 22) +,(90, 21) +,(90, 22) +,(91, 21) +,(91, 22) +,(92, 21) +,(92, 22) +,(93, 21) +,(93, 22) +,(94, 21) +,(94, 22) +,(95, 21) +,(95, 22) +,(96, 21) +,(96, 22) +,(97, 21) +,(97, 22) +,(98, 21) +,(98, 22) +,(99, 21) +,(99, 22) +,(100, 21) +,(100, 22) +,(101, 21) +,(101, 22) +,(102, 21) +,(102, 22) +,(103, 21) +,(103, 22) +,(104, 21) +,(104, 22) +,(105, 21) +,(105, 22) +,(106, 21) +,(106, 22) +,(107, 21) +,(107, 22) +,(108, 21) +,(108, 22) +,(109, 21) +,(109, 22) +,(110, 21) +,(110, 22) +,(111, 21) +,(111, 22) +,(112, 21) +,(112, 22) +,(113, 21) +,(113, 22) +,(114, 21) +,(114, 22) +,(115, 21) +,(115, 22) +,(116, 21) +,(116, 22) +,(117, 21) +,(117, 22) +,(118, 21) +,(118, 22) +,(119, 21) +,(119, 22) +,(120, 21) +,(120, 22) +,(121, 21) +,(121, 22) +,(122, 21) +,(122, 22) +,(123, 21) +,(123, 22) +,(124, 21) +,(124, 22) +,(125, 21) +,(125, 22) +,(126, 21) +,(126, 22) +,(127, 21) +,(127, 22) +,(128, 21) +,(128, 22) +,(129, 21) +,(129, 22) +,(130, 21) +,(130, 22) +,(131, 21) +,(131, 22) +,(132, 21) +,(132, 22) +,(133, 21) +,(133, 22) +,(134, 21) +,(134, 22) +,(135, 21) +,(135, 22) +,(136, 21) +,(136, 22) +,(137, 21) +,(137, 22) +,(138, 21) +,(138, 22) +,(139, 21) +,(139, 22) +,(140, 21) +,(140, 22) +,(141, 21) +,(141, 22) +,(142, 21) +,(142, 22) +,(143, 21) +,(143, 22) +,(144, 21) +,(144, 22) +,(145, 21) +,(145, 22) +,(146, 21) +,(146, 22) +,(147, 21) +,(147, 22) +,(148, 21) +,(148, 22) +,(149, 21) +,(149, 22) +,(150, 21) +,(150, 22) +,(151, 21) +,(151, 22) +,(152, 21) +,(152, 22) +,(153, 21) +,(153, 22) +,(154, 21) +,(154, 22) +,(155, 21) +,(155, 22) +,(156, 21) +,(156, 22) +,(157, 21) +,(157, 22) +,(158, 21) +,(158, 22) +,(159, 21) +,(159, 22) +,(160, 21) +,(160, 22) +,(161, 21) +,(161, 22) +,(162, 21) +,(162, 22) +,(163, 21) +,(163, 22) +,(164, 21) +,(164, 22) +,(165, 21) +,(165, 22) +,(166, 21) +,(166, 22) +,(167, 21) +,(167, 22) +,(168, 21) +,(168, 22) +,(169, 21) +,(169, 22) +,(170, 21) +,(170, 22) +,(171, 21) +,(171, 22) +,(172, 21) +,(172, 22) +,(173, 21) +,(173, 22) +,(174, 21) +,(174, 22) +,(175, 21) +,(175, 22) +,(176, 21) +,(176, 22) +,(177, 21) +,(177, 22) +,(178, 21) +,(178, 22) +,(179, 21) +,(179, 22) +,(180, 21) +,(180, 22) +,(181, 21) +,(181, 22) +,(182, 21) +,(182, 22) +,(183, 21) +,(183, 22) +,(184, 21) +,(184, 22) +,(185, 21) +,(185, 22) +,(186, 21) +,(186, 22) +,(187, 21) +,(187, 22) +,(188, 21) +,(188, 22) +,(189, 21) +,(189, 22) +,(190, 21) +,(190, 22) +,(191, 21) +,(191, 22) +,(192, 21) +,(192, 22) +,(193, 21) +,(193, 22) +,(194, 21) +,(194, 22) +,(195, 21) +,(195, 22) +,(196, 21) +,(196, 22) +,(197, 21) +,(197, 22) +,(198, 21) +,(198, 22) +,(199, 21) +,(199, 22) +,(200, 21) +,(200, 22) +,(201, 21) +,(201, 22) +,(202, 21) +,(202, 22) +,(203, 21) +,(203, 22) +,(204, 21) +,(204, 22) +,(205, 21) +,(205, 22) +,(206, 21) +,(206, 22) +,(207, 21) +,(207, 22) +,(208, 21) +,(208, 22) +,(209, 21) +,(209, 22) +,(210, 21) +,(210, 22) +,(211, 21) +,(211, 22) +,(212, 21) +,(212, 22) +,(213, 21) +,(213, 22) +,(214, 21) +,(214, 22) +,(215, 21) +,(215, 22) +,(216, 21) +,(216, 22) +,(217, 21) +,(217, 22) +,(218, 21) +,(218, 22) +,(219, 21) +,(219, 22) +,(220, 21) +,(220, 22) +,(221, 21) +,(221, 22) +,(222, 21) +,(222, 22) +,(223, 21) +,(223, 22) +,(224, 21) +,(224, 22) +,(225, 21) +,(225, 22) +,(226, 21) +,(226, 22) +,(227, 21) +,(227, 22) +,(228, 21) +,(228, 22) +,(229, 21) +,(229, 22) +,(230, 21) +,(230, 22) +,(231, 21) +,(231, 22) +,(232, 21) +,(232, 22) +,(233, 21) +,(233, 22) +,(234, 21) +,(234, 22) +,(235, 21) +,(235, 22) +,(236, 21) +,(236, 22) +,(237, 21) +,(237, 22) +,(238, 21) +,(238, 22) +,(239, 21) +,(239, 22) +,(240, 21) +,(240, 22) +,(241, 21) +,(241, 22) +,(242, 21) +,(242, 22) +,(243, 21) +,(243, 22) +,(244, 21) +,(244, 22) +,(245, 21) +,(245, 22) +,(246, 21) +,(246, 22) +,(247, 21) +,(247, 22) +,(248, 21) +,(248, 22) +,(249, 21) +,(249, 22) +,(250, 21) +,(250, 22) +,(251, 21) +,(251, 22) +,(252, 21) +,(252, 22) +,(253, 21) +,(253, 22) +,(254, 21) +,(254, 22) +,(255, 21) +,(255, 22) +,(256, 21) +,(256, 22) +,(257, 21) +,(257, 22) +,(258, 21) +,(258, 22) +,(259, 21) +,(259, 22) +,(260, 21) +,(260, 22) +,(261, 21) +,(261, 22) +,(262, 21) +,(262, 22) +,(263, 21) +,(263, 22) +,(264, 21) +,(264, 22) +,(265, 21) +,(265, 22) +,(266, 21) +,(266, 22) +,(267, 21) +,(267, 22) +,(268, 21) +,(268, 22) +,(269, 21) +,(269, 22) +,(270, 21) +,(270, 22) +,(271, 21) +,(271, 22) +,(272, 21) +,(272, 22) +,(273, 21) +,(273, 22) +,(274, 21) +,(274, 22) +,(275, 21) +,(275, 22) +,(276, 21) +,(276, 22) +,(277, 21) +,(277, 22) +,(278, 21) +,(278, 22) +,(279, 21) +,(279, 22) +,(280, 21) +,(280, 22) +,(281, 21) +,(281, 22) +,(282, 21) +,(282, 22) +,(283, 21) +,(283, 22) +,(284, 21) +,(284, 22) +,(285, 21) +,(285, 22) +,(286, 21) +,(286, 22) +,(287, 21) +,(287, 22) +,(288, 21) +,(288, 22) +,(289, 21) +,(289, 22) +,(290, 21) +,(290, 22) +,(291, 21) +,(291, 22) +,(292, 21) +,(292, 22) +,(293, 21) +,(293, 22) +,(294, 21) +,(294, 22) +,(295, 21) +,(295, 22) +,(296, 21) +,(296, 22) +,(297, 21) +,(297, 22) +,(298, 21) +,(298, 22) +,(299, 21) +,(299, 22) +,(300, 21) +,(300, 22) +,(301, 21) +,(301, 22) +,(302, 21) +,(302, 22) +,(303, 21) +,(303, 22) +,(304, 21) +,(304, 22) +,(305, 21) +,(305, 22) +,(306, 21) +,(306, 22) +,(307, 21) +,(307, 22) +,(308, 21) +,(308, 22) +,(309, 21) +,(309, 22) +,(310, 21) +,(310, 22) +,(311, 21) +,(311, 22) +,(312, 21) +,(312, 22) +,(313, 21) +,(313, 22) +,(314, 21) +,(314, 22) +,(315, 21) +,(315, 22) +,(316, 21) +,(316, 22) +,(317, 21) +,(317, 22) +,(318, 21) +,(318, 22) +,(319, 21) +,(319, 22) +,(320, 21) +,(320, 22) +,(321, 21) +,(321, 22) +,(322, 21) +,(322, 22) +,(323, 21) +,(323, 22) +,(324, 21) +,(324, 22) +,(325, 21) +,(325, 22) +,(326, 21) +,(326, 22) +,(327, 21) +,(327, 22) +,(328, 21) +,(328, 22) +,(329, 21) +,(329, 22) +,(330, 21) +,(330, 22) +,(331, 21) +,(331, 22) +,(332, 21) +,(332, 22) +,(333, 21) +,(333, 22) +,(334, 21) +,(334, 22) +,(335, 21) +,(335, 22) +,(336, 21) +,(336, 22) +,(337, 21) +,(337, 22) +,(338, 21) +,(338, 22) +,(339, 21) +,(339, 22) +,(340, 21) +,(340, 22) +,(341, 21) +,(341, 22) +,(342, 21) +,(342, 22) +,(343, 21) +,(343, 22) +,(344, 21) +,(344, 22) +,(345, 21) +,(345, 22) +,(346, 21) +,(346, 22) +,(347, 21) +,(347, 22) +,(348, 21) +,(348, 22) +,(349, 21) +,(349, 22) +,(350, 21) +,(350, 22) +,(351, 21) +,(351, 22) +,(352, 21) +,(352, 22) +,(353, 21) +,(353, 22) +,(354, 21) +,(354, 22) +,(355, 21) +,(355, 22) +,(356, 21) +,(356, 22) +,(357, 21) +,(357, 22) +,(358, 21) +,(358, 22) +,(359, 21) +,(359, 22) +,(360, 21) +,(360, 22) +,(361, 21) +,(361, 22) +,(362, 21) +,(362, 22) +,(363, 21) +,(363, 22) +,(364, 21) +,(364, 22) +,(365, 21) +,(365, 22) +,(366, 21) +,(366, 22) +,(367, 21) +,(367, 22) +,(368, 21) +,(368, 22) +,(369, 21) +,(369, 22) +,(370, 21) +,(370, 22) +,(371, 21) +,(371, 22) +,(372, 21) +,(372, 22) +,(373, 21) +,(373, 22) +,(374, 21) +,(374, 22) +,(375, 21) +,(375, 22) +,(376, 21) +,(376, 22) +,(377, 21) +,(377, 22) +,(378, 21) +,(378, 22) +,(379, 21) +,(379, 22) +,(380, 21) +,(380, 22) +,(381, 21) +,(381, 22) +,(382, 21) +,(382, 22) +,(383, 21) +,(383, 22) +,(384, 21) +,(384, 22) +,(385, 21) +,(385, 22) +,(386, 21) +,(386, 22) +,(387, 21) +,(387, 22) +,(388, 21) +,(388, 22) +,(389, 21) +,(389, 22) +,(390, 21) +,(390, 22) +,(391, 21) +,(391, 22) +,(392, 21) +,(392, 22) +,(393, 21) +,(393, 22) +,(394, 21) +,(394, 22) +,(395, 21) +,(395, 22) +,(396, 21) +,(396, 22) +,(397, 21) +,(397, 22) +,(398, 21) +,(398, 22) +,(399, 21) +,(399, 22) +,(400, 21) +,(400, 22) +,(401, 21) +,(401, 22) +,(402, 21) +,(402, 22) +,(403, 21) +,(403, 22) +,(404, 21) +,(404, 22) +,(405, 21) +,(405, 22) +,(406, 21) +,(406, 22) +,(407, 21) +,(407, 22) +,(408, 21) +,(408, 22) +,(409, 21) +,(409, 22) +,(410, 21) +,(410, 22) +,(411, 21) +,(411, 22) +,(412, 21) +,(412, 22) +,(413, 21) +,(413, 22) +,(414, 21) +,(414, 22) +,(415, 21) +,(415, 22) +,(416, 21) +,(416, 22) +,(417, 21) +,(417, 22) +,(418, 21) +,(418, 22) +,(419, 21) +,(419, 22) +,(420, 21) +,(420, 22) +,(421, 21) +,(421, 22) +,(422, 21) +,(422, 22) +,(423, 21) +,(423, 22) +,(424, 21) +,(424, 22) +,(425, 21) +,(425, 22) +,(426, 21) +,(426, 22) +,(427, 21) +,(427, 22) +,(428, 21) +,(428, 22) +,(429, 21) +,(429, 22) +,(430, 21) +,(430, 22) +,(431, 21) +,(431, 22) +,(432, 21) +,(432, 22) +,(433, 21) +,(433, 22) +,(434, 21) +,(434, 22) +,(435, 21) +,(435, 22) +,(436, 21) +,(436, 22) +,(437, 21) +,(437, 22) +,(438, 21) +,(438, 22) +,(439, 21) +,(439, 22) +,(440, 21) +,(440, 22) +,(441, 21) +,(441, 22) +,(442, 21) +,(442, 22) +,(443, 21) +,(443, 22) +,(444, 21) +,(444, 22) +,(445, 21) +,(445, 22) +,(446, 21) +,(446, 22) +,(447, 21) +,(447, 22) +,(448, 21) +,(448, 22) +,(449, 21) +,(449, 22) +,(450, 21) +,(450, 22) +,(451, 21) +,(451, 22) +,(452, 21) +,(452, 22) +,(453, 21) +,(453, 22) +,(454, 21) +,(454, 22) +,(455, 21) +,(455, 22) +,(456, 21) +,(456, 22) +,(457, 21) +,(457, 22) +,(458, 21) +,(458, 22) +,(459, 21) +,(459, 22) +,(460, 21) +,(460, 22) +,(461, 21) +,(461, 22) +,(462, 21) +,(462, 22) +,(463, 21) +,(463, 22) +,(464, 21) +,(464, 22) +,(465, 21) +,(465, 22) +,(466, 21) +,(466, 22) +,(467, 21) +,(467, 22) +,(468, 21) +,(468, 22) +,(469, 21) +,(469, 22) +,(470, 21) +,(470, 22) +,(471, 21) +,(471, 22) +; + + +--comment: event +INSERT INTO event (id, name, nameCht, description, region, organization, eventType, eventFrom, eventTo, startDate, series, awardDate, applicationDeadline, announcementDate, frequency, nextApplicationDate, reminderFlag, reminderThreshold, reminderInterval, reminderLimit) VALUES +(1, "19th Hong Kong Occupational Safety and Health Award", null, null, "Local", "Occupational Safety and Health Council", "Competition", null, null, "2021-03-21", 19, "2021-03-21", "2021-03-21", "2021-03-21", null, null, false, 0, 0, 0 ), +(2, "22nd Considerate Contractors Site Award", "第22屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2016-05-27", 22, "2016-05-27", "2016-05-27", "2016-05-27", null, null, false, 0, 0, 0 ), +(3, "23rd Considerate Contractors Site Award", "第23屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2017-09-01", 23, "2017-09-01", "2017-09-01", "2017-09-01", null, null, false, 0, 0, 0 ), +(4, "24th Considerate Contractors Site Award", "第24屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2018-05-01", 24, "2018-05-01", "2018-05-01", "2018-05-01", null, null, false, 0, 0, 0 ), +(5, "25th Considerate Contractors Site Award", "第25屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2019-05-01", 25, "2019-05-01", "2019-05-01", "2019-05-01", null, null, false, 0, 0, 0 ), +(6, "26th Considerate Contractors Site Award", "第26屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2020-12-01", 26, "2020-12-01", "2020-12-01", "2020-12-01", null, null, false, 0, 0, 0 ), +(7, "27th Considerate Contractors Site Award", "第27屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2021-08-06", 27, "2021-08-06", "2021-08-06", "2021-08-06", null, null, false, 0, 0, 0 ), +(8, "28th Considerate Contractors Site Award", "第28屆公德地盤嘉許計劃", null, "Local", "Development Bureau and Construction Industry Council", "Event", null, null, "2022-09-01", 28, "2022-09-01", "2022-09-01", "2022-09-01", null, null, false, 0, 0, 0 ), +(9, "30th HKQAA Anniversary Recognition Programme", null, null, "Local", "Hong Kong Quality Assurance Agency (HKQAA)", "Event", null, null, "2019-10-01", 30, "2019-10-01", "2019-10-01", "2019-10-01", null, null, false, 0, 0, 0 ), +(10, "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG", "第三屆亞洲創新發明展覽會 - 香港", null, "Local", "Geneva inventions", "Competition", "2023-12-07", "2023-12-08", "2023-09-01", 3, "2023-12-08", "2023-12-08", "2023-12-08", null, null, false, 0, 0, 0 ), +(11, "44th WorldSkills Competition", null, null, "Oversea", "WorldSkills", "Competition", null, null, "2017-10-01", 44, "2017-10-01", "2017-10-01", "2017-10-01", null, null, false, 0, 0, 0 ), +(12, "45th WorldSkills Competition", null, null, "Oversea", "WorldSkills", "Competition", null, null, "2019-08-01", 45, "2019-08-01", "2019-08-01", "2019-08-01", null, null, false, 0, 0, 0 ), +(13, "47th International Exhibition of Inventions of Geneva", "第47屆日內瓦國際發明展", null, "Oversea", "Geneva inventions", "Competition", null, null, "2018-10-01", 47, "2019-04-01", "2019-04-01", "2019-04-01", null, null, false, 0, 0, 0 ), +(14, "48th International Exhibition of Inventions of Geneva", "第48屆日內瓦國際發明展", null, "Oversea", "Geneva inventions", "Competition", null, null, "2022-10-01", 48, "2023-03-01", "2023-03-01", "2023-03-01", null, null, false, 0, 0, 0 ), +(15, "2022 Special Edition International Exhibition of Inventions Geneva", "2022 日內瓦國際發明展", null, "Oversea", "Geneva inventions", "Competition", null, null, "2021-10-01", null, "2022-03-01", "2022-03-01", "2022-03-01", null, null, false, 0, 0, 0 ), +(16, "9th Outstanding Occupational Safety and Health (OSH) Employees Award", null, null, "Local", "Occupational Safety and Health Council", "Event", null, null, "2017-02-01", 9, "2017-02-01", "2017-02-01", "2017-02-01", null, null, false, 0, 0, 0 ), +(17, "AEE Regional Award 2017", null, null, "Oversea", "Association of Energy Engineers (AEE)", "Event", null, null, "2017-09-01", null, "2017-09-01", "2017-09-01", "2017-09-01", null, null, false, 0, 0, 0 ), +(18, "AEE Regional Award 2018", null, null, "Oversea", "Association of Energy Engineers (AEE)", "Event", null, null, "2018-10-01", null, "2018-10-01", "2018-10-01", "2018-10-01", null, null, false, 0, 0, 0 ), +(19, "AEE Regional Award 2021", null, null, "Oversea", "Association of Energy Engineers (AEE)", "Event", null, null, "2021-10-19", null, "2021-10-19", "2021-10-19", "2021-10-19", null, null, false, 0, 0, 0 ), +(20, "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2016/17", null, null, "Local", "Airport Authority Hong Kong (AAHK)", "Competition", null, null, "2017-03-01", null, "2017-03-01", "2017-03-01", "2017-03-01", null, null, false, 0, 0, 0 ), +(21, "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18", null, null, "Local", "Airport Authority Hong Kong (AAHK)", "Competition", null, null, "2019-05-01", null, "2019-05-01", "2019-05-01", "2019-05-01", null, null, false, 0, 0, 0 ), +(22, "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2018/19", null, null, "Local", "Airport Authority Hong Kong (AAHK)", "Competition", null, null, "2019-05-01", null, "2019-05-01", "2019-05-01", "2019-05-01", null, null, false, 0, 0, 0 ), +(23, "Airport Safety Recognition Scheme 2015/16", null, null, "Local", "Airport Authority Hong Kong (AAHK)", "Event", null, null, "2016-04-01", null, "2016-04-01", "2016-04-01", "2016-04-01", null, null, false, 0, 0, 0 ), +(24, "Airport Safety Recognition Scheme 2016/17", null, null, "Local", "Airport Authority Hong Kong (AAHK)", "Event", null, null, "2017-03-01", null, "2017-03-01", "2017-03-01", "2017-03-01", null, null, false, 0, 0, 0 ), +(25, "Airport Safety Recognition Scheme 2017/18", null, null, "Local", "Airport Authority Hong Kong (AAHK)", "Event", null, null, "2018-04-01", null, "2018-04-01", "2018-04-01", "2018-04-01", null, null, false, 0, 0, 0 ), +(26, "Airport Safety Recognition Scheme 2021/22", "2021/22機場安全嘉許計劃", null, "Local", "Airport Authority Hong Kong (AAHK)", "Event", null, null, "2022-05-01", null, "2022-05-01", "2022-05-01", "2022-05-01", null, null, false, 0, 0, 0 ), +(27, "ASHRAE Technology Award 2021", null, null, "Local", "ASHRAE", "Competition", null, null, "2021-06-01", null, "2021-06-01", "2021-06-01", "2021-06-01", null, null, false, 0, 0, 0 ), +(28, "Autodesk Hong Kong BIM Awards 2019", null, null, "Local", "AUTODESK HONG KONG", "Competition", null, null, "2020-01-01", null, "2020-01-01", "2020-01-01", "2020-01-01", null, null, false, 0, 0, 0 ), +(29, "Autodesk Hong Kong BIM Awards 2022", null, null, "Local", "AUTODESK HONG KONG", "Competition", null, null, "2022-09-01", null, "2022-09-01", "2022-09-01", "2022-09-01", null, null, false, 0, 0, 0 ), +(30, "Award for Excellence in Training and Development 2021", "2021年最佳管理培訓及發展獎", null, "Local", "Hong Kong Management Association (HKMA)", "Competition", null, null, "2021-10-01", null, "2021-10-01", "2021-10-01", "2021-10-01", null, null, false, 0, 0, 0 ), +(31, "BEAM Plus Certificate 2022", null, null, "Local", "HKGBC", "Event", null, null, "2022-01-01", null, "2022-01-01", "2022-01-01", "2022-01-01", null, null, false, 0, 0, 0 ), +(32, "Best.hk Website Awards 2015", null, null, "Local", "Leisure and Cultural Services Department", "Competition", null, null, "2016-04-01", null, "2016-04-01", "2016-04-01", "2016-04-01", null, null, false, 0, 0, 0 ), +(33, "BIM Achievement 2020", null, null, "Local", "CIC", "Competition", null, null, "2020-11-01", null, "2020-11-01", "2020-11-01", "2020-11-01", null, null, false, 0, 0, 0 ), +(34, "BIM Achievement 2022", "香港建築信息模擬大獎 2022", null, "Local", "CIC", "Competition", null, null, "2022-09-01", null, "2022-09-01", "2022-09-01", "2022-09-01", null, null, false, 0, 0, 0 ), +(35, "Caring Company", null, null, "Local", "Hong Kong Council of Social +Service", "Event", null, null, "2015-01-01", null, "2015-01-01", "2015-01-01", "2015-01-01", null, null, false, 0, 0, 0 ), +(36, "Chief Executive's Commendation for Government/Public Service 2021", "2021年度勳銜頒授典禮", null, "Local", "HKSAR", "Event", null, null, "2021-07-01", null, "2021-07-01", "2021-07-01", "2021-07-01", null, null, false, 0, 0, 0 ), +(37, "Chief Executive's Commendation for Government/Public Service 2022", "2022年度勳銜頒授典禮", null, "Local", "HKSAR", "Event", null, null, "2022-11-07", null, "2022-11-07", "2022-11-07", "2022-11-07", null, null, false, 0, 0, 0 ), +(38, "CIBSE Hong Kong Awards 2021", null, null, "Local", "The Chartered Institution of Building Services Engineers (CIBSE)", "Competition", null, null, "2021-10-12", null, "2021-10-12", "2021-10-12", "2021-10-12", null, null, false, 0, 0, 0 ), +(39, "CIC Construction Digitalisation Award 2021", "建造業議會數碼化大獎 2021", null, "Local", "CIC", "Competition", null, null, "2021-11-01", null, "2021-11-01", "2021-11-01", "2021-11-01", null, null, false, 0, 0, 0 ), +(40, "CIC Sustainable Construction Award 2018", null, null, "Local", "CIC", "Competition", null, null, "2018-10-01", null, "2018-10-01", "2018-10-01", "2018-10-01", null, null, false, 0, 0, 0 ), +(41, "CIC's Life First Safety Promotional Campaign", null, null, "Local", "CIC", "Event", null, null, "2021-08-01", null, "2021-08-01", "2021-08-01", "2021-08-01", null, null, false, 0, 0, 0 ), +(42, "City I&T Grand Challenge 2021", null, null, "Local", "HKSTP", "Competition", null, null, "2021-10-16", null, "2021-10-16", "2021-10-16", "2021-10-16", null, null, false, 0, 0, 0 ), +(43, "Civil Service Outstanding Service Award Scheme 2017", "2017年公務員優質服務獎勵計劃", null, "Local", "Civil Service Bureau", "Event", null, null, "2017-09-07", null, "2017-09-07", "2017-09-07", "2017-09-07", null, null, false, 0, 0, 0 ), +(44, "Civil Service Outstanding Service Award Scheme 2019", "2019年公務員優質服務獎勵計劃", null, "Local", "Civil Service Bureau", "Event", null, null, "2019-12-01", null, "2019-12-01", "2019-12-01", "2019-12-01", null, null, false, 0, 0, 0 ), +(45, "Civil Service Outstanding Service Award Scheme 2022", "2022年公務員優質服務獎勵計劃", null, "Local", "Civil Service Bureau", "Event", null, null, "2022-12-12", null, "2022-12-12", "2022-12-12", "2022-12-12", null, null, false, 0, 0, 0 ), +(46, "Digital Award 2018", null, null, "Oversea", "The Chartered Institution of Building Services Engineers (CIBSE)", "Competition", null, null, "2019-05-01", null, "2019-05-01", "2019-05-01", "2019-05-01", null, null, false, 0, 0, 0 ), +(47, "ERB Manpower Developer Award Scheme", null, null, "Local", "Employees Retraining Board (ERB)", "Event", null, null, "2020-04-01", null, "2020-04-01", "2020-04-01", "2020-04-01", null, null, false, 0, 0, 0 ), +(48, "Global Skills Challenge 2019", null, null, "Oversea", "WorldSkills Australia", "Competition", null, null, "2019-04-01", null, "2019-04-01", "2019-04-01", "2019-04-01", null, null, false, 0, 0, 0 ), +(49, "Guangzhou Invitation Tournament", null, null, "Local", "WorldSkills", "Competition", null, null, "2019-04-01", null, "2019-04-01", "2019-04-01", "2019-04-01", null, null, false, 0, 0, 0 ), +(50, "HKIE Innovation Award 2016", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Competition", null, null, "2016-02-01", null, "2016-02-01", "2016-02-01", "2016-02-01", null, null, false, 0, 0, 0 ), +(51, "HKIE Innovation Award 2017", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Competition", null, null, "2017-03-22", null, "2017-03-22", "2017-03-22", "2017-03-22", null, null, false, 0, 0, 0 ), +(52, "HKIE Innovation Award 2019", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Competition", null, null, "2019-03-01", null, "2019-03-01", "2019-03-01", "2019-03-01", null, null, false, 0, 0, 0 ), +(53, "HKIE Innovation Award 2020", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Competition", null, null, "2020-04-01", null, "2020-04-01", "2020-04-01", "2020-04-01", null, null, false, 0, 0, 0 ), +(54, "HKIE Innovation Award 2021", "2021香港工程師學會創意獎", null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Competition", null, null, "2021-07-01", null, "2021-07-01", "2021-07-01", "2021-07-01", null, null, false, 0, 0, 0 ), +(55, "HKIE Innovation Award 2022", "2022香港工程師學會創意獎", null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Competition", null, null, "2022-06-11", null, "2022-06-11", "2022-06-11", "2022-06-11", null, null, false, 0, 0, 0 ), +(56, "HKIE Trainee of the Year Award 2016", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Event", null, null, "2017-01-01", null, "2017-01-01", "2017-01-01", "2017-01-01", null, null, false, 0, 0, 0 ), +(57, "HKIE Trainee of the Year Award 2019", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Event", null, null, "2020-01-01", null, "2020-01-01", "2020-01-01", "2020-01-01", null, null, false, 0, 0, 0 ), +(58, "HKIE Young Engineer of the Year Award 2017", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Event", null, null, "2017-03-22", null, "2017-03-22", "2017-03-22", "2017-03-22", null, null, false, 0, 0, 0 ), +(59, "HKIE Young Engineer of the Year Award 2019", null, null, "Local", "The Hong Kong Institution of Engineers (HKIE)", "Event", null, null, "2019-03-01", null, "2019-03-01", "2019-03-01", "2019-03-01", null, null, false, 0, 0, 0 ), +(60, "HKIHRM HR Excellence Award", null, null, "Local", "HKIHRM", "Competition", null, null, "2022-09-01", null, "2022-09-01", "2022-09-01", "2022-09-01", null, null, false, 0, 0, 0 ), +(61, "HKMA Best Annual Reports Awards 2022", "2022香港管理專業協會最佳年報大獎", null, "Local", "Hong Kong Management Association (HKMA)", "Competition", null, null, "2022-10-19", null, "2022-10-19", "2022-10-19", "2022-10-19", null, null, false, 0, 0, 0 ), +(62, "Hong Kong Electronics Project Competition 2016", "香港電子項目比賽2016年", null, "Local", "HKIE Electronics Division", "Competition", null, null, "2016-03-29", null, "2016-03-29", "2016-03-29", "2016-03-29", null, null, false, 0, 0, 0 ), +(63, "Hong Kong Electronics Project Competition 2022", "香港電子項目比賽2022年", null, "Local", "HKIE Electronics Division", "Competition", null, null, "2022-02-01", null, "2022-07-01", "2022-07-01", "2022-07-01", null, null, false, 0, 0, 0 ), +(64, "Hong Kong Most Innovative Knowledge Enterprise (MIKE) Award 2022", null, null, "Local", "The Behaviour and Knowledge Engineering Research Centre of The Hong Kong Polytechnic University", "Competition", null, null, "2022-10-01", null, "2022-10-01", "2022-10-01", "2022-10-01", null, null, false, 0, 0, 0 ), +(65, "ICT Awards 2007", null, null, "Local", "Office of the Government Chief Information Officer (OGCIO)", "Competition", null, null, "2007-11-01", null, "2007-11-01", "2007-11-01", "2007-11-01", null, null, false, 0, 0, 0 ), +(66, "ICT Awards 2011", null, null, "Local", "Office of the Government Chief Information Officer (OGCIO)", "Competition", null, null, "2011-11-01", null, "2011-11-01", "2011-11-01", "2011-11-01", null, null, false, 0, 0, 0 ), +(67, "ICT Awards 2014", null, null, "Local", "Office of the Government Chief Information Officer (OGCIO)", "Competition", null, null, "2014-11-01", null, "2014-11-01", "2014-11-01", "2014-11-01", null, null, false, 0, 0, 0 ), +(68, "ICT Awards 2020", null, null, "Local", "Office of the Government Chief Information Officer (OGCIO)", "Competition", null, null, "2020-11-01", null, "2020-11-01", "2020-11-01", "2020-11-01", null, null, false, 0, 0, 0 ), +(69, "ICT Awards 2021", "2021香港資訊及通訊科技獎", null, "Local", "Office of the Government Chief Information Officer (OGCIO)", "Competition", null, null, "2021-11-29", null, "2021-11-29", "2021-11-29", "2021-11-29", null, null, false, 0, 0, 0 ), +(70, "IET HK Young Professionals Exhibition and Competition", null, null, "Local", "The Institution of Engineering and Technology (IET)", "Competition", null, null, "2022-09-01", null, "2022-09-01", "2022-09-01", "2022-09-01", null, null, false, 0, 0, 0 ), +(71, "International Annual Report Competition (ARC) 2019", null, null, "Oversea", "International Annual Report Competition (ARC)", "Competition", null, null, "2019-08-01", 33, "2019-08-01", "2019-08-01", "2019-08-01", null, null, false, 0, 0, 0 ), +(72, "International Annual Report Competition (ARC) 2020", null, null, "Oversea", "International Annual Report Competition (ARC)", "Competition", null, null, "2020-09-01", 34, "2020-09-01", "2020-09-01", "2020-09-01", null, null, false, 0, 0, 0 ), +(73, "International Annual Report Competition (ARC) 2021", null, null, "Oversea", "International Annual Report Competition (ARC)", "Competition", null, null, "2021-12-01", 35, "2021-12-01", "2021-12-01", "2021-12-01", null, null, false, 0, 0, 0 ), +(74, "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021", null, null, "Local", "Office of the Government Chief Information Officer (OGCIO)", "Competition", null, null, "2021-07-28", null, "2021-07-28", "2021-07-28", "2021-07-28", null, null, false, 0, 0, 0 ), +(75, "League of American Communications Professionals (LACP) 2016 Vision Awards", null, null, "Oversea", "League of American Communications Professionals (LACP)", "Competition", null, null, "2017-07-01", null, "2017-07-01", "2017-07-01", "2017-07-01", null, null, false, 0, 0, 0 ), +(76, "League of American Communications Professionals (LACP) 2019 Vision Awards", null, null, "Oversea", "League of American Communications Professionals (LACP)", "Competition", null, null, "2020-08-01", null, "2020-08-01", "2020-08-01", "2020-08-01", null, null, false, 0, 0, 0 ), +(77, "League of American Communications Professionals (LACP) 2021 Vision Awards", null, null, "Oversea", "League of American Communications Professionals (LACP)", "Competition", null, null, "2021-12-01", null, "2021-12-01", "2021-12-01", "2021-12-01", null, null, false, 0, 0, 0 ), +(78, "Learning Enterprise Award 2019", null, null, "Local", "Professional Validation Council of Hong Kong Industries", "Event", null, null, "2019-12-01", null, "2019-12-01", "2019-12-01", "2019-12-01", null, null, false, 0, 0, 0 ), +(79, "Outstanding Staff & Teams and Young Achievers Award 2019", null, null, "Local", "Hospital Authority", "Event", null, null, "2019-05-01", null, "2019-05-01", "2019-05-01", "2019-05-01", null, null, false, 0, 0, 0 ), +(80, "Safety Quiz 2016", null, null, "Local", "Occupational Safety and Health Council and Labour Department", "Competition", null, null, "2016-09-01", null, "2016-09-01", "2016-09-01", "2016-09-01", null, null, false, 0, 0, 0 ), +(81, "Secretary for the Civil Service's Commendation Award 2016", "公務員事務局局長嘉許狀 2016", null, "Local", "Civil Service Bureau", "Event", null, null, "2016-11-01", null, "2016-11-01", "2016-11-01", "2016-11-01", null, null, false, 0, 0, 0 ), +(82, "Secretary for the Civil Service's Commendation Award 2017", "公務員事務局局長嘉許狀 2017", null, "Local", "Civil Service Bureau", "Event", null, null, "2017-11-01", null, "2017-11-01", "2017-11-01", "2017-11-01", null, null, false, 0, 0, 0 ), +(83, "Secretary for the Civil Service's Commendation Award 2018", "公務員事務局局長嘉許狀 2018", null, "Local", "Civil Service Bureau", "Event", null, null, "2018-10-01", null, "2018-10-01", "2018-10-01", "2018-10-01", null, null, false, 0, 0, 0 ), +(84, "Secretary for the Civil Service's Commendation Award 2019", "公務員事務局局長嘉許狀 2019", null, "Local", "Civil Service Bureau", "Event", null, null, "2019-11-01", null, "2019-11-01", "2019-11-01", "2019-11-01", null, null, false, 0, 0, 0 ), +(85, "Secretary for the Civil Service's Commendation Award 2020", "公務員事務局局長嘉許狀 2020", null, "Local", "Civil Service Bureau", "Event", null, null, "2021-11-01", null, "2021-11-01", "2021-11-01", "2021-11-01", null, null, false, 0, 0, 0 ), +(86, "Secretary for the Civil Service's Commendation Award 2022", "公務員事務局局長嘉許狀 2022", null, "Local", "Civil Service Bureau", "Event", null, null, "2022-09-05", null, "2022-09-05", "2022-09-05", "2022-09-05", null, null, false, 0, 0, 0 ), +(87, "Special Edition 2021 Inventions Geneva", null, null, "Oversea", "Geneva inventions", "Competition", null, null, "2021-03-01", null, "2021-03-01", "2021-03-01", "2021-03-01", null, null, false, 0, 0, 0 ), +(88, "The 11th Guangzhou/Hong Kong/Macao/Chengdu (GHMC) Youth Skills Competition", "2021(第十一屆)穗港澳蓉青年技能競賽", null, "Local", "WorldSkills Hong Kong", "Competition", null, null, "2021-12-01", 11, "2021-12-01", "2021-12-01", "2021-12-01", null, null, false, 0, 0, 0 ), +(89, "The 5th International BIM Awards", null, null, "Local", "buildingSMART Hong Kong", "Competition", null, null, "2019-12-01", 5, "2019-12-01", "2019-12-01", "2019-12-01", null, null, false, 0, 0, 0 ), +(90, "The Ombudsman's Awards 2016", "2016年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2016-10-27", null, "2016-10-27", "2016-10-27", "2016-10-27", null, null, false, 0, 0, 0 ), +(91, "The Ombudsman's Awards 2017", "2017年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2017-10-01", null, "2017-10-01", "2017-10-01", "2017-10-01", null, null, false, 0, 0, 0 ), +(92, "The Ombudsman's Awards 2018", "2018年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2018-11-01", null, "2018-11-01", "2018-11-01", "2018-11-01", null, null, false, 0, 0, 0 ), +(93, "The Ombudsman's Awards 2019", "2019年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2019-11-01", null, "2019-11-01", "2019-11-01", "2019-11-01", null, null, false, 0, 0, 0 ), +(94, "The Ombudsman's Awards 2020", "2020年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2020-12-01", null, "2020-12-01", "2020-12-01", "2020-12-01", null, null, false, 0, 0, 0 ), +(95, "The Ombudsman's Awards 2021", "2021年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2021-11-01", null, "2021-11-01", "2021-11-01", "2021-11-01", null, null, false, 0, 0, 0 ), +(96, "The Ombudsman's Awards 2022", "2022年申訴專員嘉許獎", null, "Local", "Office of the Ombudsman", "Event", null, null, "2022-11-01", null, "2022-11-01", "2022-11-01", "2022-11-01", null, null, false, 0, 0, 0 ), +(97, "VTC Outstanding Apprentices 2015", "2015 職業訓練局傑出學徒獎勵計劃", null, "Local", "Vocational Training Council (VTC)", "Event", null, null, "2016-03-23", null, "2016-03-23", "2016-03-23", "2016-03-23", null, null, false, 0, 0, 0 ), +(98, "VTC Outstanding Apprentices 2016", "2016 職業訓練局傑出學徒獎勵計劃", null, "Local", "Vocational Training Council (VTC)", "Event", null, null, "2017-03-01", null, "2017-03-01", "2017-03-01", "2017-03-01", null, null, false, 0, 0, 0 ), +(99, "VTC Outstanding Apprentices 2017", "2017 職業訓練局傑出學徒獎勵計劃", null, "Local", "Vocational Training Council (VTC)", "Event", null, null, "2018-03-01", null, "2018-03-01", "2018-03-01", "2018-03-01", null, null, false, 0, 0, 0 ), +(100, "VTC Outstanding Apprentices 2018", "2018 職業訓練局傑出學徒獎勵計劃", null, "Local", "Vocational Training Council (VTC)", "Event", null, null, "2019-03-01", null, "2019-03-01", "2019-03-01", "2019-03-01", null, null, false, 0, 0, 0 ), +(101, "VTC Outstanding Apprentices 2019", "2019 職業訓練局傑出學徒獎勵計劃", null, "Local", "Vocational Training Council (VTC)", "Event", null, null, "2020-09-01", null, "2020-09-01", "2020-09-01", "2020-09-01", null, null, false, 0, 0, 0 ), +(102, "VTC Outstanding Apprentices 2021", "2021 職業訓練局傑出學徒獎勵計劃", null, "Local", "Vocational Training Council (VTC)", "Event", null, null, "2021-07-01", null, "2021-07-01", "2021-07-01", "2021-07-01", null, null, false, 0, 0, 0 ), +(103, "WorldSkills Competition 2022", "2022年世界技能大賽", null, "Oversea", "WorldSkills", "Competition", null, null, "2022-11-01", null, "2022-11-01", "2022-11-01", "2022-11-01", null, null, false, 0, 0, 0 ), +(104, "WorldSkills Hong Kong Competition 2016", null, null, "Local", "WorldSkills Hong Kong", "Competition", null, null, "2016-06-01", null, "2016-06-01", "2016-06-01", "2016-06-01", null, null, false, 0, 0, 0 ), +(105, "WorldSkills Hong Kong Competition 2017", null, null, "Local", "WorldSkills Hong Kong", "Competition", null, null, "2017-06-01", null, "2017-06-01", "2017-06-01", "2017-06-01", null, null, false, 0, 0, 0 ), +(106, "WorldSkills Hong Kong Competition 2019/2020", null, null, "Local", "WorldSkills Hong Kong", "Competition", null, null, "2020-12-01", null, "2020-12-01", "2020-12-01", "2020-12-01", null, null, false, 0, 0, 0 ), +(107, "Yan Oi Tong Volunteer Award 2017", null, null, "Local", "Yan Oi Tong", "Event", null, null, "2017-10-01", null, "2017-10-01", "2017-10-01", "2017-10-01", null, null, false, 0, 0, 0 ); + + +--comment: application +INSERT INTO application (id, eventId, name, description, status, responsibleOfficer) VALUES +(1, (SELECT e.id FROM event e WHERE e.name = "Hong Kong Electronics Project Competition 2022" ), "i-EMSD", null, "Submitted", "Tze Wai AU (SEBCF1)" ), +(2, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2022" ), "Mr WONG Yin Kit", null, "Submitted", "Mr WONG Yin Kit" ), +(3, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2022" ), "Mr LAU Ka Shing", null, "Submitted", "Mr LAU Ka Shing" ), +(4, (SELECT e.id FROM event e WHERE e.name = "Chief Executive's Commendation for Government/Public Service 2022" ), "CHAU Shu man", null, "Submitted", "CHAU Shu man" ), +(5, (SELECT e.id FROM event e WHERE e.name = "Chief Executive's Commendation for Government/Public Service 2022" ), "WONG Wai Kwong", null, "Submitted", "WONG Wai Kwong" ), +(6, (SELECT e.id FROM event e WHERE e.name = "28th Considerate Contractors Site Award" ), "Triennial Term Contract for the Comprehensive Maintenance and Repair of Lift Installations at Various Premises of the Government of the Hong Kong Special Administrative Region (Package One of AE20)", null, "Submitted", "Anlev Elex Elevator Limited" ), +(7, (SELECT e.id FROM event e WHERE e.name = "28th Considerate Contractors Site Award" ), "Refurbishment of Central to Mid-levels Escalator and Walkway System", null, "Submitted", "Chevalier (Envirotech) Limimted" ), +(8, (SELECT e.id FROM event e WHERE e.name = "28th Considerate Contractors Site Award" ), "Design-Build-Operate a District Cooling System (Phase 2 works) at Kai Tak Development", null, "Submitted", "Anlev Elex Elevator Limited" ), +(9, (SELECT e.id FROM event e WHERE e.name = "28th Considerate Contractors Site Award" ), "Triennial Term Contract for Provision of Maintenance, Modification and Installation Works of Electronic and E&M Control Systems at Various Sewage Treatment Works and Their Outstations for Drainage Services Department", null, "Submitted", "Hong Kong District Cooling DHY Joint Venture" ), +(10, (SELECT e.id FROM event e WHERE e.name = "Autodesk Hong Kong BIM Awards 2022" ), "The First MiMEP Pilot Project in EMSTF - Chiller Plant +Replacement at Tai Lung Veterinary Laboratory", null, "Submitted", "EMSD" ), +(11, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2022" ), "Smart Boiler Cleaning and Inspection Robot", null, "Submitted", "Mr Chow Pak Hong, Mr Ng Chin Hung" ), +(12, (SELECT e.id FROM event e WHERE e.name = "WorldSkills Competition 2022" ), "CHI Chun-chung", null, "Submitted", "CHI Chun-chung" ), +(13, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2022" ), "Mr CHOW Moon-tong", null, "Submitted", "Mr CHOW Moon-tong" ), +(14, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2022" ), "Mr WU Pak-chun, Stanley, Shift Charge Engineer", null, "Submitted", "Mr WU Pak-chun, Stanley, Shift Charge Engineer" ), +(15, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2022" ), "Mr FAN Jor-cheung, Works Supervisor I", null, "Submitted", "Mr FAN Jor-cheung, Works Supervisor I" ), +(16, (SELECT e.id FROM event e WHERE e.name = "IET HK Young Professionals Exhibition and Competition" ), "MiMEP project at Tai Lung Project", null, "Submitted", "EMSD" ), +(17, (SELECT e.id FROM event e WHERE e.name = "BIM Achievement 2022" ), "EMSD", null, "Submitted", "EMSD" ), +(18, (SELECT e.id FROM event e WHERE e.name = "HKIHRM HR Excellence Award" ), "EMSD", null, "Submitted", "EMSD" ), +(19, (SELECT e.id FROM event e WHERE e.name = "Hong Kong Most Innovative Knowledge Enterprise (MIKE) Award 2022" ), "continuous contribution and performance in KM and innovation activities", null, "Submitted", "EMSD" ), +(20, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "EMSTF/EMSD", null, "Submitted", "EMSTF/EMSD" ), +(21, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "The Digital Log-books for Lifts and Escalators", null, "Submitted", "EMSD" ), +(22, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Together We Build", null, "Submitted", "BTSD/EMSd" ), +(23, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "E&M InnoPortal", null, "Submitted", "EMSD" ), +(24, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Technological Innovation Boosting the Cemeteries and Crematoria Services", null, "Submitted", "MunSD" ), +(25, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Round-the-clock Monitoring of Community Vaccination Centres", null, "Submitted", "MunSD, HSD, GESD, BTSD/EMSD" ), +(26, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Together, We Filter out the Virus", null, "Submitted", "HSD" ), +(27, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Digitalisation Journey - E&M 2.0", null, "Submitted", "DTD" ), +(28, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Temporary Body Storage Facility near Fu Shan Public Mortuary", null, "Submitted", "EMSTF/EMSD" ), +(29, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Work Together to Fight the Epidemic", null, "Submitted", "EMSD" ), +(30, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Innovation Facilitator", null, "Submitted", "DTD" ), +(31, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Advance Inspection Booking System for Lifts and Escalators Major Alteration Works", null, "Submitted", "EMSD" ), +(32, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "E&M 100 - CHT Rescue Team", null, "Submitted", "BTSD" ), +(33, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "The Robotic Lift Examiner", null, "Submitted", "EMSD" ), +(34, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "The i-EMSD", null, "Submitted", "EMSD" ), +(35, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "The i-DEMS", null, "Submitted", "EMSD" ), +(36, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "The Smart Boiler Servicing Robot", null, "Submitted", "EMSD" ), +(37, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "The Smart Driver Assistant for Automated People Mover", null, "Submitted", "EMSD" ), +(38, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "A Contactless Inmate Health Monitoring System in Prison", null, "Submitted", "EMSD" ), +(39, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "A Drone-based LoRa Network for Location Tracking in Remote Areas", null, "Submitted", "EMSD" ), +(40, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "A Novel RFID System", null, "Submitted", "EMSD" ), +(41, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "A Novel Wireless Communication System for Underground Utility Monitoring", null, "Submitted", "EMSD" ), +(42, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "AI Voicebot Training System with Performance Evaluation for Call Centre Agents", null, "Submitted", "EMSD" ), +(43, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Artificial Intelligence Based Image Analytic and Control System for Cremation Process", null, "Submitted", "EMSD" ), +(44, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Artificial Intelligent Monitoring System for Aerial Ropeways (AIMS)", null, "Submitted", "EMSD" ), +(45, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Artificial Intelligent Robotic Cleaning and Disinfection System - i-Cleaner", null, "Submitted", "EMSD" ), +(46, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Fireman Health Monitoring System", null, "Submitted", "EMSD" ), +(47, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "IoT-enabled Mobile Modular HEPA Unit (MMHU): Together, We Fight the Virus", null, "Submitted", "EMSD" ), +(48, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Responsive Web-based Solar Irradiation Map with Solar Energy Calculator for Hong Kong", null, "Submitted", "EMSD" ), +(49, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Semantic AI for Predictive Maintenance of Railway Track Systems", null, "Submitted", "EMSD" ), +(50, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Smart Lift Passenger Flow Analysis", null, "Submitted", "EMSD" ), +(51, (SELECT e.id FROM event e WHERE e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Integrated Solar Energy Performance Management Toolkit (iSMS)", null, "Submitted", "EMSD" ), +(52, (SELECT e.id FROM event e WHERE e.name = "Airport Safety Recognition Scheme 2021/22" ), "One of our team of colleagues working alongside Airport Authority Hong Kong (AAHK) personnel at HKIA was named a Role Model in Safety Behaviour in AAHK's 2021/22 Airport Safety Recognition Scheme", null, "Submitted", "EMSD" ), +(53, (SELECT e.id FROM event e WHERE e.name = "CIC's Life First Safety Promotional Campaign" ), "Most Engaging Client award", null, "Submitted", "EMSD" ), +(54, (SELECT e.id FROM event e WHERE e.name = "Chief Executive's Commendation for Government/Public Service 2021" ), "A total 24 EMSD colleagues, including staff seconded to other works departments received this for their outstanding anti-epidemic performance.", null, "Submitted", "EMSD" ), +(55, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2020" ), "Three colleagues were awarded", null, "Submitted", "EMSD" ), +(56, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2021" ), "Two colleagues awarded for their outstanding work.", null, "Submitted", "Ms Lee Kit-chun, Cherry" ), +(57, (SELECT e.id FROM event e WHERE e.name = "19th Hong Kong Occupational Safety and Health Award" ), "The Virtual Reality-based Lift Maintenance training programme", null, "Submitted", "EMSD" ), +(58, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2021" ), "Mr. LEE King (TTII)", null, "Submitted", "Mr. LEE King (TTII)" ), +(59, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2021" ), "Mr. LAW Cheuk-ho", null, "Submitted", "Mr. LAW Cheuk-ho" ), +(60, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2021" ), "Mr. CHAN Tat Fung (I/Training 1)", null, "Submitted", "Mr. CHAN Tat Fung (I/Training 1)" ), +(61, (SELECT e.id FROM event e WHERE e.name = "International Annual Report Competition (ARC) 2021" ), "The Electrical and Mechanical Services Department Annual Report 2020/21", null, "Submitted", "EMSD" ), +(62, (SELECT e.id FROM event e WHERE e.name = "International Annual Report Competition (ARC) 2021" ), "The Electrical and Mechanical Services Department Social and Environmental Report 2020/21", null, "Submitted", "EMSD" ), +(63, (SELECT e.id FROM event e WHERE e.name = "League of American Communications Professionals (LACP) 2021 Vision Awards" ), "The Electrical and Mechanical Services Department Social and Environmental Report 2020/21", null, "Submitted", "EMSD" ), +(64, (SELECT e.id FROM event e WHERE e.name = "League of American Communications Professionals (LACP) 2021 Vision Awards" ), "The Electrical and Mechanical Services Department Annual Report 2020/21", null, "Submitted", "EMSD" ), +(65, (SELECT e.id FROM event e WHERE e.name = "HKMA Best Annual Reports Awards 2022" ), "The Electrical and Mechanical Services Department Annual Report 2020/21", null, "Submitted", "EMSD" ), +(66, (SELECT e.id FROM event e WHERE e.name = "The 11th Guangzhou/Hong Kong/Macao/Chengdu (GHMC) Youth Skills Competition" ), "Three TTs from the EMSD got the top three prizes in the Electrical Installations trade of the Hong Kong Region qualifying competition", null, "Submitted", "EMSD" ), +(67, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2021" ), "Smart Prison - Video Analysis System", null, "Submitted", "EMSD" ), +(68, (SELECT e.id FROM event e WHERE e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Robotics-enabled Public Services on Toilet Bowl Cleaning Application", null, "Submitted", "EMSD" ), +(69, (SELECT e.id FROM event e WHERE e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Application of Artificial Intelligence and Robotics Technologies for Smart Warehouse", null, "Submitted", "EMSD" ), +(70, (SELECT e.id FROM event e WHERE e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Integrated Smart Robot Assistant for Building IoT Network", null, "Submitted", "EMSD" ), +(71, (SELECT e.id FROM event e WHERE e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Robotic Cleaning and Disinfection System with Video Analytics and Robotic Arm", null, "Submitted", "EMSD" ), +(72, (SELECT e.id FROM event e WHERE e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Robotic Steam Boiler Tube Cleaning & Inspection System", null, "Submitted", "EMSD" ), +(73, (SELECT e.id FROM event e WHERE e.name = "ASHRAE Technology Award 2021" ), "Joint submission of EMSD, HKGBC and Trance", null, "Submitted", "Joint submission of EMSD, HKGBC and Trance" ), +(74, (SELECT e.id FROM event e WHERE e.name = "AEE Regional Award 2021" ), "EMSD Solar Harvest scheme", null, "Submitted", "EMSD" ), +(75, (SELECT e.id FROM event e WHERE e.name = "Award for Excellence in Training and Development 2021" ), "Technician Training Scheme (SPARK Programme)", null, "Submitted", "EMSD" ), +(76, (SELECT e.id FROM event e WHERE e.name = "Award for Excellence in Training and Development 2021" ), "EMSD, Excellence in Career Development", null, "Submitted", "EMSD" ), +(77, (SELECT e.id FROM event e WHERE e.name = "Award for Excellence in Training and Development 2021" ), "EMSD, Excellence in Future Talent Development", null, "Submitted", "EMSD" ), +(78, (SELECT e.id FROM event e WHERE e.name = "Award for Excellence in Training and Development 2021" ), "EMSD, Excellence in Future Skills Development", null, "Submitted", "EMSD" ), +(79, (SELECT e.id FROM event e WHERE e.name = "Award for Excellence in Training and Development 2021" ), "EMSD, HR Professionals' Favourite", null, "Submitted", "EMSD" ), +(80, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Cloud-based Predictive Maintenance System for Real-Time Lift Monitoring", null, "Submitted", "EMSD" ), +(81, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Artificial Intelligent Nylon Optical Fibre Sensing Escalator Combs", null, "Submitted", "EMSD" ), +(82, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Internet of things (IoT)-enabled Smart Toilet Bowl Cleaning System", null, "Submitted", "EMSD" ), +(83, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Building Semantic Artificial Intelligence: The Future of Automation in City Level - a real application in today", null, "Submitted", "EMSD" ), +(84, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Air Filter 2.0 - Energy Saving Smart Air Filter Technology", null, "Submitted", "EMSD" ), +(85, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Fibre Optic Temperature Sensing System for Predictive", null, "Submitted", "EMSD" ), +(86, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Non-Intrusive Data Analytics System for Adaptive Intelligent Condition Monitoring of Lifts", null, "Submitted", "EMSD" ), +(87, (SELECT e.id FROM event e WHERE e.name = "Special Edition 2021 Inventions Geneva" ), "Smart Prison - Video Analytic Monitoring System", null, "Submitted", "EMSD" ), +(88, (SELECT e.id FROM event e WHERE e.name = "CIBSE Hong Kong Awards 2021" ), "West Kowloon Government Offices", null, "Submitted", "EMSD" ), +(89, (SELECT e.id FROM event e WHERE e.name = "CIC Construction Digitalisation Award 2021" ), "Digitalisation Journey of Electrical and Mechanical Services Department", null, "Submitted", "EMSD, Mr. LAI Chun Fai (SE/Inno)" ), +(90, (SELECT e.id FROM event e WHERE e.name = "ICT Awards 2021" ), "Smart City Management - The Regional Digital Control Centre (RDCC) & AI Platform", null, "Submitted", "EMSD, Patrick SO (SE/GES/SD)" ), +(91, (SELECT e.id FROM event e WHERE e.name = "City I&T Grand Challenge 2021" ), "A Digitalize Smart Lift Monitoring System for Elderly Persons and Persons with Disabilities", null, "Submitted", "SVSD" ), +(92, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2020" ), "Prompt repairs of traffic light systems in Public Order Events", null, "Submitted", "Mr. Chan Kin-hong" ), +(93, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2020" ), "Complaint handling on operation and maintenance of boilers in hospital", null, "Submitted", "Ms. WONG Yuen-tung, Ingrid" ), +(94, (SELECT e.id FROM event e WHERE e.name = "27th Considerate Contractors Site Award" ), "Alliance Contracting Company Limited contract supervised by the EMSD", null, "Submitted", "EMSD" ), +(95, (SELECT e.id FROM event e WHERE e.name = "27th Considerate Contractors Site Award" ), "Interlite (Asia) Limited contract supervised by the EMSD", null, "Submitted", "EMSD" ), +(96, (SELECT e.id FROM event e WHERE e.name = "27th Considerate Contractors Site Award" ), "Anlev Elex Elevator Limited contract supervised by the EMSD", null, "Submitted", "EMSD" ), +(97, (SELECT e.id FROM event e WHERE e.name = "27th Considerate Contractors Site Award" ), "Yordland Engineering Limited contract supervised by the EMSD", null, "Submitted", "EMSD" ), +(98, (SELECT e.id FROM event e WHERE e.name = "26th Considerate Contractors Site Award" ), "Chevalier (Envirotech) Limited supervised by the EMSD", null, "Submitted", "EMSD" ), +(99, (SELECT e.id FROM event e WHERE e.name = "26th Considerate Contractors Site Award" ), "The Jardine Engineering Corporation, Limited supervised by the EMSD", null, "Submitted", "EMSD" ), +(100, (SELECT e.id FROM event e WHERE e.name = "26th Considerate Contractors Site Award" ), "Yordland Engineering Limited supervised by the EMSD", null, "Submitted", "EMSD" ), +(101, (SELECT e.id FROM event e WHERE e.name = "26th Considerate Contractors Site Award" ), "M & V Engineering (E&M) Limited supervised by the EMSD", null, "Submitted", "EMSD" ), +(102, (SELECT e.id FROM event e WHERE e.name = "26th Considerate Contractors Site Award" ), "REC Engineering Company Limited supervised by the EMSD", null, "Submitted", "EMSD" ), +(103, (SELECT e.id FROM event e WHERE e.name = "WorldSkills Hong Kong Competition 2019/2020" ), "Mr. Chan Ho-yin, Mr. Ngai Wai-kuen, Miss Leung Sze-wai", null, "Submitted", "Mr. Chan Ho-yin, Mr. Ngai Wai-kuen, Miss Leung Sze-wai" ), +(104, (SELECT e.id FROM event e WHERE e.name = "WorldSkills Hong Kong Competition 2019/2020" ), "Mr. Chi Chun-chung, Mr. Chan Wai-yu, Mr. Wong Ka-hei & Mr. Ng Yin-kit", null, "Submitted", "Mr. Chi Chun-chung, Mr. Chan Wai-yu, Mr. Wong Ka-hei & Mr. Ng Yin-kit" ), +(105, (SELECT e.id FROM event e WHERE e.name = "BIM Achievement 2020" ), "BIM Organisation", null, "Submitted", "EMSD" ), +(106, (SELECT e.id FROM event e WHERE e.name = "BIM Achievement 2020" ), "Construction of Building Information Model and Asset Information Inputting for EMSD Headquarters project", null, "Submitted", "EMSD" ), +(107, (SELECT e.id FROM event e WHERE e.name = "BIM Achievement 2020" ), "BIMer 2020", null, "Submitted", "Mr. Chan Hor-yin, Steve" ), +(108, (SELECT e.id FROM event e WHERE e.name = "ICT Awards 2020" ), "Virtual-Reality Based Lift Maintenance Simulator (VR-LMS)", null, "Submitted", "EMSD" ), +(109, (SELECT e.id FROM event e WHERE e.name = "International Annual Report Competition (ARC) 2020" ), "EMSD Annual Report 2018/19", null, "Submitted", "EMSD" ), +(110, (SELECT e.id FROM event e WHERE e.name = "League of American Communications Professionals (LACP) 2019 Vision Awards" ), "EMSD Annual Report 2018/19", null, "Submitted", "EMSD" ), +(111, (SELECT e.id FROM event e WHERE e.name = "ERB Manpower Developer Award Scheme" ), "Manpower Developer", null, "Submitted", "EMSD" ), +(112, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2021" ), "Video Analytics Monitoring System +for Abnormal Behaviours Detection", null, "Submitted", "EMSD" ), +(113, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2020" ), "Development of Smart Prison in Hong Kong - Passage +Surveillance and Health Signs Monitoring System", null, "Submitted", "Mr. Cheung Ka-kei" ), +(114, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2020" ), "Development of Predictive Maintenance of Lifts Using Optical Fibre Sensing Technology", null, "Submitted", "Mr Lau Wang-yip" ), +(115, (SELECT e.id FROM event e WHERE e.name = "Autodesk Hong Kong BIM Awards 2019" ), "Smart O&M at the CIC-Zero Carbon Building", null, "Submitted", "EMSD" ), +(116, (SELECT e.id FROM event e WHERE e.name = "Autodesk Hong Kong BIM Awards 2019" ), "Renovation of the EMSD headquarters' Customer Service Centre", null, "Submitted", "EMSD" ), +(117, (SELECT e.id FROM event e WHERE e.name = "Autodesk Hong Kong BIM Awards 2019" ), "Young BIMer of the Year", null, "Submitted", "Mr. Yuen Piu-hung, Francis" ), +(118, (SELECT e.id FROM event e WHERE e.name = "Learning Enterprise Award 2019" ), "Learning Organisation", null, "Submitted", "EMSD" ), +(119, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Training Beyond Innovation project", null, "Submitted", "EMSD" ), +(120, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Smart Prison project", null, "Submitted", "EMSD" ), +(121, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Customer Centric e-Platform project", null, "Submitted", "EMSD" ), +(122, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "From Reactive to Proactive Maintenance project", null, "Submitted", "EMSD" ), +(123, (SELECT e.id FROM event e WHERE e.name = "The 5th International BIM Awards" ), "Hong Kong Children's Hospital and Tin Shui Wai Hospital projects", null, "Submitted", "EMSD" ), +(124, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2019" ), "Mr Fu King-wai", null, "Submitted", "Mr Fu King-wai" ), +(125, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2019" ), "Mr Kwong Wai-lung", null, "Submitted", "Mr Kwong Wai-lung" ), +(126, (SELECT e.id FROM event e WHERE e.name = "30th HKQAA Anniversary Recognition Programme" ), "Outstanding Organisation with Comprehensive Management Systems", null, "Submitted", "EMSD" ), +(127, (SELECT e.id FROM event e WHERE e.name = "30th HKQAA Anniversary Recognition Programme" ), "Outstanding Organisation with Holistic Management Systems", null, "Submitted", "EMSD" ), +(128, (SELECT e.id FROM event e WHERE e.name = "International Annual Report Competition (ARC) 2019" ), "Social and Environmental Report 2017/18", null, "Submitted", "EMSD" ), +(129, (SELECT e.id FROM event e WHERE e.name = "45th WorldSkills Competition" ), "Mr. Chan Yu-tai", null, "Submitted", "Mr. Chan Yu-tai" ), +(130, (SELECT e.id FROM event e WHERE e.name = "45th WorldSkills Competition" ), "Mr. Kwok Chun-ming", null, "Submitted", "Mr. Kwok Chun-ming" ), +(131, (SELECT e.id FROM event e WHERE e.name = "25th Considerate Contractors Site Award" ), "Contractor work sites +under our supervision", null, "Submitted", "EMSD" ), +(132, (SELECT e.id FROM event e WHERE e.name = "Outstanding Staff & Teams and Young Achievers Award 2019" ), "Member of Hong Kong East Cluster Environmental Management Team", null, "Submitted", "EMSD" ), +(133, (SELECT e.id FROM event e WHERE e.name = "Digital Award 2018" ), "Digitising the air-conditioning and electrical distribution systems at the Tuen Mun School Dental Clinic", null, "Submitted", "EMSD" ), +(134, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2019" ), "Mr. Kwok Hiu-fung", null, "Submitted", "Mr. Kwok Hiu-fung" ), +(135, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2019" ), "Ms. Au Ka-wun", null, "Submitted", "Ms. Au Ka-wun" ), +(136, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2019" ), "Ms. Yeung Ying-fung", null, "Submitted", "Ms. Yeung Ying-fung" ), +(137, (SELECT e.id FROM event e WHERE e.name = "HKIE Trainee of the Year Award 2019" ), "Ms Chan Wing-yee", null, "Submitted", "Ms. Chan Wing-yee" ), +(138, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2019" ), "Developing the Smart Fever Screening System with the Hong Kong University of Science and Technology and the Department of Health", null, "Submitted", "Mr Nicholas LEE, Mr Stanley SIU Hiu Fai, Miss Suye WONG Siu Yee & Mr Jeremy WONG Tsung Yin" ), +(139, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2019" ), "NeuroSmart Eyes Air-conditioning Control System", null, "Submitted", "Mr Martin LAM Sau Sing, Miss Michelle LAW Ting Fung & Miss Karman PANG Ka Man" ), +(140, (SELECT e.id FROM event e WHERE e.name = "HKIE Young Engineer of the Year Award 2019" ), "Young Engineer of the Year", null, "Submitted", "Ms. Clare Luk" ), +(141, (SELECT e.id FROM event e WHERE e.name = "47th International Exhibition of Inventions of Geneva" ), "Health Signs Monitoring System and Passage Surveillance System under the smart prison solution", null, "Submitted", "EMSD" ), +(142, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2018" ), "Prompt repairs for footbridge lifts", null, "Submitted", "EMSD" ), +(143, (SELECT e.id FROM event e WHERE e.name = "Global Skills Challenge 2019" ), "Electrical Installations", null, "Submitted", "Mr. Chan Yu-tai" ), +(144, (SELECT e.id FROM event e WHERE e.name = "Guangzhou Invitation Tournament" ), "Refrigeration and Air-conditioning", null, "Submitted", "Mr. Kwok Chun-ming" ), +(145, (SELECT e.id FROM event e WHERE e.name = "24th Considerate Contractors Site Award" ), "Supervision of contractors", null, "Submitted", "EMSD" ), +(146, (SELECT e.id FROM event e WHERE e.name = "AEE Regional Award 2018" ), "Energy management project, i.e. the first combined heat and power electricity generation project in Hong Kong hospitals, jointly developed under the collaboration of the EMSD, the Hong Kong and China Gas Company Limited (Towngas) and the Hospital Authority (HA).", null, "Submitted", "EMSD" ), +(147, (SELECT e.id FROM event e WHERE e.name = "CIC Sustainable Construction Award 2018" ), "Use of innovative +technologies solution for sustainable development in the District Cooling System +at Kai Tak Development", null, "Submitted", "Miss Law Ting-fung, Michelle" ), +(148, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2018" ), "Mr. LEUNG Tak Man, Mr. Yip Ping-Keung & Mr. LEUNG Kwong Yiu", null, "Submitted", "Mr. LEUNG Tak Man, Mr. Yip Ping-Keung & Mr. LEUNG Kwong Yiu" ), +(149, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2018" ), "Mr. Lee Wing-chung, Vincent", null, "Submitted", "Mr. Lee Wing-chung, Vincent" ), +(150, (SELECT e.id FROM event e WHERE e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2018/19" ), "Mr. HoTak-shing, Mr. Suen Wai-tai and Mr. Lau Sen-kit", null, "Submitted", "Mr. HoTak-shing, Mr. Suen Wai-tai and Mr. Lau Sen-kit" ), +(151, (SELECT e.id FROM event e WHERE e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18" ), "EMSD", null, "Submitted", "EMSD" ), +(152, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2017" ), "Project called Caring for the Public, Respecting the Departed for the Mobile Ash Collector and Barcode Verification System", null, "Submitted", "EMSD" ), +(153, (SELECT e.id FROM event e WHERE e.name = "Civil Service Outstanding Service Award Scheme 2017" ), "Project in designing, procuring and installing various life-size mock-ups of ships, trains and aircraft for fire simulation and training in Fire and Ambulance Services Academy", null, "Submitted", "EMSD" ), +(154, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2017" ), "Addressing stall owners' concerns and difficulties", null, "Submitted", "Mr. Ivan Ho Chi-ming" ), +(155, (SELECT e.id FROM event e WHERE e.name = "AEE Regional Award 2017" ), "EMSD's overall outstanding performance in energy management programmes throughout the years", null, "Submitted", "EMSD" ), +(156, (SELECT e.id FROM event e WHERE e.name = "AEE Regional Award 2017" ), "Received the award with Hospital Authority (HA) in recognition of our joint effort in replacing a total of 38 aged conventional chillers in nine public hospitals with highly energy-efficient chillers since 2015", null, "Submitted", "EMSD" ), +(157, (SELECT e.id FROM event e WHERE e.name = "23rd Considerate Contractors Site Award" ), "Public Works", null, "Submitted", "EMSD" ), +(158, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2017" ), "Mr. Lau Chi-ching and Mr. Yeung Hoi-chiu", null, "Submitted", "Mr. Lau Chi-ching and Mr. Yeung Hoi-chiu" ), +(159, (SELECT e.id FROM event e WHERE e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18" ), "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit, Mr. Tsang Tak-shun, and Mr Shum Wai-fung", null, "Submitted", "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit, Mr. Tsang Tak-shun, and Mr Shum Wai-fung" ), +(160, (SELECT e.id FROM event e WHERE e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18" ), "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit & Mr. Chan Cheuk-nin", null, "Submitted", "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit & Mr. Chan Cheuk-nin" ), +(161, (SELECT e.id FROM event e WHERE e.name = "Airport Safety Recognition Scheme 2017/18" ), "Mr. Leung Ling-fung", null, "Submitted", "Mr. Leung Ling-fung" ), +(162, (SELECT e.id FROM event e WHERE e.name = "Airport Safety Recognition Scheme 2017/18" ), "EMSD", null, "Submitted", "EMSD" ), +(163, (SELECT e.id FROM event e WHERE e.name = "WorldSkills Hong Kong Competition 2017" ), "Mr. Chan Siu-lam", null, "Submitted", "Mr. Chan Siu-lam" ), +(164, (SELECT e.id FROM event e WHERE e.name = "44th WorldSkills Competition" ), "Mr. Chan Siu-lam", null, "Submitted", "Mr. Chan Siu-lam" ), +(165, (SELECT e.id FROM event e WHERE e.name = "Yan Oi Tong Volunteer Award 2017" ), "Concerted efforts of +EMSD's volunteers for providing household maintenance service to the elderly and +the needy in our community", null, "Submitted", "EMSD Staff Club Volunteer Team" ), +(166, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2017" ), "Mr. Chiu Sung-kwong, Mr. Cheung Kam-chin, Mr. Wan Wai-chiu, Randy & Ms. Ho Fung Yin, Agnes", null, "Submitted", "Mr. Chiu Sung-kwong, Mr. Cheung Kam-chin, Mr. Wan Wai-chiu, Randy & Ms. Ho Fung Yin, Agnes" ), +(167, (SELECT e.id FROM event e WHERE e.name = "League of American Communications Professionals (LACP) 2016 Vision Awards" ), "EMSD Annual Report 2015/16", null, "Submitted", "EMSD" ), +(168, (SELECT e.id FROM event e WHERE e.name = "HKIE Young Engineer of the Year Award 2017" ), "Mr. Siu Hiu-fai, Stanley", null, "Submitted", "Mr. Siu Hiu-fai, Stanley" ), +(169, (SELECT e.id FROM event e WHERE e.name = "HKIE Young Engineer of the Year Award 2017" ), "Mr. Leung Chi-to, Vincent", null, "Submitted", "Mr. Leung Chi-to, Vincent" ), +(170, (SELECT e.id FROM event e WHERE e.name = "HKIE Trainee of the Year Award 2016" ), "Ms. Hui Wing-yin", null, "Submitted", "Ms. Hui Wing-yin" ), +(171, (SELECT e.id FROM event e WHERE e.name = "HKIE Trainee of the Year Award 2016" ), "Mr. Yan Fu-ho", null, "Submitted", "Mr. Yan Fu-ho" ), +(172, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2017" ), "Designed a mobile app +named Tap My Dish for the Hong Kong +Blind Union", null, "Submitted", "Mr. Cheng Cheuk-tak, Mr. Choi Yiu-fai, Miss Kwok Sin-ming, Mr. Leung Cheuk-fung" ), +(173, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2016" ), "Mr. Kwok Chun-ting", null, "Submitted", "Mr. Kwok Chun-ting" ), +(174, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2016" ), "Mr. Wong Man-hin", null, "Submitted", "Mr. Wong Man-hin" ), +(175, (SELECT e.id FROM event e WHERE e.name = "HKIE Innovation Award 2016" ), "Developed a Mobile Smart Terminal to optimise operation and maintenance work on WiFi network related projects", null, "Submitted", "Mr. Yuen Piu-hung, Mr. Lai Hon-fung & Miss Yuen Wai-shan" ), +(176, (SELECT e.id FROM event e WHERE e.name = "9th Outstanding Occupational Safety and Health (OSH) Employees Award" ), "Mr Chiu Cho-chuen", null, "Submitted", "Mr. Chiu Cho-chuen" ), +(177, (SELECT e.id FROM event e WHERE e.name = "9th Outstanding Occupational Safety and Health (OSH) Employees Award" ), "Mr Cheung Lap-chung", null, "Submitted", "Mr Cheung Lap-chung" ), +(178, (SELECT e.id FROM event e WHERE e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2016/17" ), "Mr Chui Chi-kit", null, "Submitted", "EMSD" ), +(179, (SELECT e.id FROM event e WHERE e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2016/17" ), "Mr Chan Chung-him", null, "Submitted", "EMSD" ), +(180, (SELECT e.id FROM event e WHERE e.name = "Airport Safety Recognition Scheme 2016/17" ), "Mr. Chui Chi-kit and Mr. Chan Chung-him", null, "Submitted", "Mr. Chui Chi-kit and Mr. Chan Chung-him" ), +(181, (SELECT e.id FROM event e WHERE e.name = "Safety Quiz 2016" ), "EMSD", null, "Submitted", "EMSD" ), +(182, (SELECT e.id FROM event e WHERE e.name = "22nd Considerate Contractors Site Award" ), "Supervised works contracts", null, "Submitted", "Supervised works contracts" ), +(183, (SELECT e.id FROM event e WHERE e.name = "The Ombudsman's Awards 2016" ), "Project of Kowloon City Market Escalator Replacement Works", null, "Submitted", "Mr. Jairus Wong Kwun-ping" ), +(184, (SELECT e.id FROM event e WHERE e.name = "Secretary for the Civil Service's Commendation Award 2016" ), "Mr Sun Wing-leung, Mr. Kung Chi-leung, Mr. Szeto Wing-sum,", null, "Submitted", "Mr Sun Wing-leung, Mr. Kung Chi-leung, Mr. Szeto Wing-sum," ), +(185, (SELECT e.id FROM event e WHERE e.name = "WorldSkills Hong Kong Competition 2016" ), "Mr. Chan Siu-lam and Mr. Law Wai-yu", null, "Submitted", "Mr. Chan Siu-lam and Mr. Law Wai-yu" ), +(186, (SELECT e.id FROM event e WHERE e.name = "WorldSkills Hong Kong Competition 2016" ), "Mr. Lau Chi-ching +and Mr. Yu Fung-yuen", null, "Submitted", "Mr. Lau Chi-ching +and Mr. Yu Fung-yuen" ), +(187, (SELECT e.id FROM event e WHERE e.name = "Hong Kong Electronics Project Competition 2016" ), "Developed iVacancy System through the use of sensors installed in seats to distinguish the vacancy status by mobile apps", null, "Submitted", "Mr. Leung Cheuk-fung and Mr. Choi Yiu-fai" ), +(188, (SELECT e.id FROM event e WHERE e.name = "Best.hk Website Awards 2015" ), "EMSD Website", null, "Submitted", "EMSD" ), +(189, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2015" ), "Mr Lam Cheuk-ki", null, "Submitted", "Mr Lam Cheuk-ki" ), +(190, (SELECT e.id FROM event e WHERE e.name = "VTC Outstanding Apprentices 2015" ), "Mr Chow Wai-lun", null, "Submitted", "Mr Chow Wai-lun" ), +(191, (SELECT e.id FROM event e WHERE e.name = "Airport Safety Recognition Scheme 2015/16" ), "Designed Portable Power +Cubicle for airfield ground lighting (AGL) system to provide temporary power +supply for the AGL system efficiently and safely", null, "Submitted", "EMSD" ), +(192, (SELECT e.id FROM event e WHERE e.name = "BEAM Plus Certificate 2022" ), "EMSD HQs", null, "Submitted", "EMSD" ), +(193, (SELECT e.id FROM event e WHERE e.name = "Caring Company" ), "EMSD HQs", null, "Submitted", "EMSD" ), +(194, (SELECT e.id FROM event e WHERE e.name = "ICT Awards 2014" ), "Electric Locks Security System", null, "Submitted", "EMSD" ), +(195, (SELECT e.id FROM event e WHERE e.name = "ICT Awards 2011" ), "Central Command System of Marine Police", null, "Submitted", "EMSD" ), +(196, (SELECT e.id FROM event e WHERE e.name = "ICT Awards 2007" ), "Self-Developed Mobile CCTV System", null, "Submitted", "EMSD" ), +(197, (SELECT e.id FROM event e WHERE e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Use of Artificial Intelligence, Dronen and Remote Sensors for Tower Inspections", null, "Submitted", "Chi Keung, Eric LAI (POINNO22)" ), +(198, (SELECT e.id FROM event e WHERE e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "City-level Robotic Inspection and Diagnostic System for Building Systems", null, "Submitted", "Ka Tai LAU (EGESSD4)" ), +(199, (SELECT e.id FROM event e WHERE e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "A.I. Real-time Lift Door Inspection System", null, "Submitted", "Jason Junsing AU (BSEGESF3)" ), +(200, (SELECT e.id FROM event e WHERE e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Smart Antifouling Seawater Screen", null, "Submitted", "Lung Wai, Chris CHAN (POINNO32)" ), +(201, (SELECT e.id FROM event e WHERE e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "AI Voicebot Training System with Performance Evaluation for Call Centre Agents", null, "Submitted", "Man Yee CHENG (ECC2)" ), +(202, (SELECT e.id FROM event e WHERE e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "3D printed Trap case and T-Shaped Bait Box for rodent control and management", null, "Submitted", "Ming Lui YEUNG (EEBIM4)" ), +(203, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Tramway Derailment and Collision Prevention System", null, "Submitted", "EMSD" ), +(204, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Smart Antifouling Seawater Screen", null, "Submitted", "EMSD" ), +(205, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Integrated Self-sustained renewable-Energy Explorer (iSEE)", null, "Submitted", "EMSD" ), +(206, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "HKeToll - Free-flow Tolling System in HK", null, "Submitted", "EMSD" ), +(207, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Integrated Intelligent Communication System in Correctional Institutions/Facilities", null, "Submitted", "EMSD" ), +(208, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Intelligent Lift Door Inspector", null, "Submitted", "EMSD" ), +(209, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Smart Overhead Rail Servicing Robot", null, "Submitted", "EMSD" ), +(210, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Smart Toilet Management System", null, "Submitted", "EMSD" ), +(211, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "LPG Tank Inspection & Data Analysis with Intelligent Tanker Robot", null, "Submitted", "EMSD" ), +(212, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Train-borne Railway Infrastructure Inspection System", null, "Submitted", "EMSD" ), +(213, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Smart Drainage System for Flood Monitoring using LPWAN IoT sensors and LoRaCam", null, "Submitted", "EMSD" ), +(214, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "City-level Robotic Inspection & Diagnostic System for Building Systems", null, "Submitted", "EMSD" ), +(215, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Artificial Intelligence Video Analytics System for Escalator Monitoring in Wet Markets of Hong Kong", null, "Submitted", "EMSD" ), +(216, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Speech-to-text Hub for Real-time Monitoring and Analysis in Customer Service Calls", null, "Submitted", "EMSD" ), +(217, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "LoRa Mesh Technology for Extending the IoT Network Coverage to the Rural Areas", null, "Submitted", "EMSD" ), +(218, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Robotics-Enabled Fogger on Mosquito Control", null, "Submitted", "EMSD" ), +(219, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Electrical Doctor – Real-time Health Diagnosis for Electricity Supply System", null, "Submitted", "EMSD" ), +(220, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Solar Harvest Map (SHM)", null, "Submitted", "EMSD" ), +(221, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Digital Log-books for Lifts and Escalators", null, "Submitted", "EMSD" ), +(222, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Gas Pipe Health AI Prediction Model", null, "Submitted", "EMSD" ), +(223, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Rail Track Collision Object Detection System", null, "Submitted", "EMSD" ), +(224, (SELECT e.id FROM event e WHERE e.name = "48th International Exhibition of Inventions of Geneva" ), "Passenger Misbehaviour Detection System", null, "Submitted", "EMSD" ); + + +--comment: award +INSERT INTO award (id, applicationId, name, nameCht, remarks, receiveDate, storageLocation, physicalAward, categoryId, responsibleOfficer, promotionChannel) VALUES +(1, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "i-EMSD" AND e.name = "Hong Kong Electronics Project Competition 2022" ), "Hong Kong electronics project competition 2022, Champion in industry category", null, "Champion in industry category", "2022-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "GLD (AU Tze wai and YIU Yung ngai)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(2, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr WONG Yin Kit" AND e.name = "The Ombudsman's Awards 2022" ), "The Ombudsman's Awards, Individual awards", null, "Individual awards", "2022-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "Mr WONG Yin Kit", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(3, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr LAU Ka Shing" AND e.name = "The Ombudsman's Awards 2022" ), "The Ombudsman's Awards, Individual awards", null, "Individual awards", "2022-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "Mr LAU Ka Shing", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(4, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "CHAU Shu man" AND e.name = "Chief Executive's Commendation for Government/Public Service 2022" ), "Chief Executive's Commendation for Government/Public Service", null, null, "2022-11-07", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "CHAU Shu man", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(5, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "WONG Wai Kwong" AND e.name = "Chief Executive's Commendation for Government/Public Service 2022" ), "Chief Executive's Commendation for Government/Public Service", null, null, "2022-11-07", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "WONG Wai Kwong", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(6, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Triennial Term Contract for the Comprehensive Maintenance and Repair of Lift Installations at Various Premises of the Government of the Hong Kong Special Administrative Region (Package One of AE20)" AND e.name = "28th Considerate Contractors Site Award" ), "Bronze", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "Anlev Elex Elevator Limited", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(7, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Refurbishment of Central to Mid-levels Escalator and Walkway System" AND e.name = "28th Considerate Contractors Site Award" ), "Merit", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "Chevalier (Envirotech) Limimted", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(8, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Design-Build-Operate a District Cooling System (Phase 2 works) at Kai Tak Development" AND e.name = "28th Considerate Contractors Site Award" ), "Merit", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "Anlev Elex Elevator Limited", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(9, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Triennial Term Contract for Provision of Maintenance, Modification and Installation Works of Electronic and E&M Control Systems at Various Sewage Treatment Works and Their Outstations for Drainage Services Department" AND e.name = "28th Considerate Contractors Site Award" ), "Merit", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "Hong Kong District Cooling DHY Joint Venture", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(10, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The First MiMEP Pilot Project in EMSTF - Chiller Plant +Replacement at Tai Lung Veterinary Laboratory" AND e.name = "Autodesk Hong Kong BIM Awards 2022" ), "Honorable mention", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(11, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Boiler Cleaning and Inspection Robot" AND e.name = "HKIE Innovation Award 2022" ), "Grand Prize, Category I - Invention", null, "Category I - Invention", "2022-06-11", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "Mr Chow Pak Hong, Mr Ng Chin Hung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(12, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "CHI Chun-chung" AND e.name = "WorldSkills Competition 2022" ), "Merit Award, Refrigeration & Air Conditioning", null, "Refrigeration & Air Conditioning", "2022-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "CHI Chun-chung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(13, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr CHOW Moon-tong" AND e.name = "Secretary for the Civil Service's Commendation Award 2022" ), "Secretary for Civil Service's Commendation Award", null, null, "2022-09-05", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "Mr CHOW Moon-tong", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(14, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr WU Pak-chun, Stanley, Shift Charge Engineer" AND e.name = "Secretary for the Civil Service's Commendation Award 2022" ), "Secretary for Civil Service's Commendation Award", null, null, "2022-09-05", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "Mr WU Pak-chun, Stanley, Shift Charge Engineer", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(15, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr FAN Jor-cheung, Works Supervisor I" AND e.name = "Secretary for the Civil Service's Commendation Award 2022" ), "Secretary for Civil Service's Commendation Award", null, null, "2022-09-05", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "Mr FAN Jor-cheung, Works Supervisor I", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(16, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "MiMEP project at Tai Lung Project" AND e.name = "IET HK Young Professionals Exhibition and Competition" ), "IET HK Young Professionals Exhibition and Competition", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(17, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "BIM Achievement 2022" ), "BIM Organisations 2022", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(18, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "BIM Achievement 2022" ), "BIM Training and R&D Organisations 2022", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(19, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "BIM Achievement 2022" ), "BIM Project 2022", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(20, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "HKIHRM HR Excellence Award" ), "Elite Employee Engagement Award (Organisational Category)", null, null, "2022-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(21, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "continuous contribution and performance in KM and innovation activities" AND e.name = "Hong Kong Most Innovative Knowledge Enterprise (MIKE) Award 2022" ), "Winner Award", null, null, "2022-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(22, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSTF/EMSD" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Gold Prize, Excellence in Service Enhancement (Large Department Category)", null, "Excellence in Service Enhancement (Large Department Category)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSTF/EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(23, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Digital Log-books for Lifts and Escalators" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Gold Prize, Excellence in Team Collaboration (Regulatory Service)", null, "Excellence in Team Collaboration (Regulatory Service)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(24, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Together We Build" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Gold Prize, Excellence in Partnership", null, "Excellence in Partnership", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "BTSD/EMSd", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(25, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "E&M InnoPortal" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Gold Prize, Innovation and Technology Awards (Best Stakeholder Collaboration)", null, "Innovation and Technology Awards (Best Stakeholder Collaboration)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(26, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Technological Innovation Boosting the Cemeteries and Crematoria Services" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Silver Prize, Excellence in Partnership", null, "Excellence in Partnership", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "MunSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(27, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Round-the-clock Monitoring of Community Vaccination Centres" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Silver Prize, Excellence in Team Collaboration (Internal Service)", null, "Excellence in Team Collaboration (Internal Service)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "MunSD, HSD, GESD, BTSD/EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(28, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Together, We Filter out the Virus" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Silver Prize, Excellence in Team Collaboration (Management of Crisis)", null, "Excellence in Team Collaboration (Management of Crisis)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "HSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(29, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Digitalisation Journey - E&M 2.0" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Silver Prize, Innovation and Technology Awards (Best Use of Technology)", null, "Innovation and Technology Awards (Best Use of Technology)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "DTD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(30, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Temporary Body Storage Facility near Fu Shan Public Mortuary" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Meritorious Award, Excellence in Partnership", null, "Excellence in Partnership", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSTF/EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(31, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Work Together to Fight the Epidemic" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Meritorious Award, Excellence in Team Collaboration (Specialised Service)", null, "Excellence in Team Collaboration (Specialised Service)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(32, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Innovation Facilitator" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Meritorious Award, Excellence in Team Collaboration (Internal Service)", null, "Excellence in Team Collaboration (Internal Service)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "DTD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(33, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Advance Inspection Booking System for Lifts and Escalators Major Alteration Works" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Meritorious Award, Excellence in Team Collaboration (Regulatory Service)", null, "Excellence in Team Collaboration (Regulatory Service)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(34, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "E&M 100 - CHT Rescue Team" AND e.name = "Civil Service Outstanding Service Award Scheme 2022" ), "Meritorious Award, Excellence in Team Collaboration (Management of Crisis)", null, "Excellence in Team Collaboration (Management of Crisis)", "2022-12-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "BTSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(35, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Robotic Lift Examiner" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(36, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The i-EMSD" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(37, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The i-DEMS" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(38, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Smart Boiler Servicing Robot" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(39, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Smart Driver Assistant for Automated People Mover" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(40, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A Contactless Inmate Health Monitoring System in Prison" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(41, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A Drone-based LoRa Network for Location Tracking in Remote Areas" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(42, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A Novel RFID System" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(43, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A Novel Wireless Communication System for Underground Utility Monitoring" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(44, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "AI Voicebot Training System with Performance Evaluation for Call Centre Agents" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(45, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Artificial Intelligence Based Image Analytic and Control System for Cremation Process" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(46, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Artificial Intelligent Monitoring System for Aerial Ropeways (AIMS)" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(47, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Artificial Intelligent Robotic Cleaning and Disinfection System - i-Cleaner" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(48, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Fireman Health Monitoring System" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(49, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "IoT-enabled Mobile Modular HEPA Unit (MMHU): Together, We Fight the Virus" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(50, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Responsive Web-based Solar Irradiation Map with Solar Energy Calculator for Hong Kong" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(51, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Semantic AI for Predictive Maintenance of Railway Track Systems" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(52, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Lift Passenger Flow Analysis" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Sliver Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(53, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Integrated Solar Energy Performance Management Toolkit (iSMS)" AND e.name = "2022 Special Edition International Exhibition of Inventions Geneva" ), "Bronze Medal-winning projects", null, null, "2022-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(54, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "One of our team of colleagues working alongside Airport Authority Hong Kong (AAHK) personnel at HKIA was named a Role Model in Safety Behaviour in AAHK's 2021/22 Airport Safety Recognition Scheme" AND e.name = "Airport Safety Recognition Scheme 2021/22" ), "Role Model in Safety Behaviour, Exemplary performance in preventing hazards and improving safety at the airport", null, "Exemplary performance in preventing hazards and improving safety at the airport", "2022-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(55, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Most Engaging Client award" AND e.name = "CIC's Life First Safety Promotional Campaign" ), "Most Engaging Client award, Most Engaging Client award", null, "Most Engaging Client award", "2021-08-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(56, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A total 24 EMSD colleagues, including staff seconded to other works departments received this for their outstanding anti-epidemic performance." AND e.name = "Chief Executive's Commendation for Government/Public Service 2021" ), "Chief Executive's Commendation for Government/Public Service, Outstanding anti-epidemic performance", null, "Outstanding anti-epidemic performance", "2021-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(57, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Three colleagues were awarded" AND e.name = "Secretary for the Civil Service's Commendation Award 2020" ), "Secretary for Civil Service's Commendation Award", null, null, "2021-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "同心" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(58, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Two colleagues awarded for their outstanding work." AND e.name = "The Ombudsman's Awards 2021" ), "Ombudsman's Award for Officers, Public Organisations", null, "Public Organisations", "2021-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "惠民" ), "Ms Lee Kit-chun, Cherry", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(59, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Virtual Reality-based Lift Maintenance training programme" AND e.name = "19th Hong Kong Occupational Safety and Health Award" ), "Gold Award", null, null, "2021-03-21", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(60, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. LEE King (TTII)" AND e.name = "VTC Outstanding Apprentices 2021" ), "Outstanding Apprentice", null, null, "2021-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "Mr. LEE King (TTII)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(61, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. LAW Cheuk-ho" AND e.name = "VTC Outstanding Apprentices 2021" ), "Outstanding Apprentice (Merit)", null, null, "2021-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "Mr. LAW Cheuk-ho", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(62, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. CHAN Tat Fung (I/Training 1)" AND e.name = "VTC Outstanding Apprentices 2021" ), "Outstanding Apprentice Mentors", null, null, "2021-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "Mr. CHAN Tat Fung (I/Training 1)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(63, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Annual Report 2020/21" AND e.name = "International Annual Report Competition (ARC) 2021" ), "Silver Award, Chairman's/President's Letter: Government Agencies & Offices", null, "Chairman's/President's Letter: Government Agencies & Offices", "2021-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(64, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Social and Environmental Report 2020/21" AND e.name = "International Annual Report Competition (ARC) 2021" ), "Silver Award, Green/Environmentally Sound Annual Report", null, "Green/Environmentally Sound Annual Report", "2021-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(65, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Annual Report 2020/21" AND e.name = "International Annual Report Competition (ARC) 2021" ), "Bronze Award, Non-Profit Organisation (Print A.R.): Government Agencies & Offices", null, "Non-Profit Organisation (Print A.R.): Government Agencies & Offices", "2021-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(66, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Social and Environmental Report 2020/21" AND e.name = "League of American Communications Professionals (LACP) 2021 Vision Awards" ), "Platinum Award, Annual Report", null, "Annual Report", "2021-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(67, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Annual Report 2020/21" AND e.name = "League of American Communications Professionals (LACP) 2021 Vision Awards" ), "Gold Award, Sustainability Report", null, "Sustainability Report", "2021-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(68, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Annual Report 2020/21" AND e.name = "HKMA Best Annual Reports Awards 2022" ), "Best New Entry, Non-Profit Making and Charitable Organisations Category", null, "Non-Profit Making and Charitable Organisations Category", "2022-10-19", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(69, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Electrical and Mechanical Services Department Annual Report 2020/21" AND e.name = "HKMA Best Annual Reports Awards 2022" ), "Certificate of Excellence, Environmental, Social and Governance Reporting", null, "Environmental, Social and Governance Reporting", "2022-10-19", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(70, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Three TTs from the EMSD got the top three prizes in the Electrical Installations trade of the Hong Kong Region qualifying competition" AND e.name = "The 11th Guangzhou/Hong Kong/Macao/Chengdu (GHMC) Youth Skills Competition" ), "Top three prizes in the Electrical Installations trade of the Hong Kong Region qualifying competition", null, "N/A", "2021-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(71, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Prison - Video Analysis System" AND e.name = "HKIE Innovation Award 2021" ), "Certificate of Merit, Category II – An Innovative Application of Engineering Theories", null, "Category II – An Innovative Application of Engineering Theories", "2021-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(72, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Robotics-enabled Public Services on Toilet Bowl Cleaning Application" AND e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Grand Award", null, null, "2021-07-28", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(73, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Application of Artificial Intelligence and Robotics Technologies for Smart Warehouse" AND e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "1st Runner up Award", null, null, "2021-07-28", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(74, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Integrated Smart Robot Assistant for Building IoT Network" AND e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Merit Awards", null, null, "2021-07-28", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(75, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Robotic Cleaning and Disinfection System with Video Analytics and Robotic Arm" AND e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Merit Awards", null, null, "2021-07-28", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(76, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Robotic Steam Boiler Tube Cleaning & Inspection System" AND e.name = "Leading Towards Robotics Technologies Innovation Competition 2020 - 2021" ), "Merit Awards", null, null, "2021-07-28", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(77, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Joint submission of EMSD, HKGBC and Trance" AND e.name = "ASHRAE Technology Award 2021" ), "Hong Kong Chapter – First Place Winner, Hong Kong Chapter", null, "Hong Kong Chapter", "2021-06-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "Joint submission of EMSD, HKGBC and Trance", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Facebook" ))), +(78, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Joint submission of EMSD, HKGBC and Trance" AND e.name = "ASHRAE Technology Award 2021" ), "Second Place Winner, Regional Level (involve nine Asian countries/regions)", null, "Regional Level (involve nine Asian countries/regions)", "2021-06-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "Joint submission of EMSD, HKGBC and Trance", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Facebook" ))), +(79, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD Solar Harvest scheme" AND e.name = "AEE Regional Award 2021" ), "Innovative Energy Project of the Year Award, Asia-Pacific Rim region", null, "Asia-Pacific Rim region", "2021-10-19", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(80, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Technician Training Scheme (SPARK Programme)" AND e.name = "Award for Excellence in Training and Development 2021" ), "Silver Award, Silver Award", null, "Silver Award", "2021-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(81, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD, Excellence in Career Development" AND e.name = "Award for Excellence in Training and Development 2021" ), "Special Award, Excellence in Career Development", null, "Excellence in Career Development", "2021-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(82, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD, Excellence in Future Talent Development" AND e.name = "Award for Excellence in Training and Development 2021" ), "Special Award, Excellence in Future Talent Development", null, "Excellence in Future Talent Development", "2021-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(83, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD, Excellence in Future Skills Development" AND e.name = "Award for Excellence in Training and Development 2021" ), "Special Award, Excellence in Future Skills Development", null, "Excellence in Future Skills Development", "2021-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(84, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD, HR Professionals' Favourite" AND e.name = "Award for Excellence in Training and Development 2021" ), "Special Award, HR Professionals' Favourite Campaign", null, "HR Professionals' Favourite Campaign", "2021-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "傳承" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(85, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Cloud-based Predictive Maintenance System for Real-Time Lift Monitoring" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(86, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Artificial Intelligent Nylon Optical Fibre Sensing Escalator Combs" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(87, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Internet of things (IoT)-enabled Smart Toilet Bowl Cleaning System" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(88, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Building Semantic Artificial Intelligence: The Future of Automation in City Level - a real application in today" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Gold Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(89, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Air Filter 2.0 - Energy Saving Smart Air Filter Technology" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Silver Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(90, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Fibre Optic Temperature Sensing System for Predictive" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Silver Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(91, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Non-Intrusive Data Analytics System for Adaptive Intelligent Condition Monitoring of Lifts" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Silver Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(92, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Prison - Video Analytic Monitoring System" AND e.name = "Special Edition 2021 Inventions Geneva" ), "Silver Medal-winning projects", null, null, "2021-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "創新" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(93, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "West Kowloon Government Offices" AND e.name = "CIBSE Hong Kong Awards 2021" ), "Project of the Year Award, Public Use Building", null, "Public Use Building", "2021-10-12", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(94, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Digitalisation Journey of Electrical and Mechanical Services Department" AND e.name = "CIC Construction Digitalisation Award 2021" ), "Gold Award, Organisation Category - Client", null, "Organisation Category - Client", "2021-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD, Mr. LAI Chun Fai (SE/Inno)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(95, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart City Management - The Regional Digital Control Centre (RDCC) & AI Platform" AND e.name = "ICT Awards 2021" ), "Gold Award, Smart Business Award (Big Data and Open Data Applications)", null, "Smart Business Award (Big Data and Open Data Applications)", "2021-11-29", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD, Patrick SO (SE/GES/SD)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(96, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A Digitalize Smart Lift Monitoring System for Elderly Persons and Persons with Disabilities" AND e.name = "City I&T Grand Challenge 2021" ), "Innovation Award, Innovation Awards", null, "Innovation Awards", "2021-10-16", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "SVSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(97, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Prompt repairs of traffic light systems in Public Order Events" AND e.name = "The Ombudsman's Awards 2020" ), "Ombudsman's Awards 2020, Ombudsman's Awards 2020 for Officers of Public Organisations", null, "Ombudsman's Awards 2020 for Officers of Public Organisations", "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Kin-hong", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(98, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Complaint handling on operation and maintenance of boilers in hospital" AND e.name = "The Ombudsman's Awards 2020" ), "Ombudsman's Awards 2020, Ombudsman's Awards 2020 for Officers of Public Organisations", null, "Ombudsman's Awards 2020 for Officers of Public Organisations", "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ms. WONG Yuen-tung, Ingrid", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(99, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Alliance Contracting Company Limited contract supervised by the EMSD" AND e.name = "27th Considerate Contractors Site Award" ), "Gold Award", null, null, "2021-08-06", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(100, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Interlite (Asia) Limited contract supervised by the EMSD" AND e.name = "27th Considerate Contractors Site Award" ), "Silver Award", null, null, "2021-08-06", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(101, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Anlev Elex Elevator Limited contract supervised by the EMSD" AND e.name = "27th Considerate Contractors Site Award" ), "Merit Award", null, null, "2021-08-06", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(102, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Yordland Engineering Limited contract supervised by the EMSD" AND e.name = "27th Considerate Contractors Site Award" ), "Merit Award", null, null, "2021-08-06", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(103, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Chevalier (Envirotech) Limited supervised by the EMSD" AND e.name = "26th Considerate Contractors Site Award" ), "Silver Award", null, null, "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(104, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Jardine Engineering Corporation, Limited supervised by the EMSD" AND e.name = "26th Considerate Contractors Site Award" ), "Bronze Award", null, null, "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(105, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Yordland Engineering Limited supervised by the EMSD" AND e.name = "26th Considerate Contractors Site Award" ), "Bronze Award", null, null, "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(106, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "The Jardine Engineering Corporation, Limited supervised by the EMSD" AND e.name = "26th Considerate Contractors Site Award" ), "Merit Award", null, null, "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(107, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "M & V Engineering (E&M) Limited supervised by the EMSD" AND e.name = "26th Considerate Contractors Site Award" ), "Merit Award", null, null, "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(108, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "REC Engineering Company Limited supervised by the EMSD" AND e.name = "26th Considerate Contractors Site Award" ), "Merit Award", null, null, "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(109, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chan Ho-yin, Mr. Ngai Wai-kuen, Miss Leung Sze-wai" AND e.name = "WorldSkills Hong Kong Competition 2019/2020" ), "Outstanding performance, \"Electrical Installations\" category", null, "\"Electrical Installations\" category", "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Ho-yin, Mr. Ngai Wai-kuen, Miss Leung Sze-wai", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(110, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chi Chun-chung, Mr. Chan Wai-yu, Mr. Wong Ka-hei & Mr. Ng Yin-kit" AND e.name = "WorldSkills Hong Kong Competition 2019/2020" ), "Outstanding performance, \"Refrigeration and Air Conditioning\" category", null, "\"Refrigeration and Air Conditioning\" category", "2020-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chi Chun-chung, Mr. Chan Wai-yu, Mr. Wong Ka-hei & Mr. Ng Yin-kit", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(111, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "BIM Organisation" AND e.name = "BIM Achievement 2020" ), "BIM Organisations 2020", null, null, "2020-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(112, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Construction of Building Information Model and Asset Information Inputting for EMSD Headquarters project" AND e.name = "BIM Achievement 2020" ), "BIM Project 2020", null, null, "2020-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(113, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "BIMer 2020" AND e.name = "BIM Achievement 2020" ), "BIMer 2020", null, null, "2020-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Hor-yin, Steve", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(114, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Virtual-Reality Based Lift Maintenance Simulator (VR-LMS)" AND e.name = "ICT Awards 2020" ), "Certificate of Merit, Smart Business (Solution for Business and Public Sector Enterprise)", null, "Smart Business (Solution for Business and Public Sector Enterprise)", "2020-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(115, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD Annual Report 2018/19" AND e.name = "International Annual Report Competition (ARC) 2020" ), "ARC Awards, Bronze award in both the Non-Profit Organisation (Print A.R.): Government Agencies & Offices category and the Specialised A.R.: Combined Annual and Sustainability Report category", null, "Bronze award in both the Non-Profit Organisation (Print A.R.): Government Agencies & Offices category and the Specialised A.R.: Combined Annual and Sustainability Report category", "2020-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(116, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD Annual Report 2018/19" AND e.name = "League of American Communications Professionals (LACP) 2019 Vision Awards" ), "2019 Vision Awards, Silver Award – Government Industry and Top 50 Chinese Reports in the 2019 Vision Awards", null, "Silver Award – Government Industry and Top 50 Chinese Reports in the 2019 Vision Awards", "2020-08-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(117, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Manpower Developer" AND e.name = "ERB Manpower Developer Award Scheme" ), "Manpower Developer Status, Manpower Developer Status of ERB Manpower Developer Award Scheme (1 Apr 2020 to 31 March 2022) - Electrical and Mechanical Services Department", null, "Manpower Developer Status of ERB Manpower Developer Award Scheme (1 Apr 2020 to 31 March 2022) - Electrical and Mechanical Services Department", "2020-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(118, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Video Analytics Monitoring System +for Abnormal Behaviours Detection" AND e.name = "HKIE Innovation Award 2021" ), "Innovative Application of Engineering Theories (Certificate of Merit), Young Member Group - Category II", null, "Young Member Group - Category II", "2021-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(119, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Development of Smart Prison in Hong Kong - Passage +Surveillance and Health Signs Monitoring System" AND e.name = "HKIE Innovation Award 2020" ), "Invention (Certificate of Merit), Young Member Group - Category I", null, "Young Member Group - Category I", "2020-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Cheung Ka-kei", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(120, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Development of Predictive Maintenance of Lifts Using Optical Fibre Sensing Technology" AND e.name = "HKIE Innovation Award 2020" ), "Innovative Application of Engineering Theories (Certificate of Merit), Young Member Group - Category II", null, "Young Member Group - Category II", "2020-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Lau Wang-yip", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(121, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart O&M at the CIC-Zero Carbon Building" AND e.name = "Autodesk Hong Kong BIM Awards 2019" ), "Autodesk Hong Kong BIM Award 2019, BIM Award", null, "BIM Award", "2020-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(122, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Renovation of the EMSD headquarters' Customer Service Centre" AND e.name = "Autodesk Hong Kong BIM Awards 2019" ), "Autodesk Hong Kong BIM Award 2019, Honourable Mention", null, "Honourable Mention", "2020-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(123, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Young BIMer of the Year" AND e.name = "Autodesk Hong Kong BIM Awards 2019" ), "Autodesk Hong Kong BIM Award 2019, Young BIMer of the Year", null, "Young BIMer of the Year", "2020-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Yuen Piu-hung, Francis", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(124, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Learning Organisation" AND e.name = "Learning Enterprise Award 2019" ), "Learning Organisation Honorary Award 2019, Learning Organisation Honorary Award", null, "Learning Organisation Honorary Award", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(125, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Training Beyond Innovation project" AND e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Civil Service Outstanding Service Award 2019, Gold Prize in Team Award (Internal Support)", null, "Gold Prize in Team Award (Internal Support)", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(126, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Training Beyond Innovation project" AND e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Civil Service Outstanding Service Award 2019, Special Citation Award (Application of Innovation and Technology)", null, "Special Citation Award (Application of Innovation and Technology)", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(127, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Prison project" AND e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Civil Service Outstanding Service Award 2019, Meritorious Award in inter-departmental Partnership Award", null, "Meritorious Award in inter-departmental Partnership Award", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(128, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Customer Centric e-Platform project" AND e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Civil Service Outstanding Service Award 2019, Meritorious Award in Team Award (Internal Support)", null, "Meritorious Award in Team Award (Internal Support)", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(129, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "From Reactive to Proactive Maintenance project" AND e.name = "Civil Service Outstanding Service Award Scheme 2019" ), "Civil Service Outstanding Service Award 2019, Special Citation Award (Application of Innovation and Technology) in Team Award (General Service)", null, "Special Citation Award (Application of Innovation and Technology) in Team Award (General Service)", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(130, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Hong Kong Children's Hospital and Tin Shui Wai Hospital projects" AND e.name = "The 5th International BIM Awards" ), "5th International BIM Award, The 5th International BIM Awards - the Best BIM Government Department Award", null, "The 5th International BIM Awards - the Best BIM Government Department Award", "2019-12-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(131, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Fu King-wai" AND e.name = "Secretary for the Civil Service's Commendation Award 2019" ), "Secretary for Civil Service's Commendation Award, Secretary for Civil Service's Commendation Award 2019", null, "Secretary for Civil Service's Commendation Award 2019", "2019-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Fu King-wai", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(132, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Kwong Wai-lung" AND e.name = "The Ombudsman's Awards 2019" ), "Ombudsman's Awards 2019, Ombudsman's Awards 2019 for Officers of Public Organisations", null, "Ombudsman's Awards 2019 for Officers of Public Organisations", "2019-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Kwong Wai-lung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(133, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Outstanding Organisation with Comprehensive Management Systems" AND e.name = "30th HKQAA Anniversary Recognition Programme" ), "Outstanding Organisation with Comprehensive Management Systems, Outstanding Organisation with Comprehensive Management Systems", null, "Outstanding Organisation with Comprehensive Management Systems", "2019-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Group Voice", "Voice Link" ))), +(134, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Outstanding Organisation with Holistic Management Systems" AND e.name = "30th HKQAA Anniversary Recognition Programme" ), "Outstanding Organisation with Holistic Management Systems, Outstanding Organisation with Holistic Management Systems", null, "Outstanding Organisation with Holistic Management Systems", "2019-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Group Voice", "Voice Link" ))), +(135, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Social and Environmental Report 2017/18" AND e.name = "International Annual Report Competition (ARC) 2019" ), "ARC Award, Silver Award in the Non-Profit Organisation (Online A.R.): Green/ Environmentally Sound Report", null, "Silver Award in the Non-Profit Organisation (Online A.R.): Green/ Environmentally Sound Report", "2019-08-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Group Voice", "Voice Link" ))), +(136, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chan Yu-tai" AND e.name = "45th WorldSkills Competition" ), "Medallion for Excellence, Medallion for Excellence in \"Electrical Installations\" category", null, "Medallion for Excellence in \"Electrical Installations\" category", "2019-08-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Yu-tai", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(137, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Kwok Chun-ming" AND e.name = "45th WorldSkills Competition" ), "Medallion for Excellence, Medallion for Excellence in \"Refrigeration and Air-conditioning\" category", null, "Medallion for Excellence in \"Refrigeration and Air-conditioning\" category", "2019-08-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Kwok Chun-ming", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(138, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Contractor work sites +under our supervision" AND e.name = "25th Considerate Contractors Site Award" ), "25th Considerate Contractors Site Award, One Bronze Award and two Merit Awards in the 25th Considerate Contractors Site Award Scheme", null, "One Bronze Award and two Merit Awards in the 25th Considerate Contractors Site Award Scheme", "2019-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(139, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Member of Hong Kong East Cluster Environmental Management Team" AND e.name = "Outstanding Staff & Teams and Young Achievers Award 2019" ), "HA Outstanding Team Award 2019, HA Outstanding Team Award 2019", null, "HA Outstanding Team Award 2019", "2019-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(140, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Digitising the air-conditioning and electrical distribution systems at the Tuen Mun School Dental Clinic" AND e.name = "Digital Award 2018" ), "Digital Award 2018, Digital Award 2018 for the Best Small Project/Collaboration", null, "Digital Award 2018 for the Best Small Project/Collaboration", "2019-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(141, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Kwok Hiu-fung" AND e.name = "VTC Outstanding Apprentices 2019" ), "2019 Outstanding Apprentices Award, Outstanding Apprentices", null, "Outstanding Apprentices", "2020-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Kwok Hiu-fung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(142, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Ms. Au Ka-wun" AND e.name = "VTC Outstanding Apprentices 2019" ), "2019 Outstanding Apprentices Award, Apprentices of Excellent Performance", null, "Apprentices of Excellent Performance", "2020-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ms. Au Ka-wun", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(143, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Ms. Yeung Ying-fung" AND e.name = "VTC Outstanding Apprentices 2019" ), "2019 Outstanding Apprentices Award, Apprentices of Excellent Performance", null, "Apprentices of Excellent Performance", "2020-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ms. Yeung Ying-fung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(144, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Ms Chan Wing-yee" AND e.name = "HKIE Trainee of the Year Award 2019" ), "2019 HKIE Trainee of the Year Award, 2019 HKIE Trainee of the Year Award", null, "2019 HKIE Trainee of the Year Award", "2020-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ms. Chan Wing-yee", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(145, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Developing the Smart Fever Screening System with the Hong Kong University of Science and Technology and the Department of Health" AND e.name = "HKIE Innovation Award 2019" ), "Grand prize, Category I – An Invention", null, "Category I – An Invention", "2019-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Nicholas LEE, Mr Stanley SIU Hiu Fai, Miss Suye WONG Siu Yee & Mr Jeremy WONG Tsung Yin", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(146, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "NeuroSmart Eyes Air-conditioning Control System" AND e.name = "HKIE Innovation Award 2019" ), "Merit award, Category II – An Innovative Application of Engineering Theories", null, "Category II – An Innovative Application of Engineering Theories", "2019-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Martin LAM Sau Sing, Miss Michelle LAW Ting Fung & Miss Karman PANG Ka Man", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(147, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Young Engineer of the Year" AND e.name = "HKIE Young Engineer of the Year Award 2019" ), "Young Engineer of the Year Award 2019, Young Engineer of the Year Award 2019", null, "Young Engineer of the Year Award 2019", "2019-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ms. Clare Luk", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(148, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Health Signs Monitoring System and Passage Surveillance System under the smart prison solution" AND e.name = "47th International Exhibition of Inventions of Geneva" ), "Gold Medal at the 47th International Exhibition of Inventions of Geneva", null, "Gold Medal at the 47th International Exhibition of Inventions of Geneva", "2019-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(149, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Prompt repairs for footbridge lifts" AND e.name = "The Ombudsman's Awards 2018" ), "Ombudsman's Award 2018, Ombudsman's Awards 2018 for Officers of Public Organisations", null, "Ombudsman's Awards 2018 for Officers of Public Organisations", "2018-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(150, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Electrical Installations" AND e.name = "Global Skills Challenge 2019" ), "First runner-up, \"Electrical Installations\" category and \"Best in Nation\"", null, "\"Electrical Installations\" category and \"Best in Nation\"", "2019-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Yu-tai", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(151, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Refrigeration and Air-conditioning" AND e.name = "Guangzhou Invitation Tournament" ), "First runner-up, \"Refrigeration and Air-conditioning\"", null, "\"Refrigeration and Air-conditioning\"", "2019-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Kwok Chun-ming", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(152, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Supervision of contractors" AND e.name = "24th Considerate Contractors Site Award" ), "24th Considerate Contractors Site Award, 1 Bronze Award and 1 Merit Award in the category of \"Repair, Maintenance, Alteration and Addition\" of Public Works Sites", null, "1 Bronze Award and 1 Merit Award in the category of \"Repair, Maintenance, Alteration and Addition\" of Public Works Sites", "2018-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(153, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Energy management project, i.e. the first combined heat and power electricity generation project in Hong Kong hospitals, jointly developed under the collaboration of the EMSD, the Hong Kong and China Gas Company Limited (Towngas) and the Hospital Authority (HA)." AND e.name = "AEE Regional Award 2018" ), "Regional Energy Project of the Year Award for the Asia-Pacific region, Regional Energy Project of the Year Award for the Asia-Pacific region", null, "Regional Energy Project of the Year Award for the Asia-Pacific region", "2018-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(154, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Use of innovative +technologies solution for sustainable development in the District Cooling System +at Kai Tak Development" AND e.name = "CIC Sustainable Construction Award 2018" ), "Young Practitioner – Excellent Award, Young Practitioner – Excellent Award, under the Construction Industry Council Sustainable Construction Award", null, "Young Practitioner – Excellent Award, under the Construction Industry Council Sustainable Construction Award", "2018-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Miss Law Ting-fung, Michelle", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(155, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. LEUNG Tak Man, Mr. Yip Ping-Keung & Mr. LEUNG Kwong Yiu" AND e.name = "Secretary for the Civil Service's Commendation Award 2018" ), "Secretary for the Civil Service's Commendation Awards, Secretary for the Civil Service's Commendation Awards in 2018", null, "Secretary for the Civil Service's Commendation Awards in 2018", "2018-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. LEUNG Tak Man, Mr. Yip Ping-Keung & Mr. LEUNG Kwong Yiu", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(156, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Lee Wing-chung, Vincent" AND e.name = "VTC Outstanding Apprentices 2018" ), "2018 Outstanding Apprentices", null, "2018 Outstanding Apprentices", "2019-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Lee Wing-chung, Vincent", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(157, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. HoTak-shing, Mr. Suen Wai-tai and Mr. Lau Sen-kit" AND e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2018/19" ), "First runner-up, First runner-up in the Safety Knowledge Competition", null, "First runner-up in the Safety Knowledge Competition", "2019-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. HoTak-shing, Mr. Suen Wai-tai and Mr. Lau Sen-kit", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(158, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18" ), "Corporate Recognition Award, Corporate Recognition Award in the Airport Authority's Technical Services Department +(TSD) Contractors Safety Campaign 2018/19", null, "Corporate Recognition Award in the Airport Authority's Technical Services Department +(TSD) Contractors Safety Campaign 2018/19", "2019-05-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(159, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Project called Caring for the Public, Respecting the Departed for the Mobile Ash Collector and Barcode Verification System" AND e.name = "Civil Service Outstanding Service Award Scheme 2017" ), "2017 Civil Service Outstanding Service Award, Civil Service Outstanding Service Award 2017 Special Citation (Innovation) of the Team Award (Specialised Service)", null, "Civil Service Outstanding Service Award 2017 Special Citation (Innovation) of the Team Award (Specialised Service)", "2017-09-07", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(160, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Project in designing, procuring and installing various life-size mock-ups of ships, trains and aircraft for fire simulation and training in Fire and Ambulance Services Academy" AND e.name = "Civil Service Outstanding Service Award Scheme 2017" ), "2017 Civil Service Outstanding Service Award, Civil Service Outstanding Service Award 2017 Partnership Award Silver Prize in conjunction with Fire Services Department (FSD) and the Architectural Services Department", null, "Civil Service Outstanding Service Award 2017 Partnership Award Silver Prize in conjunction with Fire Services Department (FSD) and the Architectural Services Department", "2017-09-07", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(161, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Addressing stall owners' concerns and difficulties" AND e.name = "The Ombudsman's Awards 2017" ), "Ombudsman's Award 2017, Ombudsman's Award 2017 for Officers of Public Organisations for the lift and escalator replacement programme at public markets and cooked food centres", null, "Ombudsman's Award 2017 for Officers of Public Organisations for the lift and escalator replacement programme at public markets and cooked food centres", "2017-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Ivan Ho Chi-ming", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(162, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD's overall outstanding performance in energy management programmes throughout the years" AND e.name = "AEE Regional Award 2017" ), "AEE Regional Institutional Energy Management Award, AEE Regional Institutional Energy Management Award for the Asia-Pacific region", null, "AEE Regional Institutional Energy Management Award for the Asia-Pacific region", "2017-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(163, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Received the award with Hospital Authority (HA) in recognition of our joint effort in replacing a total of 38 aged conventional chillers in nine public hospitals with highly energy-efficient chillers since 2015" AND e.name = "AEE Regional Award 2017" ), "Regional Energy Project of the Year Award for the Asia -Pacific region from the Association of Energy Engineers (AEE)", null, "Regional Energy Project of the Year Award for the Asia -Pacific region from the Association of Energy Engineers (AEE)", "2017-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(164, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Public Works" AND e.name = "23rd Considerate Contractors Site Award" ), "23rd Considerate Contractors Site Award, Gold Award and the Silver Award in +the 23rd Considerate Contractors Site +Award Scheme", null, "Gold Award and the Silver Award in +the 23rd Considerate Contractors Site +Award Scheme", "2017-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(165, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Lau Chi-ching and Mr. Yeung Hoi-chiu" AND e.name = "VTC Outstanding Apprentices 2017" ), "2017 Outstanding Apprentices Award, Outstanding Apprentices (Merit)", null, "Outstanding Apprentices (Merit)", "2018-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Lau Chi-ching and Mr. Yeung Hoi-chiu", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(166, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit, Mr. Tsang Tak-shun, and Mr Shum Wai-fung" AND e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18" ), "TSD Contractors Safety Campaign Award, 2nd runner-up +in the Safety Video Shooting Competition", null, "2nd runner-up +in the Safety Video Shooting Competition", "2018-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit, Mr. Tsang Tak-shun, and Mr Shum Wai-fung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(167, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit & Mr. Chan Cheuk-nin" AND e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2017/18" ), "TSD Contractors Safety Campaign Award, 2nd runner-up in Best Workplace Competition", null, "2nd runner-up in Best Workplace Competition", "2018-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Mak Kwok-leung, Mr. Tsui Chi-kit & Mr. Chan Cheuk-nin", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(168, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Leung Ling-fung" AND e.name = "Airport Safety Recognition Scheme 2017/18" ), "Best Safety Supervisor Award, Best Safety Supervisor Award", null, "Best Safety Supervisor Award", "2018-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Leung Ling-fung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(169, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "Airport Safety Recognition Scheme 2017/18" ), "Corporate Safety Performance Award, Corporate Safety Performance Award", null, "Corporate Safety Performance Award", "2018-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(170, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chan Siu-lam" AND e.name = "WorldSkills Hong Kong Competition 2017" ), "Gold Award, Gold award in the \"Electrical Installations\" +category", null, "Gold award in the \"Electrical Installations\" +category", "2017-06-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Siu-lam", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(171, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chan Siu-lam" AND e.name = "44th WorldSkills Competition" ), "Medallion for Excellence, Medallion for Excellence in \"Electrical Installations\" category", null, "Medallion for Excellence in \"Electrical Installations\" category", "2017-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Siu-lam", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(172, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Concerted efforts of +EMSD's volunteers for providing household maintenance service to the elderly and +the needy in our community" AND e.name = "Yan Oi Tong Volunteer Award 2017" ), "Compassion Group Volunteer Award", null, "Compassion Group Volunteer Award by Yan Oi Tong", "2017-10-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD Staff Club Volunteer Team", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(173, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chiu Sung-kwong, Mr. Cheung Kam-chin, Mr. Wan Wai-chiu, Randy & Ms. Ho Fung Yin, Agnes" AND e.name = "Secretary for the Civil Service's Commendation Award 2017" ), "Secretary for the Civil Service's Commendation Awards, Secretary for the Civil Service's Commendation Awards in 2017", null, "Secretary for the Civil Service's Commendation Awards in 2017", "2017-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chiu Sung-kwong, Mr. Cheung Kam-chin, Mr. Wan Wai-chiu, Randy & Ms. Ho Fung Yin, Agnes", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(174, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD Annual Report 2015/16" AND e.name = "League of American Communications Professionals (LACP) 2016 Vision Awards" ), "2016 Vision Awards, \"Platinum Award – Government Industry\", Gold Award in the category of \"Best Letter to Shareholders\" in the Asia-Pacific Region at the 2016 Vision Awards, the Top 80 Reports in the Asia-Pacific Region (Ranked 17th), the Top 100 Reports Worldwide (Ranked 50th) and the Top 40 Chinese Reports of 2016", null, "\"Platinum Award – Government Industry\", Gold Award in the category of \"Best Letter to Shareholders\" in the Asia-Pacific Region at the 2016 Vision Awards, the Top 80 Reports in the Asia-Pacific Region (Ranked 17th), the Top 100 Reports Worldwide (Ranked 50th) and the Top 40 Chinese Reports of 2016", "2017-07-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(175, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Siu Hiu-fai, Stanley" AND e.name = "HKIE Young Engineer of the Year Award 2017" ), "Young Engineer of the Year Award 2017, Young Engineer of the Year Award 2017", null, "Young Engineer of the Year Award 2017", "2017-03-22", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Siu Hiu-fai, Stanley", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(176, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Leung Chi-to, Vincent" AND e.name = "HKIE Young Engineer of the Year Award 2017" ), "Young Engineer of the Year Award 2017– Certificate of Merits, Young Engineer of the Year Award 2017 – Certificate of Merits", null, "Young Engineer of the Year Award 2017 – Certificate of Merits", "2017-03-22", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Leung Chi-to, Vincent", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(177, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Ms. Hui Wing-yin" AND e.name = "HKIE Trainee of the Year Award 2016" ), "HKIE's Trainee of the Year Award 2016, First Prize of HKIE's Trainee of the Year Award 2016", null, "First Prize of HKIE's Trainee of the Year Award 2016", "2017-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ms. Hui Wing-yin", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(178, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Yan Fu-ho" AND e.name = "HKIE Trainee of the Year Award 2016" ), "HKIE's Trainee of the Year Award 2016, Third Prize of HKIE's Trainee of the Year Award 2016", null, "Third Prize of HKIE's Trainee of the Year Award 2016", "2017-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Yan Fu-ho", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(179, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Designed a mobile app +named Tap My Dish for the Hong Kong +Blind Union" AND e.name = "HKIE Innovation Award 2017" ), "HKIE Innovation Awards 2017 for Young Members, Grand Prize of the HKIE Innovation Awards 2017 for Young Members - Category I", null, "Grand Prize of the HKIE Innovation Awards 2017 for Young Members - Category I", "2017-03-22", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Cheng Cheuk-tak, Mr. Choi Yiu-fai, Miss Kwok Sin-ming, Mr. Leung Cheuk-fung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(180, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Kwok Chun-ting" AND e.name = "VTC Outstanding Apprentices 2016" ), "2016 Outstanding Apprentices Award, 2016 Outstanding Apprentices Award", null, "2016 Outstanding Apprentices Award", "2017-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Kwok Chun-ting", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(181, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Wong Man-hin" AND e.name = "VTC Outstanding Apprentices 2016" ), "2016 Outstanding Apprentices Award, 2016 Outstanding Apprentices Award", null, "2016 Outstanding Apprentices Award", "2017-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Wong Man-hin", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(182, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Developed a Mobile Smart Terminal to optimise operation and maintenance work on WiFi network related projects" AND e.name = "HKIE Innovation Award 2016" ), "HKIE Innovation Awards 2016, HKIE Innovation Awards for Young Members – Category I - An Invention – Certificate of Merits", null, "HKIE Innovation Awards for Young Members – Category I - An Invention – Certificate of Merits", "2016-02-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Yuen Piu-hung, Mr. Lai Hon-fung & Miss Yuen Wai-shan", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(183, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Chiu Cho-chuen" AND e.name = "9th Outstanding Occupational Safety and Health (OSH) Employees Award" ), "Outstanding OSH Employee Award, Outstanding OSH Employee Award – Bronze (Supervisor Category)", null, "Outstanding OSH Employee Award – Bronze (Supervisor Category)", "2017-02-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chiu Cho-chuen", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(184, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Cheung Lap-chung" AND e.name = "9th Outstanding Occupational Safety and Health (OSH) Employees Award" ), "Outstanding OSH Employee Award, Outstanding OSH Employee Award – Merit (Frontline Staff Category)", null, "Outstanding OSH Employee Award – Merit (Frontline Staff Category)", "2017-02-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Cheung Lap-chung", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(185, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Chui Chi-kit" AND e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2016/17" ), "TSD Contractors Safety Campaign Award, 2nd runner-up in Safety +Quiz in n TSD Contractors Safety Campaign 2016/17", null, "2nd runner-up in Safety +Quiz in n TSD Contractors Safety Campaign 2016/17", "2017-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(186, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Chan Chung-him" AND e.name = "Airport Authority's Technical Services Department (TSD) Contractors Safety Campaign 2016/17" ), "TSD Contractors Safety Campaign Award, 2nd runner-up in Best Workplace Safety Inspection Competition", null, "2nd runner-up in Best Workplace Safety Inspection Competition", "2017-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(187, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chui Chi-kit and Mr. Chan Chung-him" AND e.name = "Airport Safety Recognition Scheme 2016/17" ), "Role Model Safety Behaviour Award, Role Model Safety Behaviour Award at the 2016/17 Airport Safety Recognition Award", null, "Role Model Safety Behaviour Award at the 2016/17 Airport Safety Recognition Award", "2017-03-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chui Chi-kit and Mr. Chan Chung-him", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(188, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD" AND e.name = "Safety Quiz 2016" ), "First runner-up, 1st runner-up in the Corporate Category in the Safety Quiz 2016", null, "1st runner-up in the Corporate Category in the Safety Quiz 2016", "2016-09-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(189, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Supervised works contracts" AND e.name = "22nd Considerate Contractors Site Award" ), "22nd Considerate Contractors Site Award, Silver Award under the Public Works – Model Subcontractor category", null, "Silver Award under the Public Works – Model Subcontractor category", "2016-05-27", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(190, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Supervised works contracts" AND e.name = "22nd Considerate Contractors Site Award" ), "22nd Considerate Contractors Site Award, Two Bronze & Two Merit Awards under the Public Works - Repair, Maintenance, Alteration and Addition Works category", null, "Two Bronze & Two Merit Awards under the Public Works - Repair, Maintenance, Alteration and Addition Works category", "2016-05-27", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(191, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Project of Kowloon City Market Escalator Replacement Works" AND e.name = "The Ombudsman's Awards 2016" ), "Ombudsman's Awards 2016, Ombudsman's Awards 2016 for Officers of Public Organisations", null, "Ombudsman's Awards 2016 for Officers of Public Organisations", "2016-10-27", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Jairus Wong Kwun-ping", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(192, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Sun Wing-leung, Mr. Kung Chi-leung, Mr. Szeto Wing-sum," AND e.name = "Secretary for the Civil Service's Commendation Award 2016" ), "Secretary for the Civil Service's Commendation Awards, Secretary for the Civil Service's Commendation Awards in 2016", null, "Secretary for the Civil Service's Commendation Awards in 2016", "2016-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Sun Wing-leung, Mr. Kung Chi-leung, Mr. Szeto Wing-sum,", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(193, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Chan Siu-lam and Mr. Law Wai-yu" AND e.name = "WorldSkills Hong Kong Competition 2016" ), "WorldSkills Hong Kong Competition 2016, \"Electrical Installations\" +category", null, "\"Electrical Installations\" +category", "2016-06-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Chan Siu-lam and Mr. Law Wai-yu", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(194, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr. Lau Chi-ching +and Mr. Yu Fung-yuen" AND e.name = "WorldSkills Hong Kong Competition 2016" ), "WorldSkills Hong Kong Competition 2016, \"Refrigeration and Air Conditioning\" category", null, "\"Refrigeration and Air Conditioning\" category", "2016-06-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Lau Chi-ching and Mr. Yu Fung-yuen", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(195, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Developed iVacancy System through the use of sensors installed in seats to distinguish the vacancy status by mobile apps" AND e.name = "Hong Kong Electronics Project Competition 2016" ), "Second runner-up, Second runner-up in the \"Hong Kong Electronics Project Competition 2016 – Internet of Things Development for Smart City\"", null, "Second runner-up in the \"Hong Kong Electronics Project Competition 2016 – Internet of Things Development for Smart City\"", "2016-03-29", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr. Leung Cheuk-fung and Mr. Choi Yiu-fai", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(196, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD Website" AND e.name = "Best.hk Website Awards 2015" ), "Best.hk Website Awards 2015, Honourable Mention in the \"Best.hk Website Awards 2015\"", null, "Honourable Mention in the \"Best.hk Website Awards 2015\"", "2016-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report", "Voice Link" ))), +(197, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Lam Cheuk-ki" AND e.name = "VTC Outstanding Apprentices 2015" ), "2015 Outstanding Apprentice Award, Outstanding Apprentice award", null, "Outstanding Apprentice award", "2016-03-23", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Lam Cheuk-ki", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(198, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Mr Chow Wai-lun" AND e.name = "VTC Outstanding Apprentices 2015" ), "2015 Outstanding Apprentice Award, Apprentice of Excellent Performance award", null, "Apprentice of Excellent Performance award", "2016-03-23", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Mr Chow Wai-lun", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(199, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Designed Portable Power +Cubicle for airfield ground lighting (AGL) system to provide temporary power +supply for the AGL system efficiently and safely" AND e.name = "Airport Safety Recognition Scheme 2015/16" ), "Airport Safety Recognition Award, Good Safety Suggestion in Airport Safety Recognition Award", null, "Good Safety Suggestion in Airport Safety Recognition Award", "2016-04-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(200, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD HQs" AND e.name = "BEAM Plus Certificate 2022" ), "BEAM Plus Existing Building V1.2 Final Platinum Rating, BEAM Plus Existing Building V1.2 Final Platinum Rating", null, "BEAM Plus Existing Building V1.2 Final Platinum Rating", "2022-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(201, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD HQs" AND e.name = "BEAM Plus Certificate 2022" ), "BEAM Plus Neighbourhood (Pilot Version) Platinum, Neighbourhood (Pilot Version) Platinum", null, "Neighbourhood (Pilot Version) Platinum", "2022-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(202, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "EMSD HQs" AND e.name = "Caring Company" ), "Caring Organisation Logo, Caring Organisation Logo", null, "Caring Organisation Logo", "2015-01-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Annual Report" ))), +(203, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Electric Locks Security System" AND e.name = "ICT Awards 2014" ), "ICT Awards 2014, Best Innovation (Entrepreneurial Innovation) Special Mention", null, "Best Innovation (Entrepreneurial Innovation) Special Mention", "2014-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(204, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Central Command System of Marine Police" AND e.name = "ICT Awards 2011" ), "ICT Awards 2011, Award of the Year and Best Public Service Application Grand Award", null, "Award of the Year and Best Public Service Application Grand Award", "2011-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(205, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Self-Developed Mobile CCTV System" AND e.name = "ICT Awards 2007" ), "ICT Awards 2007, Bronze Award - Best Public Service Application (Small Scale Project)", null, "Bronze Award - Best Public Service Application (Small Scale Project)", "2007-11-01", null, null, (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "EMSD", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( "Voice Link" ))), +(206, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Use of Artificial Intelligence, Dronen and Remote Sensors for Tower Inspections" AND e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Silver Award", null, null, "2023-12-08", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Chi Keung, Eric LAI (POINNO22)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(207, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "City-level Robotic Inspection and Diagnostic System for Building Systems" AND e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Silver Award", null, null, "2023-12-08", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ka Tai LAU (EGESSD4)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(208, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "A.I. Real-time Lift Door Inspection System" AND e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Silver Award", null, null, "2023-12-08", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Jason Junsing AU (BSEGESF3)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(209, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Antifouling Seawater Screen" AND e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Gold Award", null, null, "2023-12-08", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Lung Wai, Chris CHAN (POINNO32)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(210, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "AI Voicebot Training System with Performance Evaluation for Call Centre Agents" AND e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Silver Award", null, null, "2023-12-08", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Man Yee CHENG (ECC2)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(211, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "3D printed Trap case and T-Shaped Bait Box for rodent control and management" AND e.name = "3rd ASIA EXHIBITION OF INNOVATIONS AND INVENTIONS HONG KONG" ), "Silver Award", null, null, "2023-12-08", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "Ming Lui YEUNG (EEBIM4)", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(212, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Tramway Derailment and Collision Prevention System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Special Award", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(213, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Antifouling Seawater Screen" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Gold Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(214, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Integrated Self-sustained renewable-Energy Explorer (iSEE)" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Gold Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(215, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Tramway Derailment and Collision Prevention System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Gold Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(216, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "HKeToll - Free-flow Tolling System in HK" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(217, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Integrated Intelligent Communication System in Correctional Institutions/Facilities" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(218, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Intelligent Lift Door Inspector" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(219, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Overhead Rail Servicing Robot" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(220, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Toilet Management System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(221, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "LPG Tank Inspection & Data Analysis with Intelligent Tanker Robot" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(222, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Train-borne Railway Infrastructure Inspection System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Silver medals", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(223, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Smart Drainage System for Flood Monitoring using LPWAN IoT sensors and LoRaCam" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(224, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "City-level Robotic Inspection & Diagnostic System for Building Systems" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(225, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Artificial Intelligence Video Analytics System for Escalator Monitoring in Wet Markets of Hong Kong" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(226, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Speech-to-text Hub for Real-time Monitoring and Analysis in Customer Service Calls" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(227, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "LoRa Mesh Technology for Extending the IoT Network Coverage to the Rural Areas" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(228, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Robotics-Enabled Fogger on Mosquito Control" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(229, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Electrical Doctor – Real-time Health Diagnosis for Electricity Supply System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(230, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Solar Harvest Map (SHM)" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(231, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Digital Log-books for Lifts and Escalators" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(232, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Gas Pipe Health AI Prediction Model" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(233, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Rail Track Collision Object Detection System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))), +(234, (SELECT DISTINCT a.id FROM application a LEFT JOIN event e ON e.id = a.eventId WHERE a.name = "Passenger Misbehaviour Detection System" AND e.name = "48th International Exhibition of Inventions of Geneva" ), "Bronze Medal", null, null, "2023-03-01", null, "Certificate, Medal", (SELECT DISTINCT c.id FROM category c WHERE c.name = "NA" ), "NA", (SELECT JSON_ARRAYAGG(JSON_OBJECT('id', pc.id, 'name', pc.name, 'label', pc.name)) FROM promotion_channel pc WHERE pc.name IN ( null ))); + + +--comment: file_ref +INSERT INTO file_ref (id, refId, refType, remarks) VALUES +(1, 6, "award", "https://www.info.gov.hk/gia/general/202209/02/P2022090200383.htm"), +(2, 10, "award", "https://www.autodesk.com.hk/hkbimawards/2022"), +(3, 17, "award", "https://www.bim.cic.hk/en/events/page/Details_CBA2022?back=%2Fen%2Fevents%2Fpage%2FCBA2022"), +(4, 18, "award", "https://www.bim.cic.hk/en/events/page/Details_CBA2022?back=%2Fen%2Fevents%2Fpage%2FCBA2022"), +(5, 19, "award", "https://www.bim.cic.hk/en/events/page/Details_CBA2022?back=%2Fen%2Fevents%2Fpage%2FCBA2022"), +(6, 35, "award", "https://www.info.gov.hk/gia/general/202203/29/P2022032900265.htm"), +(7, 36, "award", "https://www.hongkongairport.com/iwov-resources/html/hkairportnews/2022issue168/hkairportnews2022issue168.pdf"), +(8, 54, "award", "https://www.hongkongairport.com/iwov-resources/html/hkairportnews/2022issue168/hkairportnews2022issue168.pdf"), +(9, 77, "award", "https://zh-hk.facebook.com/ASHRAE.HKC/posts/we-are-pleased-to-announce-the-awardees-of-the-ashrae-hong-kong-chapter-technolo/3981834485249059/"), +(10, 78, "award", "https://zh-hk.facebook.com/ASHRAE.HKC/posts/we-are-pleased-to-announce-the-awardees-of-the-ashrae-hong-kong-chapter-technolo/3981834485249059/"), +(11, 80, "award", "https://www.hkma.org.hk/training-award/pdf/the_standard_20211025.pdf"), +(12, 81, "award", "https://www.hkma.org.hk/training-award/pdf/the_standard_20211025.pdf"), +(13, 82, "award", "https://www.hkma.org.hk/training-award/pdf/the_standard_20211025.pdf"), +(14, 83, "award", "https://www.hkma.org.hk/training-award/pdf/the_standard_20211025.pdf"), +(15, 84, "award", "https://www.hkma.org.hk/training-award/pdf/the_standard_20211025.pdf"), +(16, 114, "award", "Ihttps://www.hkictawards.hk/pdf/HKICTA_2020_General_Award_Programme.pdf"), +(17, 204, "award", "Ihttps://www.hkictawards.hk/award_en.php?year=20062015#"); + diff --git a/src/main/resources/db/changelog/changes/02_enhancement/01_create_enhancement_table.sql b/src/main/resources/db/changelog/changes/02_enhancement/01_create_enhancement_table.sql new file mode 100644 index 0000000..4480cc3 --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/01_create_enhancement_table.sql @@ -0,0 +1,76 @@ +--liquibase formatted sql +--changeset system:create enhancement table + +--comment: apr_appreciation +CREATE TABLE `apr_appreciation` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` varchar(30) DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` varchar(30) DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + + `sbuIds` json NOT NULL, + `receiptDate` datetime NOT NULL, + `description` TEXT NOT NULL, + `clientDepartment` VARCHAR(255) DEFAULT NULL, + `clientOrganization` VARCHAR(255) DEFAULT NULL, + `clientFullname` VARCHAR(50) DEFAULT NULL, + `clientPost` VARCHAR(50) DEFAULT NULL, + `venue` VARCHAR(255) DEFAULT NULL, + `clientReplyDate` datetime DEFAULT NULL, + `staffReplyDate` datetime DEFAULT NULL, + `noOfStaff` int(11) DEFAULT NULL, + `lnReceiptDate` datetime NOT NULL, + `aprCategoryId` int(11) NOT NULL , + `remarks` VARCHAR(255) DEFAULT NULL, + `importDate` datetime DEFAULT NULL, + + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +--comment: apr_sbu +CREATE TABLE `apr_sbu` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` varchar(30) DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` varchar(30) DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + + `name` VARCHAR(255) NOT NULL, + + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +--comment: apr_client_department +CREATE TABLE `apr_client_department` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` varchar(30) DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` varchar(30) DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + + `name` VARCHAR(255) NOT NULL, + + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +--comment: apr_category +CREATE TABLE `apr_category` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` varchar(30) DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` varchar(30) DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + + `name` VARCHAR(255) NOT NULL, + + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/src/main/resources/db/changelog/changes/02_enhancement/02_import_client_department.sql b/src/main/resources/db/changelog/changes/02_enhancement/02_import_client_department.sql new file mode 100644 index 0000000..8ec447f --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/02_import_client_department.sql @@ -0,0 +1,108 @@ +--liquibase formatted sql +--changeset system:import apr_client_department +INSERT INTO apr_client_department (name) VALUES ("Agriculture, Fisheries and Conservation Department"); +INSERT INTO apr_client_department (name) VALUES ("Architectural Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Audit Commission"); +INSERT INTO apr_client_department (name) VALUES ("Auxiliary Medical Service"); +INSERT INTO apr_client_department (name) VALUES ("Buildings Department"); +INSERT INTO apr_client_department (name) VALUES ("Census and Statistics Department"); +INSERT INTO apr_client_department (name) VALUES ("Chief Executive's Office"); +INSERT INTO apr_client_department (name) VALUES ("Chief Executive's Policy Unit"); +INSERT INTO apr_client_department (name) VALUES ("Chief Secretary for Administration's Office"); +INSERT INTO apr_client_department (name) VALUES ("Chief Secretary for Administration's Private Office"); +INSERT INTO apr_client_department (name) VALUES ("Civil Aid Service"); +INSERT INTO apr_client_department (name) VALUES ("Civil Aviation Department"); +INSERT INTO apr_client_department (name) VALUES ("Civil Engineering and Development Department"); +INSERT INTO apr_client_department (name) VALUES ("Civil Service Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Commerce and Economic Development Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Companies Registry"); +INSERT INTO apr_client_department (name) VALUES ("Competition Commission"); +INSERT INTO apr_client_department (name) VALUES ("Constitutional and Mainland Affairs Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Consumer Council"); +INSERT INTO apr_client_department (name) VALUES ("Correctional Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Culture, Sports and Tourism Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Customs and Excise Department"); +INSERT INTO apr_client_department (name) VALUES ("Department of Health"); +INSERT INTO apr_client_department (name) VALUES ("Department of Justice"); +INSERT INTO apr_client_department (name) VALUES ("Development Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Drainage Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Education Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Electrical & Mechanical Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Employees Retraining Board"); +INSERT INTO apr_client_department (name) VALUES ("Environmental Protection Department"); +INSERT INTO apr_client_department (name) VALUES ("Environment and Ecology Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Equal Opportunities Commission (EOC)"); +INSERT INTO apr_client_department (name) VALUES ("Estate Agents Authority"); +INSERT INTO apr_client_department (name) VALUES ("Financial Secretary's Office"); +INSERT INTO apr_client_department (name) VALUES ("Financial Secretary's Private Office"); +INSERT INTO apr_client_department (name) VALUES ("Financial Services and the Treasury Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Fire Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Food and Environmental Hygiene Department"); +INSERT INTO apr_client_department (name) VALUES ("Government Flying Service"); +INSERT INTO apr_client_department (name) VALUES ("Government Laboratory"); +INSERT INTO apr_client_department (name) VALUES ("Government Logistics Department"); +INSERT INTO apr_client_department (name) VALUES ("Government Property Agency"); +INSERT INTO apr_client_department (name) VALUES ("Guardianship Board"); +INSERT INTO apr_client_department (name) VALUES ("Health Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Highways Department"); +INSERT INTO apr_client_department (name) VALUES ("Home Affairs Department"); +INSERT INTO apr_client_department (name) VALUES ("Home and Youth Affairs Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Arts Development Council"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Council for Accreditation of Academic and Vocational Qualifications"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Council on Smoking and Health"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Economic, Trade and Cultural Office (Taiwan)"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Export Credit Insurance Corporation"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Monetary Authority"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Observatory"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Police Force"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Productivity Council"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Science and Technology Parks Corporation"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Sports Institute Limited"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Tourism Board"); +INSERT INTO apr_client_department (name) VALUES ("Hong Kong Trade Development Council"); +INSERT INTO apr_client_department (name) VALUES ("Hospital Authority"); +INSERT INTO apr_client_department (name) VALUES ("Housing Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Housing Department"); +INSERT INTO apr_client_department (name) VALUES ("Immigration Department"); +INSERT INTO apr_client_department (name) VALUES ("Independent Commission Against Corruption"); +INSERT INTO apr_client_department (name) VALUES ("Independent Police Complaints Council"); +INSERT INTO apr_client_department (name) VALUES ("Information Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Inland Revenue Department"); +INSERT INTO apr_client_department (name) VALUES ("Innovation, Technology and Industry Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Intellectual Property Department"); +INSERT INTO apr_client_department (name) VALUES ("Invest Hong Kong"); +INSERT INTO apr_client_department (name) VALUES ("Joint Secretariat SCS"); +INSERT INTO apr_client_department (name) VALUES ("Judiciary"); +INSERT INTO apr_client_department (name) VALUES ("Labour and Welfare Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Labour Department"); +INSERT INTO apr_client_department (name) VALUES ("Land Registry"); +INSERT INTO apr_client_department (name) VALUES ("Lands Department"); +INSERT INTO apr_client_department (name) VALUES ("Legal Aid Department"); +INSERT INTO apr_client_department (name) VALUES ("Legal Aid Services Council"); +INSERT INTO apr_client_department (name) VALUES ("Legislative Council Secretariat"); +INSERT INTO apr_client_department (name) VALUES ("Leisure and Cultural Services Department"); +INSERT INTO apr_client_department (name) VALUES ("Mandatory Provident Fund Schemes Authority"); +INSERT INTO apr_client_department (name) VALUES ("Marine Department"); +INSERT INTO apr_client_department (name) VALUES ("Office of Former Chief Executives"); +INSERT INTO apr_client_department (name) VALUES ("Office of the Communications Authority"); +INSERT INTO apr_client_department (name) VALUES ("Office of The Ombudsman"); +INSERT INTO apr_client_department (name) VALUES ("Office of the Privacy Commissioner for Personal Data"); +INSERT INTO apr_client_department (name) VALUES ("Official Receiver's Office"); +INSERT INTO apr_client_department (name) VALUES ("Planning Department"); +INSERT INTO apr_client_department (name) VALUES ("Post Office"); +INSERT INTO apr_client_department (name) VALUES ("Public Service Commission"); +INSERT INTO apr_client_department (name) VALUES ("Radio Television Hong Kong"); +INSERT INTO apr_client_department (name) VALUES ("Rating and Valuation Department"); +INSERT INTO apr_client_department (name) VALUES ("Registration and Electoral Office"); +INSERT INTO apr_client_department (name) VALUES ("Secretariat, Commissioner on Interception of Communications and Surveillance"); +INSERT INTO apr_client_department (name) VALUES ("Securities and Futures Commission"); +INSERT INTO apr_client_department (name) VALUES ("Security Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Social Welfare Department"); +INSERT INTO apr_client_department (name) VALUES ("Trade and Industry Department"); +INSERT INTO apr_client_department (name) VALUES ("Transport and Logistics Bureau"); +INSERT INTO apr_client_department (name) VALUES ("Transport Department"); +INSERT INTO apr_client_department (name) VALUES ("Treasury"); +INSERT INTO apr_client_department (name) VALUES ("University Grants Committee Secretariat"); +INSERT INTO apr_client_department (name) VALUES ("Vocational Training Council"); +INSERT INTO apr_client_department (name) VALUES ("Water Supplies Department"); +INSERT INTO apr_client_department (name) VALUES ("Working Family and Student Financial Assistance Agency"); \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/02_enhancement/03_insert_new_authorities.sql b/src/main/resources/db/changelog/changes/02_enhancement/03_insert_new_authorities.sql new file mode 100644 index 0000000..a9db09f --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/03_insert_new_authorities.sql @@ -0,0 +1,10 @@ +--liquibase formatted sql + +--changeset jason:insert new authorities +--comment: insert new authorities +INSERT INTO `authority` VALUES +(23,'VIEW_APPRECIATION','View Appreciation','Appreciation','Allow to view appreciation'), +(24,'MAINTAIN_APPRECIATION','Maintain Appreciation','Appreciation','Allow to maintain appreciation'), +(25,'GENERATE_REPORTS','Generate reports','Appreciation','Allow to generate reports'), +(26,'MAINTAIN_CLIENT_DEPARTMENT','Maintain Client Department','System Administration','Allow to maintain client department') +; diff --git a/src/main/resources/db/changelog/changes/02_enhancement/04_update_client_department.sql b/src/main/resources/db/changelog/changes/02_enhancement/04_update_client_department.sql new file mode 100644 index 0000000..cb1486a --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/04_update_client_department.sql @@ -0,0 +1,7 @@ +--liquibase formatted sql + +--changeset system:update apr_client_department +--comment: update department name +UPDATE apr_client_department acd +SET acd.name = "Electrical & Mechanical Services Department" +WHERE acd.name = "Electrical & Mechanical Services Department" \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/02_enhancement/05_insert_new_authority.sql b/src/main/resources/db/changelog/changes/02_enhancement/05_insert_new_authority.sql new file mode 100644 index 0000000..d8ac750 --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/05_insert_new_authority.sql @@ -0,0 +1,15 @@ +--liquibase formatted sql + +--changeset jason:insert authority +--comment: insert authority + +INSERT INTO `authority` VALUES + (27,'VIEW_DASHBOARD','View Dashboard','Dashboard','Allow to view dashboard'), + (28,'VIEW_REMINDER','View Reminder','Reminder','Allow to view reminder'), + (29,'MAINTAIN_SEARCH_TEMPLATE','Maintain Search Template','Search Template','Allow to maintain template'); + +INSERT INTO `group_authority` VALUES +(1,27),(1,28),(1,29), +(2,27),(2,28),(2,29), +(3,27),(3,28),(3,29), +(4,27),(4,28),(4,29); diff --git a/src/main/resources/db/changelog/changes/02_enhancement/06_update_client_post_size.sql b/src/main/resources/db/changelog/changes/02_enhancement/06_update_client_post_size.sql new file mode 100644 index 0000000..e078cc9 --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/06_update_client_post_size.sql @@ -0,0 +1,5 @@ +--liquibase formatted sql + +--changeset jason:update client post size +--comment: update client post size +ALTER TABLE apr_appreciation MODIFY COLUMN clientPost varchar(255) DEFAULT NULL; diff --git a/src/main/resources/db/changelog/changes/02_enhancement/07_update_client_full_name_size.sql b/src/main/resources/db/changelog/changes/02_enhancement/07_update_client_full_name_size.sql new file mode 100644 index 0000000..c7ab16a --- /dev/null +++ b/src/main/resources/db/changelog/changes/02_enhancement/07_update_client_full_name_size.sql @@ -0,0 +1,5 @@ +--liquibase formatted sql + +--changeset terence:update client fullname size +--comment: update client fullname size +ALTER TABLE apr_appreciation MODIFY COLUMN clientFullname varchar(255) DEFAULT NULL; diff --git a/src/main/resources/db/changelog/changes/03_CR002/01_add_import_date_column.sql b/src/main/resources/db/changelog/changes/03_CR002/01_add_import_date_column.sql new file mode 100644 index 0000000..ca3e70e --- /dev/null +++ b/src/main/resources/db/changelog/changes/03_CR002/01_add_import_date_column.sql @@ -0,0 +1,8 @@ +--liquibase formatted sql + +--changeset jason:add import date column +--comment: add import date column +ALTER TABLE event ADD importDate DATETIME NULL; +ALTER TABLE application ADD importDate DATETIME NULL; +ALTER TABLE award ADD importDate DATETIME NULL; +ALTER TABLE file_ref ADD importDate DATETIME NULL; diff --git a/src/main/resources/db/changelog/changes/03_CR002/02_add_mass_import_authority.sql b/src/main/resources/db/changelog/changes/03_CR002/02_add_mass_import_authority.sql new file mode 100644 index 0000000..8e97ea9 --- /dev/null +++ b/src/main/resources/db/changelog/changes/03_CR002/02_add_mass_import_authority.sql @@ -0,0 +1,6 @@ +--liquibase formatted sql + +--changeset jason:insert mass import authority +--comment: insert mass import authority +INSERT INTO authority VALUES + (30,'MASS_IMPORT_AWARD','Import New Event, Application and Award','Award','Allow to import new set of event, application and award records'); diff --git a/src/main/resources/db/changelog/changes/04_lionerTest/01_add_client_table.sql b/src/main/resources/db/changelog/changes/04_lionerTest/01_add_client_table.sql new file mode 100644 index 0000000..b1b9089 --- /dev/null +++ b/src/main/resources/db/changelog/changes/04_lionerTest/01_add_client_table.sql @@ -0,0 +1,30 @@ +--liquibase formatted sql + +--changeset kelvin:add client table + +CREATE TABLE `client` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `createdBy` varchar(30) DEFAULT NULL, + `version` int NOT NULL DEFAULT '0', + `modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modifiedBy` varchar(30) DEFAULT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT '0', + + `fullname` VARCHAR(90) NOT NULL, + `lastname` VARCHAR(30), + `firstname` VARCHAR(60), + `title` VARCHAR(60), + `email` VARCHAR(120), + `phone1` VARCHAR(30), + `phone2` VARCHAR(30), + `joinDate` datetime NOT NULL, + `caseManagerId` int, + `consultantId` int, + `remarks` VARCHAR(600), + + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `lionerdb`.`client` (`fullname`, `lastname`, `firstname`, `title`, `email`, `phone1`, `phone2`, `joinDate`, `remarks`) VALUES ('Chan Tai Ming', 'Chan', 'Tai Ming', 'Mr', 'ctm@mail.com', '30187234', '91092357', '2025-04-13', 'Testing'); +INSERT INTO `lionerdb`.`client` (`fullname`, `lastname`, `firstname`, `title`, `email`, `phone1`, `joinDate`, `remarks`) VALUES ('Testing Wong', 'Wong', 'Testing', 'Ms', 'tw@mail.com', '23812238', '2025-06-08', 'Testing2'); \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml new file mode 100644 index 0000000..b5832ba --- /dev/null +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -0,0 +1,3 @@ +databaseChangeLog: + - includeAll: + path: classpath:/db/changelog/changes \ No newline at end of file diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..058f38c --- /dev/null +++ b/src/main/resources/i18n/messages.properties @@ -0,0 +1,3 @@ +USER.resetPwd.subject=New Password +USER.newAc.subject=New Account +DAILY_REPORT.subject=Daily Maintanance Report \ No newline at end of file diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties new file mode 100644 index 0000000..058f38c --- /dev/null +++ b/src/main/resources/i18n/messages_en.properties @@ -0,0 +1,3 @@ +USER.resetPwd.subject=New Password +USER.newAc.subject=New Account +DAILY_REPORT.subject=Daily Maintanance Report \ No newline at end of file diff --git a/src/main/resources/i18n/messages_zh_CN.properties b/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..8329c7f --- /dev/null +++ b/src/main/resources/i18n/messages_zh_CN.properties @@ -0,0 +1,3 @@ +USER.resetPwd.subject=新密码 +USER.newAc.subject=新用户 +DAILY_REPORT.subject=日常维护报告 \ No newline at end of file diff --git a/src/main/resources/i18n/messages_zh_TW.properties b/src/main/resources/i18n/messages_zh_TW.properties new file mode 100644 index 0000000..d6903c5 --- /dev/null +++ b/src/main/resources/i18n/messages_zh_TW.properties @@ -0,0 +1,3 @@ +USER.resetPwd.subject=新密碼 +USER.newAc.subject=新用戶 +DAILY_REPORT.subject=日常維護報告 \ No newline at end of file diff --git a/src/main/resources/ldap-test-users.ldif b/src/main/resources/ldap-test-users.ldif new file mode 100644 index 0000000..4561829 --- /dev/null +++ b/src/main/resources/ldap-test-users.ldif @@ -0,0 +1,37 @@ +dn: dc=springframework,dc=org +objectClass: top +objectClass: domain +dc: springframework + +dn: O=EMSD,dc=springframework,dc=org +objectClass: organization +o: EMSD + +dn: uid=user1,O=EMSD,dc=springframework,dc=org +objectClass: user +cn: user1 +userPassword: userPass1 +mail: user1@email.com + +dn: uid=user2,O=EMSD,dc=springframework,dc=org +objectClass: user +cn: user2 +userPassword: userPass2 +mail: user2@email.com + +dn: uid=ITMCC1,O=EMSD,dc=springframework,dc=org +objectClass: user +cn: ITMCC1 +userPassword: mms1234 +mail: ITMCC1@email.com + +dn: O=EMSD2,dc=springframework,dc=org +objectClass: organization +o: EMSD2 + +dn: uid=user3,O=EMSD2,dc=springframework,dc=org +objectClass: user +cn: user3 +userPassword: userPass3 +mail: user3@email.com + diff --git a/src/main/resources/log4j2-prod-linux.yml b/src/main/resources/log4j2-prod-linux.yml new file mode 100644 index 0000000..137c831 --- /dev/null +++ b/src/main/resources/log4j2-prod-linux.yml @@ -0,0 +1,54 @@ +Configutation: + name: Prod-Default + Properties: + Property: + name: log_location + value: /usr/springboot/logs/ + Appenders: + RollingFile: + - name: AllRollingFile_Appender + fileName: ${log_location}lioner-all.log + filePattern: ${log_location}lioner-all-%d{yyyy-MM-dd}.log.gz + PatternLayout: + Pattern: "%d %p [%l] - %m%n" + Policies: + TimeBasedTriggeringPolicy: + interval: 1 + modulate: true + DefaultRolloverStrategy: + Delete: + basePath: ${log_location} + maxDepth: 1 + IfFileName: + glob: lioner-all-*.log.gz + IfLastModified: + age: P40D + - name: ReminderRollingFile_Appender + fileName: ${log_location}lioner-reminder.log + filePattern: ${log_location}lioner-reminder-%d{yyyy-MM-dd}.log.gz + PatternLayout: + Pattern: "%d %p [%l] - %m%n" + Policies: + TimeBasedTriggeringPolicy: + interval: 1 + modulate: true + DefaultRolloverStrategy: + Delete: + basePath: ${log_location} + maxDepth: 1 + IfFileName: + glob: lioner-reminder-*.log.gz + IfLastModified: + age: P7D + Loggers: + Logger: + - name: com.ffii.lioner.modules.lioner.service.TodoReminderService + level: info + additivity: false + AppenderRef: + - ref: ReminderRollingFile_Appender + + Root: + level: info + AppenderRef: + - ref: AllRollingFile_Appender \ No newline at end of file diff --git a/src/main/resources/log4j2-prod-win.yml b/src/main/resources/log4j2-prod-win.yml new file mode 100644 index 0000000..2cf14e1 --- /dev/null +++ b/src/main/resources/log4j2-prod-win.yml @@ -0,0 +1,23 @@ +Configutation: + name: Prod-Default + Properties: + Property: + name: log_location + value: C:/workspace/ + Appenders: + RollingFile: + name: RollingFile_Appender + fileName: ${log_location}lioner-all.log + filePattern: ${log_location}lioner-all.log.%i.gz + PatternLayout: + Pattern: "%d %p [%l] - %m%n" + Policies: + SizeBasedTriggeringPolicy: + size: 4096KB + DefaultRollOverStrategy: + max: 99 + Loggers: + Root: + level: info + AppenderRef: + - ref: RollingFile_Appender \ No newline at end of file diff --git a/src/main/resources/log4j2.yml b/src/main/resources/log4j2.yml new file mode 100644 index 0000000..1d9a0cd --- /dev/null +++ b/src/main/resources/log4j2.yml @@ -0,0 +1,17 @@ +Configutation: + name: Default + Properties: + Property: + name: log_pattern + value: "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex" + Appenders: + Console: + name: Console_Appender + target: SYSTEM_OUT + PatternLayout: + pattern: ${log_pattern} + Loggers: + Root: + level: info + AppenderRef: + - ref: Console_Appender \ No newline at end of file diff --git a/src/main/resources/templates/mail/applicationAnnouncement.ftl b/src/main/resources/templates/mail/applicationAnnouncement.ftl new file mode 100644 index 0000000..143e44b --- /dev/null +++ b/src/main/resources/templates/mail/applicationAnnouncement.ftl @@ -0,0 +1,77 @@ + + + + + + + + + <#assign arsLink>${arsLink} +
+
+

Dear ${receiver},

+ +

Friendly remind you the date of result announcement for below event. :

+ +
+
Event Name
+
${eventName}
+ +
+
+
Application
+
${applicationDetail}
+
+
+
Announcement Date
+
${announcementDate}
+
+ + +

You may click here to update your achievements at Award Registration System. Thank you for your participation.

+
+

Best Regards,

+

${subDivision}

+

${division}

+

${sender}

+
+ +
+ + diff --git a/src/main/resources/templates/mail/applicationReminder.ftl b/src/main/resources/templates/mail/applicationReminder.ftl new file mode 100644 index 0000000..6865694 --- /dev/null +++ b/src/main/resources/templates/mail/applicationReminder.ftl @@ -0,0 +1,72 @@ + + + + + + + + + <#assign arsLink>${arsLink} +
+
+

Dear ${receiver},

+ +

Please note below event will be opened for application:

+ +
+
Event Name
+
${eventName}
+ +
+
+
Application Deadline
+
${applicationDeadLine}
+
+ + +

Please click here to create application at Award Registration System. Thank You.

+

Best Regards,

+

${subDivision}

+

${division}

+

${sender}

+
+ +
+ + diff --git a/src/main/resources/templates/mail/content.ftl b/src/main/resources/templates/mail/content.ftl new file mode 100644 index 0000000..6837461 --- /dev/null +++ b/src/main/resources/templates/mail/content.ftl @@ -0,0 +1,10 @@ + + + + + + + + ${content} + + diff --git a/src/main/resources/templates/mail/newUser.ftl b/src/main/resources/templates/mail/newUser.ftl new file mode 100644 index 0000000..69c0d38 --- /dev/null +++ b/src/main/resources/templates/mail/newUser.ftl @@ -0,0 +1,40 @@ + + + + + + + + +
+
+
+

New User

+

Username: ${username}

+

Password: ${password}

+
+
+
+ + diff --git a/src/main/resources/templates/mail/newUser_zh-CN.ftl b/src/main/resources/templates/mail/newUser_zh-CN.ftl new file mode 100644 index 0000000..e1dd381 --- /dev/null +++ b/src/main/resources/templates/mail/newUser_zh-CN.ftl @@ -0,0 +1,40 @@ + + + + + + + + +
+
+
+

新用户

+

用户名称: ${username}

+

密码: ${password}

+
+
+
+ + diff --git a/src/main/resources/templates/mail/newUser_zh-TW.ftl b/src/main/resources/templates/mail/newUser_zh-TW.ftl new file mode 100644 index 0000000..2b6f832 --- /dev/null +++ b/src/main/resources/templates/mail/newUser_zh-TW.ftl @@ -0,0 +1,40 @@ + + + + + + + + +
+
+
+

新用戶

+

用戶名稱: ${username}

+

密碼: ${password}

+
+
+
+ + diff --git a/src/main/resources/templates/mail/resetPwd.ftl b/src/main/resources/templates/mail/resetPwd.ftl new file mode 100644 index 0000000..2c6edaa --- /dev/null +++ b/src/main/resources/templates/mail/resetPwd.ftl @@ -0,0 +1,40 @@ + + + + + + + + +
+
+
+

Reset Password

+

Username: ${username}

+

New Password: ${password}

+
+
+
+ + diff --git a/src/main/resources/templates/mail/resetPwd_zh-CN.ftl b/src/main/resources/templates/mail/resetPwd_zh-CN.ftl new file mode 100644 index 0000000..44423c4 --- /dev/null +++ b/src/main/resources/templates/mail/resetPwd_zh-CN.ftl @@ -0,0 +1,40 @@ + + + + + + + + +
+
+
+

重置密码

+

用户名称: ${username}

+

新密码: ${password}

+
+
+
+ + diff --git a/src/main/resources/templates/mail/resetPwd_zh-TW.ftl b/src/main/resources/templates/mail/resetPwd_zh-TW.ftl new file mode 100644 index 0000000..42e1bba --- /dev/null +++ b/src/main/resources/templates/mail/resetPwd_zh-TW.ftl @@ -0,0 +1,40 @@ + + + + + + + + +
+
+
+

重置密碼

+

用戶名稱: ${username}

+

新密碼: ${password}

+
+
+
+ + diff --git a/src/main/resources/templates/report/Appreciation_Records_Import_Template_v1.0.xlsx b/src/main/resources/templates/report/Appreciation_Records_Import_Template_v1.0.xlsx new file mode 100644 index 0000000..2a35539 Binary files /dev/null and b/src/main/resources/templates/report/Appreciation_Records_Import_Template_v1.0.xlsx differ diff --git a/src/main/resources/templates/report/Award_Record_Import_Template_v1.0.xlsx b/src/main/resources/templates/report/Award_Record_Import_Template_v1.0.xlsx new file mode 100644 index 0000000..00b4c65 Binary files /dev/null and b/src/main/resources/templates/report/Award_Record_Import_Template_v1.0.xlsx differ diff --git a/src/main/resources/templates/report/Report 1 - Appreciations Case (Sample) - test.ftl b/src/main/resources/templates/report/Report 1 - Appreciations Case (Sample) - test.ftl new file mode 100644 index 0000000..ca08b2b --- /dev/null +++ b/src/main/resources/templates/report/Report 1 - Appreciations Case (Sample) - test.ftl @@ -0,0 +1,6417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Report 1: Appreciations Case + + + + + + + + + + + + + + + + + + + + + + + + + + + + + In + + + + + + + + + + + + + + + + + + ${reportDate?html}, ${monthTotal?html} + + + + + + + + + + + + + + + + + appreciation cases from customers were recorded by the Corporate Communications Sub-division (CCSD), in which, + + + <#list catBreakDowns as catBd> + + + + + + + + + + + + + + + + + + + + + ${catBd.catTotal?html} + + + + + + + + related to + + + + + + + + ${catBd.catName?html} + + + + + + + + works; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The number of appreciation cases recorded in this month was + + + + + + + + + ${compareStr1?html} + + + + + + + + the previous month + + + + + + + + + ${compareStr2?html} + + + + + + + + . Details of the appreciation cases were in + + + + + + + + + + Annex III + + + + + + + + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + ${finYear?html} Cumulative Commendations / Appreciations: + + + + + + + + + ${yearTotal?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SBUs + + + + <#list sbuYearTotal as sbuKey, sbuYearCount> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${sbuKey?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Total + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FY ${finYear?html} Cumulative Appreciations + + + + <#list sbuYearTotal as sbuKey, sbuYearCount> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${sbuYearCount?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${yearTotal?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The summary of + + + + + + + + + appreciation cases, sorted according to + + + + + + + + + TF Divisions + + + + + + + + + , + + + + + + + + + was shown + + + + + + + + + in the chart below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Chart 3 - Appreciation Cases + + + + + + + + of + + + + + + + + TF Divisions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LN Receipt Date + + + + + + : + + + + + + + ${lnReceiptDateRange?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No. + + + + + + + + + + + + + + + + + + + + + + + + + + SBU/ CSU + + + + + + + + + + + + + + + + + + + + + + + + + + Receipt date + + + + + + + + + + + + + + + + + + + + + + + + + + Brief Description + + + + + + + + + + + + + + + + + + + + + + + + + + + Reply Date + + + + + + + + + + + + + + + + + + + + + + + + + + No. of Staff + + + + + + + + + + + + + + + + + + + + + + + + + + Client Dept. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Client + + + + + + + + + + + + + + + + + + + + + + + + + Staff + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#list aprRecords as aprRecord> + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord_index + 1} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.sbu?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.receiptDate?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.description?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.clientReplyDate?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.staffReplyDate?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.numOfStaff?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.clientDeptOrg?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Cumulative Commendations/ Appreciations in FY ${finYear?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#list series as serie> + + + + + + Sheet1!$A$${serie_index + 2} + + + + ${serie.serieName?html} + + + + + + + + + + + + + + + + + Sheet1!$B$1:$M$1 + + + + Apr + + + May + + + Jun + + + Jul + + + Aug + + + Sep + + + Oct + + + Nov + + + Dec + + + Jan + + + Feb + + + Mar + + + + + + + Sheet1!$B$${serie_index + 2}:$M$${serie_index + 2} + + General + + <#list serie["data"] as serieData> + + ${serieData?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UEsDBBQABgAIAAAAIQDdK4tYbAEAABAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs +lE1PwzAMhu9I/IcqV9Rm44AQWrcDH0eYxPgBoXHXaG0Sxd7Y/j1u9iGEyiq0Xmq1id/nrR1nMts2 +dbKBgMbZXIyzkUjAFk4bu8zFx+IlvRcJkrJa1c5CLnaAYja9vposdh4w4WyLuaiI/IOUWFTQKMyc +B8srpQuNIn4NS+lVsVJLkLej0Z0snCWwlFKrIaaTJyjVuqbkecuf904C1CiSx/3GlpUL5X1tCkXs +VG6s/kVJD4SMM+MerIzHG7YhZCehXfkbcMh749IEoyGZq0CvqmEbclvLLxdWn86tsvMiHS5dWZoC +tCvWDVcgQx9AaawAqKmzGLNGGXv0fYYfN6OMYTywkfb/onCPD+J+g4zPyy1EmR4g0q4GHLrsUbSP +XKkA+p0CT8bgBn5q95VcfXIFJLVh6LZH0XN8Prfz4DzyBAf4fxeOI9pmp56FIJCB05B2HfYTkaf/ +4rZDe79o0B1sGe+z6TcAAAD//wMAUEsDBBQABgAIAAAAIQC1VTAj9AAAAEwCAAALAAgCX3JlbHMv +LnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAArJJNT8MwDIbvSPyHyPfV3ZAQQkt3QUi7IVR+gEncD7WNoyQb3b8nHBBUGoMD +R3+9fvzK2908jerIIfbiNKyLEhQ7I7Z3rYaX+nF1ByomcpZGcazhxBF21fXV9plHSnkodr2PKqu4 +qKFLyd8jRtPxRLEQzy5XGgkTpRyGFj2ZgVrGTVneYviuAdVCU+2thrC3N6Dqk8+bf9eWpukNP4g5 +TOzSmRXIc2Jn2a58yGwh9fkaVVNoOWmwYp5yOiJ5X2RswPNEm78T/XwtTpzIUiI0Evgyz0fHJaD1 +f1q0NPHLnXnENwnDq8jwyYKLH6jeAQAA//8DAFBLAwQUAAYACAAAACEACuDJU60CAADRBQAADwAA +AHhsL3dvcmtib29rLnhtbKxU32+bMBB+n7T/AfmdYvO7KKSChmqRtqlau/axcsEEK4CRMQ1d1f99 +Z1LSdXmpukUJl+PMd993d9zibGxq44HJnos2RuQEI4O1uSh4u4nRz+sLM0RGr2hb0Fq0LEaPrEdn +y8+fFjsht/dCbA0AaPsYVUp1kWX1ecUa2p+IjrUQKYVsqAJXbqy+k4wWfcWYamrLxti3GspbtEeI +5HswRFnynK1EPjSsVXsQyWqqgH5f8a6f0Zr8PXANlduhM3PRdABxz2uuHidQZDR5tN60QtL7GmSP +xDNGCV8ffgTDxZ4zQegoVcNzKXpRqhOAtvakj/QTbBHypgTjcQ3eh+Rakj1w3cMDK+l/kJV/wPJf +wQj+ZzQCozXNSgTF+yCad+Bmo+Wi5DW72Y+uQbvuO210p2pk1LRXWcEVK2IUgCt27M0NOXTpwGuI +2oHtEGQtD+N8KY2ClXSo1TUM8gwPB7HtYKxPjjKai32ppAH/16uvkPCKPkB6EFm8TOca8Ilz1+Yy +CjG5e7JXpxkOvNDMSIpNN0gDMwnTzDxPkgvnIsg8nLrPUCPpR7mgg6petGnwGLkg5Cj0jY5zhOBo +4MUrkSf88jG1/esyx561IP0W33C261+roF1jvOVtIXagndihh4zH2Sc2JuDvpvAtL1QFQjGxD/e+ +ML6pgLOjS6bfFU0tRk8kw/Yq81zTd4LUdFeJZ6aBn5hu4gVu6uMwO08nStYfnKaFAdwma7RTk6/0 +EiGwmbSd6owMGekccl1M/bTmx3Ja59BUbfTBqYWiZlf8F4PelTFKpvNsVF97tVyANQbJNVkXJwE+ +dU2cOZ7phqe2GbqObZ67KzvzgmyVpZ5ul15/0f9YAjBaxIvmvao5V1Sqa0nzLWzjH6xMaQ8TtpcH +PJcLa2ZtzU8tfwMAAP//AwBQSwMEFAAGAAgAAAAhAIE+lJfzAAAAugIAABoACAF4bC9fcmVscy93 +b3JrYm9vay54bWwucmVscyCiBAEooAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKxSTUvE +MBC9C/6HMHebdhUR2XQvIuxV6w8IybQp2yYhM3703xsqul1Y1ksvA2+Gee/Nx3b3NQ7iAxP1wSuo +ihIEehNs7zsFb83zzQMIYu2tHoJHBRMS7Orrq+0LDppzE7k+ksgsnhQ45vgoJRmHo6YiRPS50oY0 +as4wdTJqc9Adyk1Z3su05ID6hFPsrYK0t7cgmilm5f+5Q9v2Bp+CeR/R8xkJSTwNeQDR6NQhK/jB +RfYI8rz8Zk15zmvBo/oM5RyrSx6qNT18hnQgh8hHH38pknPlopm7Ve/hdEL7yim/2/Isy/TvZuTJ +x9XfAAAA//8DAFBLAwQUAAYACAAAACEAIs8P0iAEAAC6DwAAGAAAAHhsL3dvcmtzaGVldHMvc2hl +ZXQxLnhtbJyT24rbMBCG7wt9B6F7R7Fim8TEWUyT0L0olG4P14o8jkUsy5WUE6Xv3rGdZBcCJSzY +ZizNfP/81nj+dNI1OYB1yjQZDUdjSqCRplDNNqM/vq+DKSXOi6YQtWkgo2dw9Gnx8cP8aOzOVQCe +IKFxGa28b1PGnKxACzcyLTS4UxqrhcdXu2WutSCKvkjXjI/HCdNCNXQgpPYRhilLJWFp5F5D4weI +hVp47N9VqnVXmpaP4LSwu30bSKNbRGxUrfy5h1KiZfq8bYwVmxp9n8JISHKyeHG8J1eZfv1OSStp +jTOlHyGZDT3f25+xGRPyRrr3/xAmjJiFg+oO8BXF39dSGN9Y/BU2eScsucG6z2XTvSoy+ifKV3EY +rtdBzvkkiPJZFExXCQ9W8XKZ81Uyi5PoL13MC4Un3LkiFsqM5mH6ZUrZYt7Pz08FR/cmJl5sXqAG +6QE1Qkq68dwYs+sSn3FpjETXJ3REIb06wCeoawSjU/d70OCdALspvI2vaut+oL9aUkAp9rX/Zo6f +QW0rj7Ix2uzmJC3OS3ASBxSFRzzuqNLUiMAn0ar703DAxGloVRW+wigc8WkcxgnmE7l33uhfl51L +/VCJ7faVEZq87KPGfypZL/0PAAD//wAAAP//lNbfboIwFAbwVzE8wBDFPzNIslKgpe1DEEayK7eI +cdvbr9qlek6+G+709x25+A4Fi+ljHC+yv/Rlcf78XpyPSZYspq/+NPlPh2ydLH6yvB8O779ynIbx +dDkmy5fVJimL4Tb85qc9Tf77tVwW6bUs0uE/E8/ZnmbVc/ZKM/mcZeyiNQkz+suGhCsatiRc01CR +MKehJuGGhh0JtzQ0JNzR0JKQFeRI+Ggo9VuKq1rNWpWfjqtixYnVfYGssQqqDMr3EpQvBM62UBVU +DbWDaqBaqI4rKdbf9jPOgJ+OxbIKxRqdjAqqhFpDbaC2UBVUDbWDaqBaqI4rKTafVayfjsWyAyvy +e7HsvFVB2fmVQdkVaniFJii7u1uoCqqG2kE1UC1Ux5UUu5lVrJ+OxbKyxO3Zfi35owCqhFpDbYLy +YqEqqBpqB9VAtVAdV1LsdlaxfjoWy14UYguLhSqDsqdpHZQtrAnKi4WqoGqoHVQD1UJ1XEmxu1nF ++ulYLDv0Yncvlh3vKii7jyWcrYPyl1dQXixUBVVD7aAaqBaq40qK3c8q1k/HYtkfFLGHLy+oEmoN +tYHaQlVQNdQOqoFqoTquodj08S/5DwAA//8AAAD//0yMWwrCQAxFtxKyANsiIpS2/34I3ULqpDND +HymZiNt3FAb9u+dwuN1Bnu+kPu4JVp6tx/p0RdDoQ9kmx9deECYxk61QYHKsHzojzCJWoBo6o2nl +kdQSPOS5568G/yxoG12PenMN5rr65RleoksKzDa8AQAA//8DAFBLAwQUAAYACAAAACEA9mC0QbgH +AAARIgAAEwAAAHhsL3RoZW1lL3RoZW1lMS54bWzsWs2PG7cVvwfI/0DMXdbM6HthOdCnN/bueuGV +XeRISZSGXs5wQFK7KxQBCufUS4ECadFLgd56KIoGaIAGueSPMWAjTf+IPHJGmuGKir3+QJJidy8z +1O89/ua9x8c3j3P3k6uYoQsiJOVJ1wvu+B4iyYzPabLsek8m40rbQ1LhZI4ZT0jXWxPpfXLv44/u +4gMVkZggkE/kAe56kVLpQbUqZzCM5R2ekgR+W3ARYwW3YlmdC3wJemNWDX2/WY0xTTyU4BjUPlos +6IygiVbp3dsoHzG4TZTUAzMmzrRqYkkY7Pw80Ai5lgMm0AVmXQ/mmfPLCblSHmJYKvih6/nmz6ve +u1vFB7kQU3tkS3Jj85fL5QLz89DMKZbT7aT+KGzXg61+A2BqFzdq6/+tPgPAsxk8acalrDNoNP12 +mGNLoOzSobvTCmo2vqS/tsM56DT7Yd3Sb0CZ/vruM447o2HDwhtQhm/s4Ht+2O/ULLwBZfjmDr4+ +6rXCkYU3oIjR5HwX3Wy1280cvYUsODt0wjvNpt8a5vACBdGwjS49xYInal+sxfgZF2MAaCDDiiZI +rVOywDOI4l6quERDKlOG1x5KccIlDPthEEDo1f1w+28sjg8ILklrXsBE7gxpPkjOBE1V13sAWr0S +5OU337x4/vWL5/958cUXL57/Cx3RZaQyVZbcIU6WZbkf/v7H//31d+i///7bD1/+yY2XZfyrf/7+ +1bff/ZR6WGqFKV7++atXX3/18i9/+P4fXzq09wSeluETGhOJTsglesxjeEBjCps/mYqbSUwiTC0J +HIFuh+qRiizgyRozF65PbBM+FZBlXMD7q2cW17NIrBR1zPwwii3gMeesz4XTAA/1XCULT1bJ0j25 +WJVxjzG+cM09wInl4NEqhfRKXSoHEbFonjKcKLwkCVFI/8bPCXE83WeUWnY9pjPBJV8o9BlFfUyd +JpnQqRVIhdAhjcEvaxdBcLVlm+OnqM+Z66mH5MJGwrLAzEF+Qphlxvt4pXDsUjnBMSsb/AiryEXy +bC1mZdxIKvD0kjCORnMipUvmkYDnLTn9IYbE5nT7MVvHNlIoeu7SeYQ5LyOH/HwQ4Th1cqZJVMZ+ +Ks8hRDE65coFP+b2CtH34Aec7HX3U0osd78+ETyBBFemVASI/mUlHL68T7i9HtdsgYkry/REbGXX +nqDO6OivllZoHxHC8CWeE4KefOpg0OepZfOC9IMIssohcQXWA2zHqr5PiIQySdc1uynyiEorZM/I +ku/hc7y+lnjWOImx2Kf5BLxuhe5UwGJ0UHjEZudl4AmF8g/ixWmURxJ0lIJ7tE/raYStvUvfS3e8 +roXlvzdZY7Aun910XYIMubEMJPY3ts0EM2uCImAmmKIjV7oFEcv9hYjeV43Yyim3sBdt4QYojKx6 +J6bJ64qfEywEv/x5ap8PVvW4Fb9LvbMvrxxeq3L24X6Ftc0Qr5JTAtvJbuK6LW1uSxvv/7602beW +bwua24LmtqBxvYJ9kIKmqGGgvClaPabxE+/t+ywoY2dqzciRNK0fCa818zEMmp6UaUxu+4BpBJf6 +eWACC7cU2MggwdVvqIrOIpxCfygwXcylzFUvJUq5hLaRGTb9VHJNt2k+reJjPs/anaa/5GcmlFgV +434DGk/ZOLSqVIZutvJBzW9D3bBdmlbrhoCWvQmJ0mQ2iZqDRGsz+BoSunP2flh0HCzaWv3GVTum +AGpbr8B7N4K39a7XqGeMoCMHNfpc+ylz9ca72jnv1dP7jMnKEQCtxV1PdzTXvY+nny4LtTfwtEXC +OCULK5uE8ZUp8GQEb8N5dJb77j8VcDf1dadwqUVPm2KzGgoarfaH8LVOItdyA0vKmYIl6BLWeAiL +zkMznHa9BfSN4TJOIXikfvfCbAmHLzMlshX/NqklFVINsYwyi5usk/knpooIxGjc9fTzb8OBJSaJ +ZOQ6sHR/qeRCveB+aeTA67aXyWJBZqrs99KItnR2Cyk+SxbOX43424O1JF+Bu8+i+SWaspV4jCHE +Gq1Ae3dOJRwfBJmr5xTOw7aZrIi/aztTnv2tQ64iH2OWRjjfUsrZPIObDWVLx9xtbVC6y58ZDLpr +wulS77DvvO2+fq/Wliv2x06xaVppRW+b7mz64Xb5EqtiF7VYZbn7es7tbJIdBKpzm3j3vb9ErZjM +oqYZ7+ZhnbTzUZvae6wISrtPc4/dtpuE0xJvu/WD3PWo1TvEprA0gW8Ozstn23z6DJLHEE4RVyw7 +7WYJ3JnSMj0VxrdTPl/nl0xmiSbzuS5Ks1T+mCwQnV91vdBVOeaHx3k1wBJAm5oXVthW0Fnt2YJ6 +s8tFswW7Fc7K2Gv1qi28ldgcs26FTWvRRVtdbU7Uda1uZtYOy57apGFjKbjatSK0yQWG0jk7zM1y +L+SZK5VX2nCFVoJ2vd/6jV59EDYGFb/dGFXqtbpfaTd6tUqv0agFo0bgD/vh50BPRXHQyL58GMNp +EFvn3z+Y8Z1vIOLNgdedGY+r3HzjUDXeN99ABOH+byDAkUArHAX1sBcOKoNh0KzUw2Gz0m7VepVB +2ByGPdi0m+Pe5x66MOCgPxyOx42w0hwAru73GpVevzaoNNujfjgORvWhD+B8+7mCtxidc3NbwKXh +de9HAAAA//8DAFBLAwQUAAYACAAAACEAT/Yo0qkCAABXBgAADQAAAHhsL3N0eWxlcy54bWykVW1r +2zAQ/j7YfxD67sp24ywJtkvT1FDoyqAd7Ktiy4moXoykpM7G/ntPdl4cOrbRfolPp9Nzz90jXdKr +Vgq0ZcZyrTIcXYQYMVXqiqtVhr8/FcEEI+uoqqjQimV4xyy+yj9/Sq3bCfa4ZswhgFA2w2vnmhkh +tlwzSe2FbpiCnVobSR0szYrYxjBaWX9IChKH4ZhIyhXuEWay/B8QSc3zpglKLRvq+JIL7nYdFkay +nN2tlDZ0KYBqG41oidpobGLUmkOSzvsmj+Sl0VbX7gJwia5rXrK3dKdkSmh5QgLk9yFFCQnjs9pb +806kETFsy718OE9rrZxFpd4oB2ICUd+C2bPSL6rwW97ZR+Wp/Ym2VIAnwiRPSy20QQ6kg851HkUl +6yOuG6cteqDG6BcfW1PJxa7fi72jk3wfLDkI4J3Ek9l/LBziQhypxZ4FOPIUNHTMqAIWaG8/7Rrg +oOC69TBd3D+iV4buojgZHCBdwjxdalPB9T415eDKU8FqB0QNX6391+kGfpfaObgCeVpxutKKCl9K +D3I0oJySCfHon8CP+gy7rZHayEK6uyrD8Jh8Ew4mFLI3e7x+4fGHaD32h2FRW5/jA+KA9hnpY3rk +Rc/wg3+zAq7PHgItN1w4rv5AGDCr9tSC0Cvg/PvrmnPMAp2oWE03wj0dNzN8sr+yim9kfIz6xrfa +dRAZPtn3Xqlo7HOw1t1buF7wRRvDM/zrdv5lurgt4mASzifB6JIlwTSZL4JkdDNfLIppGIc3vwdT +4AMzoBtaeQqva2YFTAqzL3Zf4uPJl+HBoqff3VGgPeQ+jcfhdRKFQXEZRsFoTCfBZHyZBEUSxYvx +aH6bFMmAe/LOWRGSKOqnjiefzByXTHB10Oqg0NALIsHyL0WQgxLk9I+QvwIAAP//AwBQSwMEFAAG +AAgAAAAhAG8UpLb1AAAANgIAABQAAAB4bC9zaGFyZWRTdHJpbmdzLnhtbGTRQU/DIBQH8LuJ36Hh +7qg7GLNQlrk6jcn00M470mfbpH0gDxr37cUdjIEjPx78/wSx/Z6nYgFHo8GK3a5KVgBq043YV+zU +Hm7uWUFeYacmg1CxMxDbyusrQeSLeBapYoP3dsM56QFmRStjAePOp3Gz8nHpek7WgepoAPDzxNdl +ecdnNSIrtAnoK7aOsQHHrwD7P5CCRikuIRuySsfweAuBW4DJQnAvBf+duEzJh7apU9vn9PSY23NO +x4A5Nu+51W2WurMuLXJU55ReAuY0pbQLfUoN2JTetE/p1Swp1aCzRJWVOMBH3v7fg3j8d/kDAAD/ +/wMAUEsDBBQABgAIAAAAIQConPUAvAAAACUBAAAjAAAAeGwvd29ya3NoZWV0cy9fcmVscy9zaGVl +dDEueG1sLnJlbHOEj8EKwjAQRO+C/xD2btJ6EJGmvYjQq+gHrOm2DbZJyEbRvzfgRUHwNOwO+2an +ah7zJO4U2XqnoZQFCHLGd9YNGs6nw2oLghO6DifvSMOTGJp6uaiONGHKRzzawCJTHGsYUwo7pdiM +NCNLH8hlp/dxxpTHOKiA5ooDqXVRbFT8ZED9xRRtpyG2XQni9Aw5+T/b9701tPfmNpNLPyJUwstE +GYhxoKRByveG31LK/CyoulJf5eoXAAAA//8DAFBLAwQUAAYACAAAACEAiEc0fi4DAACgBgAAFAAA +AHhsL3RhYmxlcy90YWJsZTEueG1snJXbbuM2EIbvC/QdBN1PTFKkSBrrLHgQgV10t0CzfQBFlhOh +OhiSnI1R9N07shPLRnRR1FfyyPz4z/wz40+fX5s6ein7oeraTUzvSByVbdFtq/ZpE//5I4CKo2HM +221ed225iY/lEH++//WXT2P+WJcRnm6HTfw8jvv1ajUUz2WTD3fdvmzxza7rm3zEr/3Tatj3Zb4d +nstybOoVIyRdNXnVxmfCuin+C6TJ+78Oeyi6Zp+P1WNVV+PxxIqjplh/eWq7flK1iV/76LVP3uGv +/Qd4UxV9N3S78Q5hq263q4ryg0bKV335Uk2lmVHJ/2SlFxbqqrZYa2T268P0+DcXqVSMCzDOWeAu +ONDGGhAk455lmqWM/RNHbd5gcj+mHPH0thr2dX78fhPsy90mNnT9DX0buzGvhz+6nw/P3U90l8T3 +Z9tcVx+adoiK7tCOqCS5fTHLS970CalMlmoN0vkUOPMCrBMSiGOUBCUyJ+xFXxSvbu454diU7TvO +c2eZohacIRw4TSko6SVI5ZShPhAnkgvO7Psl4OTuBeg8FY4JDiJoBdxqBsYnCTiROk28pkHIC/Bb +flwC8mugcTaIhAfQGQnANQtgBQKJ9MRnifLeZxfg10O7BBTXwIyQhKbSgaLMAQ+OYgV1CiyzhthA +uebuClgvAdNroE7TjDBJgTAqgOMHlEgVoLZMKMOVVXyu4eFpCSivgSTDc4o6lMTQY5cxwLpxyLzN +sCEzycOc8kO5XwJi082mEK05Y5qDC1MN8RF0oikkgVOLjvmEzE39ezEuAfWNKR5zplhDprD3OLEa +DEkDJKn2UluJF6aXlL93L0tAigtulmi9cJ5LAYIbnDuO9TNOOcR6w4T0idazK74sFomnQX5v7YCN +bYX2YLWbbGFoi8Uqei8xnmqe6Dnpr/li49CbYUmItlwmFiSX2NsuJaBZJiElxCsSuKFiNjqUj4sa +b6bFEqWZMQGoFxp44g0ojnU0PLFcW8akIFfTchq/1Wnfvy2Ot/F+GI91+aXdddGACyZU/TCefzCt +mlPst/xDaFpHY1/tS/zbwMpNJ8+HLlEyZXC+7/5fAAAA//8DAFBLAwQUAAYACAAAACEAdlhs+UQB +AABnAgAAEQAIAWRvY1Byb3BzL2NvcmUueG1sIKIEASigAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAjJJRS8MwFIXfBf9DyXubtJ1uhLYDleGDgmBF8S0kd1uxSUOS2e3fm7Zb7ZgPPuaeky/n +XJIt97IOvsHYqlE5iiOCAlC8EZXa5OitXIULFFjHlGB1oyBHB7BoWVxfZVxT3hh4MY0G4yqwgScp +S7nO0dY5TTG2fAuS2cg7lBfXjZHM+aPZYM34F9sATgi5xRIcE8wx3AFDPRLRESn4iNQ7U/cAwTHU +IEE5i+Moxr9eB0baPy/0ysQpK3fQvtMx7pQt+CCO7r2tRmPbtlGb9jF8/hh/PD+99lXDSnW74oCK +THDKDTDXmKIE43cKweOuyvBk3u2wZtY9+3WvKxB3h3PrpeypfYkBDSLwsehQ4qS8p/cP5QoVCUlm +IUlDMivJnCZzmi4+u9fP7ncxh4E8Zvg3Mb2hZD4hngBFhi++RvEDAAD//wMAUEsDBBQABgAIAAAA +IQBhSQkQiQEAABEDAAAQAAgBZG9jUHJvcHMvYXBwLnhtbCCiBAEooAABAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAJySQW/bMAyF7wP6HwzdGzndUAyBrGJIV/SwYQGStmdNpmOhsiSIrJHs14+2 +0dTZeuqN5Ht4+kRJ3Rw6X/SQ0cVQieWiFAUEG2sX9pV42N1dfhUFkgm18TFAJY6A4kZffFKbHBNk +coAFRwSsREuUVlKibaEzuGA5sNLE3BniNu9lbBpn4Tbalw4CyauyvJZwIAg11JfpFCimxFVPHw2t +ox348HF3TAys1beUvLOG+Jb6p7M5Ymyo+H6w4JWci4rptmBfsqOjLpWct2prjYc1B+vGeAQl3wbq +HsywtI1xGbXqadWDpZgLdH94bVei+G0QBpxK9CY7E4ixBtvUjLVPSFk/xfyMLQChkmyYhmM5985r +90UvRwMX58YhYAJh4Rxx58gD/mo2JtM7xMs58cgw8U4424FvOnPON16ZT/onex27ZMKRhVP1w4Vn +fEi7eGsIXtd5PlTb1mSo+QVO6z4N1D1vMvshZN2asIf61fO/MDz+4/TD9fJ6UX4u+V1nMyXf/rL+ +CwAA//8DAFBLAQItABQABgAIAAAAIQDdK4tYbAEAABAFAAATAAAAAAAAAAAAAAAAAAAAAABbQ29u +dGVudF9UeXBlc10ueG1sUEsBAi0AFAAGAAgAAAAhALVVMCP0AAAATAIAAAsAAAAAAAAAAAAAAAAA +pQMAAF9yZWxzLy5yZWxzUEsBAi0AFAAGAAgAAAAhAArgyVOtAgAA0QUAAA8AAAAAAAAAAAAAAAAA +ygYAAHhsL3dvcmtib29rLnhtbFBLAQItABQABgAIAAAAIQCBPpSX8wAAALoCAAAaAAAAAAAAAAAA +AAAAAKQJAAB4bC9fcmVscy93b3JrYm9vay54bWwucmVsc1BLAQItABQABgAIAAAAIQAizw/SIAQA +ALoPAAAYAAAAAAAAAAAAAAAAANcLAAB4bC93b3Jrc2hlZXRzL3NoZWV0MS54bWxQSwECLQAUAAYA +CAAAACEA9mC0QbgHAAARIgAAEwAAAAAAAAAAAAAAAAAtEAAAeGwvdGhlbWUvdGhlbWUxLnhtbFBL +AQItABQABgAIAAAAIQBP9ijSqQIAAFcGAAANAAAAAAAAAAAAAAAAABYYAAB4bC9zdHlsZXMueG1s +UEsBAi0AFAAGAAgAAAAhAG8UpLb1AAAANgIAABQAAAAAAAAAAAAAAAAA6hoAAHhsL3NoYXJlZFN0 +cmluZ3MueG1sUEsBAi0AFAAGAAgAAAAhAKic9QC8AAAAJQEAACMAAAAAAAAAAAAAAAAAERwAAHhs +L3dvcmtzaGVldHMvX3JlbHMvc2hlZXQxLnhtbC5yZWxzUEsBAi0AFAAGAAgAAAAhAIhHNH4uAwAA +oAYAABQAAAAAAAAAAAAAAAAADh0AAHhsL3RhYmxlcy90YWJsZTEueG1sUEsBAi0AFAAGAAgAAAAh +AHZYbPlEAQAAZwIAABEAAAAAAAAAAAAAAAAAbiAAAGRvY1Byb3BzL2NvcmUueG1sUEsBAi0AFAAG +AAgAAAAhAGFJCRCJAQAAEQMAABAAAAAAAAAAAAAAAAAA6SIAAGRvY1Byb3BzL2FwcC54bWxQSwUG +AAAAAAwADAATAwAAqCUAAAAA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LEUNG YAT SING + + + Terence Hui + 3 + 2024-03-04T07:52:00Z + 2024-03-05T06:42:00Z + + + + + + + + 5 + 3 + 451 + 2573 + Microsoft Office Word + 0 + 21 + 6 + false + + false + 3018 + false + false + 16.0000 + + + + \ No newline at end of file diff --git a/src/main/resources/templates/report/Report 2 - Annex III – Summary of Appreciation Cases.ftl b/src/main/resources/templates/report/Report 2 - Annex III – Summary of Appreciation Cases.ftl new file mode 100644 index 0000000..95c622e --- /dev/null +++ b/src/main/resources/templates/report/Report 2 - Annex III – Summary of Appreciation Cases.ftl @@ -0,0 +1,2463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Report 2 - + + + + + + + + Annex III - Summary of Appreciation Cases in ${reportDate?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#list aprRecords as aprRecord> + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.index?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.description?html} + + + + + + + + + + + + + + + + + + + + + + + + ${aprRecord.category?html} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + LEUNG YAT SING + + + K14325 + 2 + 2024-03-27T08:57:00Z + 2024-03-27T08:57:00Z + + + + + + + + 0 + 1 + 90 + 519 + Microsoft Office Word + 0 + 4 + 1 + false + + false + 608 + false + false + 16.0000 + + + + \ No newline at end of file diff --git a/src/main/resources/templates/report/awardSummaryByDivision.xlsx b/src/main/resources/templates/report/awardSummaryByDivision.xlsx new file mode 100644 index 0000000..6ce2adc Binary files /dev/null and b/src/main/resources/templates/report/awardSummaryByDivision.xlsx differ diff --git a/src/main/resources/templates/report/test_sample.ftl b/src/main/resources/templates/report/test_sample.ftl new file mode 100644 index 0000000..2c45974 --- /dev/null +++ b/src/main/resources/templates/report/test_sample.ftl @@ -0,0 +1,2758 @@ + + + + + + + + + + + + + + + + + + + + ${reportTitle} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#list headers as header> + + + + + + + ${header} + + + + + + + <#list rows1 as row> + + + + + + + ${row} + + + + + + + <#list rows2 as row> + + + + + + + ${row} + + + + + + + + + + + + + Row3Col1 + + + + + + + + + + Row3Col2 + + + + + + + + + + Row3Col3 + + + + + + + + + + Row3Col4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <#list chartData["series"] as seriesKey, series> + + + + + + Sheet1!$${series["col"]}$1 + + + + ${seriesKey} + + + + + + + + + + + + + + + + + Sheet1!$A$2:$A$5 + + + <#list chartData["categories"] as category> + + ${category} + + + + + + + + Sheet1!$${series["col"]}$2:$${series["col"]}$${series["size"]} + + General + + <#list series["data"] as value> + + ${value} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + UEsDBBQABgAIAAAAIQDdK4tYbAEAABAFAAATAAgCW0NvbnRlbnRfVHlwZXNdLnhtbCCiBAIooAAC +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACs +lE1PwzAMhu9I/IcqV9Rm44AQWrcDH0eYxPgBoXHXaG0Sxd7Y/j1u9iGEyiq0Xmq1id/nrR1nMts2 +dbKBgMbZXIyzkUjAFk4bu8zFx+IlvRcJkrJa1c5CLnaAYja9vposdh4w4WyLuaiI/IOUWFTQKMyc +B8srpQuNIn4NS+lVsVJLkLej0Z0snCWwlFKrIaaTJyjVuqbkecuf904C1CiSx/3GlpUL5X1tCkXs +VG6s/kVJD4SMM+MerIzHG7YhZCehXfkbcMh749IEoyGZq0CvqmEbclvLLxdWn86tsvMiHS5dWZoC +tCvWDVcgQx9AaawAqKmzGLNGGXv0fYYfN6OMYTywkfb/onCPD+J+g4zPyy1EmR4g0q4GHLrsUbSP +XKkA+p0CT8bgBn5q95VcfXIFJLVh6LZH0XN8Prfz4DzyBAf4fxeOI9pmp56FIJCB05B2HfYTkaf/ +4rZDe79o0B1sGe+z6TcAAAD//wMAUEsDBBQABgAIAAAAIQC1VTAj9AAAAEwCAAALAAgCX3JlbHMv +LnJlbHMgogQCKKAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAArJJNT8MwDIbvSPyHyPfV3ZAQQkt3QUi7IVR+gEncD7WNoyQb3b8nHBBUGoMD +R3+9fvzK2908jerIIfbiNKyLEhQ7I7Z3rYaX+nF1ByomcpZGcazhxBF21fXV9plHSnkodr2PKqu4 +qKFLyd8jRtPxRLEQzy5XGgkTpRyGFj2ZgVrGTVneYviuAdVCU+2thrC3N6Dqk8+bf9eWpukNP4g5 +TOzSmRXIc2Jn2a58yGwh9fkaVVNoOWmwYp5yOiJ5X2RswPNEm78T/XwtTpzIUiI0Evgyz0fHJaD1 +f1q0NPHLnXnENwnDq8jwyYKLH6jeAQAA//8DAFBLAwQUAAYACAAAACEAo4s9Qq4CAADRBQAADwAA +AHhsL3dvcmtib29rLnhtbKxU32+bMBB+n7T/AfmdYvM7KKQqIdEibVO1du1j5YIJVgAjYxq6qv/7 +zqSk6/JSdYsSLuaO777v7rj5+VBXxgOTHRdNjMgZRgZrMpHzZhujn9drM0RGp2iT00o0LEaPrEPn +i8+f5nshd/dC7AwAaLoYlUq1kWV1Wclq2p2JljXgKYSsqYKj3FpdKxnNu5IxVVeWjbFv1ZQ36IAQ +yfdgiKLgGUtF1tesUQcQySqqgH5X8rab0OrsPXA1lbu+NTNRtwBxzyuuHkdQZNRZtNk2QtL7CmQP +xDMGCV8ffgTDxZ4ygeskVc0zKTpRqDOAtg6kT/QTbBHypgTDaQ3eh+Rakj1w3cMjK+l/kJV/xPJf +wQj+ZzQCozXOSgTF+yCad+Rmo8W84BW7OYyuQdv2O611pypkVLRTq5wrlscogKPYszc3ZN8mPa/A +awfE9pG1OI7zpTRyVtC+UtcwyBM8BGLbwVhHDjKain2ppAH/N+lXSHhFHyA9iMxfpnMD+MS5azIZ +hZjcPSVOEiRJ6JleQBzTDcjMvAhXS3O19NPAD4N0ZvvPUCPpR5mgvSpftGnwGLkg5MT1jQ6Th+Co +5/krkSf88jG1/esy+Z61IP0W33C2716roI/GcMubXOy1iJB4yHg8nvHMBp370X3Lc1VCDCY2xBzu +fWF8WwJnR5dMvyuaWoyeEm/tLJ1lahLPsU03xa45W4fEdGyohrte+q6djJSsPziNCwO4jdZoxiZf +6SVCYDNpO9YZGTLSOeQmJ1qUNT2W0SqDpmqjA8cWiopd8V8MelfE6GKMZ4P62qnFHKzRSw5kiYsv +AjxzTbxyPNMNZ7YZukB76ab2ygtW6SrxdLv0+ov+xxKA0SJeNO1VzbmkUl1Lmu1gG/9gRUI7mLCD +POAJIifW1vTU4jcAAAD//wMAUEsDBBQABgAIAAAAIQCBPpSX8wAAALoCAAAaAAgBeGwvX3JlbHMv +d29ya2Jvb2sueG1sLnJlbHMgogQBKKAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACsUk1L +xDAQvQv+hzB3m3YVEdl0LyLsVesPCMm0KdsmITN+9N8bKrpdWNZLLwNvhnnvzcd29zUO4gMT9cEr +qIoSBHoTbO87BW/N880DCGLtrR6CRwUTEuzq66vtCw6acxO5PpLILJ4UOOb4KCUZh6OmIkT0udKG +NGrOMHUyanPQHcpNWd7LtOSA+oRT7K2CtLe3IJopZuX/uUPb9gafgnkf0fMZCUk8DXkA0ejUISv4 +wUX2CPK8/GZNec5rwaP6DOUcq0seqjU9fIZ0IIfIRx9/KZJz5aKZu1Xv4XRC+8opv9vyLMv072bk +ycfV3wAAAP//AwBQSwMEFAAGAAgAAAAhALFsNthSAwAAKAgAABgAAAB4bC93b3Jrc2hlZXRzL3No +ZWV0MS54bWyck9tuozAQhu9X2newfE8MFNKAQqrmUG3vVnu8dswQrNiYtZ2TVvvuHUhDK0VaRZUA +DZ6Z759hhunDUSuyB+ukaQoajUJKoBGmlM2moD9/PAUTSpznTcmVaaCgJ3D0Yfb50/Rg7NbVAJ4g +oXEFrb1vc8acqEFzNzItNOipjNXc46vdMNda4GWfpBWLw3DMNJcNPRNyewvDVJUUsDRip6HxZ4gF +xT3W72rZugtNi1twmtvtrg2E0S0i1lJJf+qhlGiRP28aY/laYd/HKOGCHC1eMd53F5n+/EpJS2GN +M5UfIZmda75uP2MZ42IgXfd/EyZKmIW97Ab4hoo/VlKUDqz4DXb3Qdh4gHWfy+Y7WRb0732c3Ier +OAuWYbIMksdsFWSTbB6E8zhLl4tsHGeLf3Q2LSVOuOuKWKgK+hjlq5Sy2bTfn18SDu6dTTxffwcF +wgNqRJR067k2ZtsFPuNRiETXB3RELrzcwwKUKugco92fXgNNFGCDwnv7ovbUL/RXS0qo+E75b+bw +BeSm9iibYpvdnuTlaQlO4IKi8CjuyxZGIQKfRMvuT8MF48dzqbL0NVrRKJ6kUTrGeCJ2zhv9+9XT +VTVk4mD6zASbfPWjxn8yWS/9AgAA//8AAAD//5ST0Q6CIBiFX8XxAKiIWY3YQnoQZ25dVQtn9fb9 +gkP49SK90nMO4/vlIMyt63rd9I0Ur8c7eZ1IThLzbO4G3o4lST45b9rj9as703b3/kQyykoiRTtm +zxAGycD3ICuRDlKk7eSp0Mtirw69PPZ06LHYu4Te3nspoHt+toUfwp6/QPzMTsUp0munM8oR96Qj +4nCHwzpxsYUYwp4YEajCEjNaor/tdE55Fj14gGk5GsCpu906O9/CDmHPjhgVt+zFgt3pOZ1P2zZP +T3kE69RqrmJUDajz/9WGsIedh7ebq/ECDJIvYJ3OFrBORyNfnLrHPU7nO/kDAAD//wAAAP//TIxb +CsJADEW3ErIA2yIilLb/fgjdQuqkM0MfKZmI23cUBv2753C43UGe76Q+7glWnq3H+nRF0OhD2SbH +114QJjGTrVBgcqwfOiPMIlagGjqjaeWR1BI85Lnnrwb/LGgbXY96cw3muvrlGV6iSwrMNrwBAAD/ +/wMAUEsDBBQABgAIAAAAIQD2YLRBuAcAABEiAAATAAAAeGwvdGhlbWUvdGhlbWUxLnhtbOxazY8b +txW/B8j/QMxd1szoe2E50Kc39u564ZVd5EhJlIZeznBAUrsrFAEK59RLgQJp0UuB3nooigZogAa5 +5I8xYCNN/4g8ckaa4YqKvf5AkmJ3LzPU7z3+5r3HxzePc/eTq5ihCyIk5UnXC+74HiLJjM9psux6 +TybjSttDUuFkjhlPSNdbE+l9cu/jj+7iAxWRmCCQT+QB7nqRUulBtSpnMIzlHZ6SBH5bcBFjBbdi +WZ0LfAl6Y1YNfb9ZjTFNPJTgGNQ+WizojKCJVund2ygfMbhNlNQDMybOtGpiSRjs/DzQCLmWAybQ +BWZdD+aZ88sJuVIeYlgq+KHr+ebPq967W8UHuRBTe2RLcmPzl8vlAvPz0MwpltPtpP4obNeDrX4D +YGoXN2rr/60+A8CzGTxpxqWsM2g0/XaYY0ug7NKhu9MKaja+pL+2wznoNPth3dJvQJn++u4zjjuj +YcPCG1CGb+zge37Y79QsvAFl+OYOvj7qtcKRhTegiNHkfBfdbLXbzRy9hSw4O3TCO82m3xrm8AIF +0bCNLj3FgidqX6zF+BkXYwBoIMOKJkitU7LAM4jiXqq4REMqU4bXHkpxwiUM+2EQQOjV/XD7byyO +DwguSWtewETuDGk+SM4ETVXXewBavRLk5TffvHj+9Yvn/3nxxRcvnv8LHdFlpDJVltwhTpZluR/+ +/sf//fV36L///tsPX/7JjZdl/Kt//v7Vt9/9lHpYaoUpXv75q1dff/XyL3/4/h9fOrT3BJ6W4RMa +E4lOyCV6zGN4QGMKmz+ZiptJTCJMLQkcgW6H6pGKLODJGjMXrk9sEz4VkGVcwPurZxbXs0isFHXM +/DCKLeAx56zPhdMAD/VcJQtPVsnSPblYlXGPMb5wzT3AieXg0SqF9EpdKgcRsWieMpwovCQJUUj/ +xs8JcTzdZ5Radj2mM8ElXyj0GUV9TJ0mmdCpFUiF0CGNwS9rF0FwtWWb46eoz5nrqYfkwkbCssDM +QX5CmGXG+3ilcOxSOcExKxv8CKvIRfJsLWZl3Egq8PSSMI5GcyKlS+aRgOctOf0hhsTmdPsxW8c2 +Uih67tJ5hDkvI4f8fBDhOHVypklUxn4qzyFEMTrlygU/5vYK0ffgB5zsdfdTSix3vz4RPIEEV6ZU +BIj+ZSUcvrxPuL0e12yBiSvL9ERsZdeeoM7o6K+WVmgfEcLwJZ4Tgp586mDQ56ll84L0gwiyyiFx +BdYDbMeqvk+IhDJJ1zW7KfKISitkz8iS7+FzvL6WeNY4ibHYp/kEvG6F7lTAYnRQeMRm52XgCYXy +D+LFaZRHEnSUgnu0T+tphK29S99Ld7yuheW/N1ljsC6f3XRdggy5sQwk9je2zQQza4IiYCaYoiNX +ugURy/2FiN5XjdjKKbewF23hBiiMrHonpsnrip8TLAS//Hlqnw9W9bgVv0u9sy+vHF6rcvbhfoW1 +zRCvklMC28lu4rotbW5LG+//vrTZt5ZvC5rbgua2oHG9gn2QgqaoYaC8KVo9pvET7+37LChjZ2rN +yJE0rR8JrzXzMQyanpRpTG77gGkEl/p5YAILtxTYyCDB1W+ois4inEJ/KDBdzKXMVS8lSrmEtpEZ +Nv1Uck23aT6t4mM+z9qdpr/kZyaUWBXjfgMaT9k4tKpUhm628kHNb0PdsF2aVuuGgJa9CYnSZDaJ +moNEazP4GhK6c/Z+WHQcLNpa/cZVO6YAaluvwHs3grf1rteoZ4ygIwc1+lz7KXP1xrvaOe/V0/uM +ycoRAK3FXU93NNe9j6efLgu1N/C0RcI4JQsrm4TxlSnwZARvw3l0lvvuPxVwN/V1p3CpRU+bYrMa +Chqt9ofwtU4i13IDS8qZgiXoEtZ4CIvOQzOcdr0F9I3hMk4heKR+98JsCYcvMyWyFf82qSUVUg2x +jDKLm6yT+SemigjEaNz19PNvw4ElJolk5DqwdH+p5EK94H5p5MDrtpfJYkFmquz30oi2dHYLKT5L +Fs5fjfjbg7UkX4G7z6L5JZqylXiMIcQarUB7d04lHB8EmavnFM7DtpmsiL9rO1Oe/a1DriIfY5ZG +ON9Sytk8g5sNZUvH3G1tULrLnxkMumvC6VLvsO+87b5+r9aWK/bHTrFpWmlFb5vubPrhdvkSq2IX +tVhluft6zu1skh0EqnObePe9v0StmMyiphnv5mGdtPNRm9p7rAhKu09zj922m4TTEm+79YPc9ajV +O8SmsDSBbw7Oy2fbfPoMkscQThFXLDvtZgncmdIyPRXGt1M+X+eXTGaJJvO5LkqzVP6YLBCdX3W9 +0FU55ofHeTXAEkCbmhdW2FbQWe3Zgnqzy0WzBbsVzsrYa/WqLbyV2ByzboVNa9FFW11tTtR1rW5m +1g7LntqkYWMpuNq1IrTJBYbSOTvMzXIv5JkrlVfacIVWgna93/qNXn0QNgYVv90YVeq1ul9pN3q1 +Sq/RqAWjRuAP++HnQE9FcdDIvnwYw2kQW+ffP5jxnW8g4s2B150Zj6vcfONQNd4330AE4f5vIMCR +QCscBfWwFw4qg2HQrNTDYbPSbtV6lUHYHIY92LSb497nHrow4KA/HI7HjbDSHACu7vcalV6/Nqg0 +26N+OA5G9aEP4Hz7uYK3GJ1zc1vApeF170cAAAD//wMAUEsDBBQABgAIAAAAIQBP9ijSqQIAAFcG +AAANAAAAeGwvc3R5bGVzLnhtbKRVbWvbMBD+Pth/EPruynbjLAm2S9PUUOjKoB3sq2LLiahejKSk +zsb+e092Xhw6ttF+iU+n03PP3SNd0qtWCrRlxnKtMhxdhBgxVeqKq1WGvz8VwQQj66iqqNCKZXjH +LL7KP39KrdsJ9rhmzCGAUDbDa+eaGSG2XDNJ7YVumIKdWhtJHSzNitjGMFpZf0gKEofhmEjKFe4R +ZrL8HxBJzfOmCUotG+r4kgvudh0WRrKc3a2UNnQpgGobjWiJ2mhsYtSaQ5LO+yaP5KXRVtfuAnCJ +rmtesrd0p2RKaHlCAuT3IUUJCeOz2lvzTqQRMWzLvXw4T2utnEWl3igHYgJR34LZs9IvqvBb3tlH +5an9ibZUgCfCJE9LLbRBDqSDznUeRSXrI64bpy16oMboFx9bU8nFrt+LvaOTfB8sOQjgncST2X8s +HOJCHKnFngU48hQ0dMyoAhZobz/tGuCg4Lr1MF3cP6JXhu6iOBkcIF3CPF1qU8H1PjXl4MpTwWoH +RA1frf3X6QZ+l9o5uAJ5WnG60ooKX0oPcjSgnJIJ8eifwI/6DLutkdrIQrq7KsPwmHwTDiYUsjd7 +vH7h8YdoPfaHYVFbn+MD4oD2GeljeuRFz/CDf7MCrs8eAi03XDiu/kAYMKv21ILQK+D8++uac8wC +nahYTTfCPR03M3yyv7KKb2R8jPrGt9p1EBk+2fdeqWjsc7DW3Vu4XvBFG8Mz/Ot2/mW6uC3iYBLO +J8HokiXBNJkvgmR0M18simkYhze/B1PgAzOgG1p5Cq9rZgVMCrMvdl/i48mX4cGip9/dUaA95D6N +x+F1EoVBcRlGwWhMJ8FkfJkERRLFi/FofpsUyYB78s5ZEZIo6qeOJ5/MHJdMcHXQ6qDQ0AsiwfIv +RZCDEuT0j5C/AgAA//8DAFBLAwQUAAYACAAAACEANN5orOMAAACrAQAAFAAAAHhsL3NoYXJlZFN0 +cmluZ3MueG1sdJDBasMwEETvhf6DEL02chJTSpCVQyA/0KR3YW9tgbVytOuQUPrv3Vxc6pCj3jAz +2rHbS+zVGTKFhJVeLgqtAOvUBGwrfTzsX9+1IvbY+D4hVPoKpLfu+ckSsRIvUqU75mFjDNUdRE+L +NACK8pVy9CzP3BoaMviGOgCOvVkVxZuJPqBWdRqRpVdqRwynEXYTcJaCs+w+IAcgtbSGnTU39o+v +HvD1nO88Q5vy9T5pUu6yJuVxWjnruY2yocHXMpZcTZDPoJ2a/+YAxGU5N7uXbxbh0/cj/PxZjIzt +fgEAAP//AwBQSwMEFAAGAAgAAAAhAKic9QC8AAAAJQEAACMAAAB4bC93b3Jrc2hlZXRzL19yZWxz +L3NoZWV0MS54bWwucmVsc4SPwQrCMBBE74L/EPZu0noQkaa9iNCr6Aes6bYNtknIRtG/N+BFQfA0 +7A77ZqdqHvMk7hTZeqehlAUIcsZ31g0azqfDaguCE7oOJ+9Iw5MYmnq5qI40YcpHPNrAIlMcaxhT +Cjul2Iw0I0sfyGWn93HGlMc4qIDmigOpdVFsVPxkQP3FFG2nIbZdCeL0DDn5P9v3vTW09+Y2k0s/ +IlTCy0QZiHGgpEHK94bfUsr8LKi6Ul/l6hcAAAD//wMAUEsDBBQABgAIAAAAIQDGRoEWJAIAACYE +AAAUAAAAeGwvdGFibGVzL3RhYmxlMS54bWyck99umzAUxu8n7R2Q7138DwNRaWXASJWmXbTdA1Di +NNYAI9tpE01795kkTZa2F9OujI85v/Od89nXt9uhj16UddqMBcBXCERq7MxSj88F+PHYwAxEzrfj +su3NqAqwUw7c3nz9cu3bp15FIXt0BVh7Py3i2HVrNbTuykxqDCcrY4fWh619jt1kVbt0a6X80McE +IR4PrR7BgbAYun+BDK39uZlgZ4ap9fpJ99rv9iwQDd3i7nk0dlZVgK2Ntpa+wbf2A3zQnTXOrPxV +gMVmtdKd+qARs9iqFz2P5oyi/8niJ1bQpZdh1oFpF5v581eeVZITwiHPWQNZjjMoUiygRLwRWZXU +TUN/g2hsh9Dc49xjyF5qN/Xt7vtF0KpVAQReyARE3vi2d/fm9WFtXoO7CNwcbKtMvxlGF3VmM/oC +JJfxszp6lIcIx4wKBHlDCWQYSygwLiGpskqgqhZSoJO8CMQXZfY4Mjf7hhNc1IxVKRRZ6JHVMoO5 +QCmUrKQkTyRJJD7hHpTVykX4M+rs8ImKaVmxUtawydIwQyKaIDIRoUiaE5GHhVfvqeQzKrvUSlNO +OIMElRwyygQscU0hqWvZ4FrkDZLvqfQzavDjrwnI4GteB60pClRJUljSqoaZSDiqkorU+Vnro3Ke +MTZD4/2rO9p3nPKD3/XqblyZyAWbG22dP/wwG76PfWs/hOZL4a2eVHi84SrNmYekUxSd6938AQAA +//8DAFBLAwQUAAYACAAAACEALvK3wUgBAABnAgAAEQAIAWRvY1Byb3BzL2NvcmUueG1sIKIEASig +AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjJJRS8MwFIXfBf9DyXubpitTQtuByvDBgWBF +8S0kd1uwSUOS2e3fm3azdswHH3PPyZdzLikWe9VEX2CdbHWJSJKiCDRvhdSbEr3Wy/gWRc4zLVjT +aijRARxaVNdXBTeUtxaebWvAegkuCiTtKDcl2npvKMaOb0ExlwSHDuK6tYr5cLQbbBj/ZBvAWZrO +sQLPBPMM98DYjER0Qgo+Is3ONgNAcAwNKNDeYZIQ/Ov1YJX788KgTJxK+oMJnU5xp2zBj+Lo3js5 +GruuS7rZECPkJ/h99fQyVI2l7nfFAVWF4JRbYL61VQ027BSix50s8GTe77Bhzq/CutcSxN3h3Hop +B+pQ4ogGEYVY9FjiR3mb3T/US1RlaZbHKYnJTZ1mNJ/TnHz0r5/d72MeB+qU4R/EbFanOSUZzfIJ +8QdQFfjia1TfAAAA//8DAFBLAwQUAAYACAAAACEAYUkJEIkBAAARAwAAEAAIAWRvY1Byb3BzL2Fw +cC54bWwgogQBKKAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACckkFv2zAMhe8D+h8M3Rs5 +3VAMgaxiSFf0sGEBkrZnTaZjobIkiKyR7NePttHU2XrqjeR7ePpESd0cOl/0kNHFUInlohQFBBtr +F/aVeNjdXX4VBZIJtfExQCWOgOJGX3xSmxwTZHKABUcErERLlFZSom2hM7hgObDSxNwZ4jbvZWwa +Z+E22pcOAsmrsryWcCAINdSX6RQopsRVTx8NraMd+PBxd0wMrNW3lLyzhviW+qezOWJsqPh+sOCV +nIuK6bZgX7Kjoy6VnLdqa42HNQfrxngEJd8G6h7MsLSNcRm16mnVg6WYC3R/eG1XovhtEAacSvQm +OxOIsQbb1Iy1T0hZP8X8jC0AoZJsmIZjOffOa/dFL0cDF+fGIWACYeEccefIA/5qNibTO8TLOfHI +MPFOONuBbzpzzjdemU/6J3sdu2TCkYVT9cOFZ3xIu3hrCF7XeT5U29ZkqPkFTus+DdQ9bzL7IWTd +mrCH+tXzvzA8/uP0w/XyelF+LvldZzMl3/6y/gsAAP//AwBQSwECLQAUAAYACAAAACEA3SuLWGwB +AAAQBQAAEwAAAAAAAAAAAAAAAAAAAAAAW0NvbnRlbnRfVHlwZXNdLnhtbFBLAQItABQABgAIAAAA +IQC1VTAj9AAAAEwCAAALAAAAAAAAAAAAAAAAAKUDAABfcmVscy8ucmVsc1BLAQItABQABgAIAAAA +IQCjiz1CrgIAANEFAAAPAAAAAAAAAAAAAAAAAMoGAAB4bC93b3JrYm9vay54bWxQSwECLQAUAAYA +CAAAACEAgT6Ul/MAAAC6AgAAGgAAAAAAAAAAAAAAAAClCQAAeGwvX3JlbHMvd29ya2Jvb2sueG1s +LnJlbHNQSwECLQAUAAYACAAAACEAsWw22FIDAAAoCAAAGAAAAAAAAAAAAAAAAADYCwAAeGwvd29y +a3NoZWV0cy9zaGVldDEueG1sUEsBAi0AFAAGAAgAAAAhAPZgtEG4BwAAESIAABMAAAAAAAAAAAAA +AAAAYA8AAHhsL3RoZW1lL3RoZW1lMS54bWxQSwECLQAUAAYACAAAACEAT/Yo0qkCAABXBgAADQAA +AAAAAAAAAAAAAABJFwAAeGwvc3R5bGVzLnhtbFBLAQItABQABgAIAAAAIQA03mis4wAAAKsBAAAU +AAAAAAAAAAAAAAAAAB0aAAB4bC9zaGFyZWRTdHJpbmdzLnhtbFBLAQItABQABgAIAAAAIQConPUA +vAAAACUBAAAjAAAAAAAAAAAAAAAAADIbAAB4bC93b3Jrc2hlZXRzL19yZWxzL3NoZWV0MS54bWwu +cmVsc1BLAQItABQABgAIAAAAIQDGRoEWJAIAACYEAAAUAAAAAAAAAAAAAAAAAC8cAAB4bC90YWJs +ZXMvdGFibGUxLnhtbFBLAQItABQABgAIAAAAIQAu8rfBSAEAAGcCAAARAAAAAAAAAAAAAAAAAIUe +AABkb2NQcm9wcy9jb3JlLnhtbFBLAQItABQABgAIAAAAIQBhSQkQiQEAABEDAAAQAAAAAAAAAAAA +AAAAAAQhAABkb2NQcm9wcy9hcHAueG1sUEsFBgAAAAAMAAwAEwMAAMMjAAAAAA== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Terence Hui + + + Terence Hui + 6 + 2024-01-17T04:05:00Z + 2024-01-23T07:32:00Z + + + + + + + + 197 + 1 + 24 + 138 + Microsoft Office Word + 0 + 1 + 1 + false + + + + Title + + + 1 + + + + + + + + + + false + 161 + false + false + 16.0000 + + + + \ No newline at end of file diff --git a/src/test/java/com/ffii/lioner/LionerApplicationTests.java b/src/test/java/com/ffii/lioner/LionerApplicationTests.java new file mode 100644 index 0000000..a4c6b3c --- /dev/null +++ b/src/test/java/com/ffii/lioner/LionerApplicationTests.java @@ -0,0 +1,13 @@ +package com.ffii.lioner; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class LionerApplicationTests { + + @Test + void contextLoads() { + } + +}